Panda3D

palettizer.cxx

00001 // Filename: palettizer.cxx
00002 // Created by:  drose (01Dec00)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "palettizer.h"
00016 #include "eggFile.h"
00017 #include "textureImage.h"
00018 #include "pal_string_utils.h"
00019 #include "paletteGroup.h"
00020 #include "filenameUnifier.h"
00021 #include "textureMemoryCounter.h"
00022 
00023 #include "pnmImage.h"
00024 #include "pnmFileTypeRegistry.h"
00025 #include "pnmFileType.h"
00026 #include "eggData.h"
00027 #include "datagram.h"
00028 #include "datagramIterator.h"
00029 #include "bamReader.h"
00030 #include "bamWriter.h"
00031 #include "indent.h"
00032 
00033 Palettizer *pal = (Palettizer *)NULL;
00034 
00035 // This number is written out as the first number to the pi file, to
00036 // indicate the version of egg-palettize that wrote it out.  This
00037 // allows us to easily update egg-palettize to write out additional
00038 // information to its pi file, without having it increment the bam
00039 // version number for all bam and boo files anywhere in the world.
00040 int Palettizer::_pi_version = 20;
00041 // Updated to version 8 on 3/20/03 to remove extensions from texture key names.
00042 // Updated to version 9 on 4/13/03 to add a few properties in various places.
00043 // Updated to version 10 on 4/15/03 to add _alpha_file_channel.
00044 // Updated to version 11 on 4/30/03 to add TextureReference::_tref_name.
00045 // Updated to version 12 on 9/11/03 to add _generated_image_pattern.
00046 // Updated to version 13 on 9/13/03 to add _keep_format and _background.
00047 // Updated to version 14 on 7/26/05 to add _omit_everything.
00048 // Updated to version 15 on 8/01/05 to make TextureImages be case-insensitive.
00049 // Updated to version 16 on 4/03/06 to add Palettizer::_cutout_mode et al.
00050 // Updated to version 17 on 3/02/07 to add TextureImage::_txa_wrap_u etc.
00051 // Updated to version 18 on 5/13/08 to add TextureProperties::_quality_level.
00052 // Updated to version 19 on 7/16/09 to add PaletteGroup::_override_margin
00053 // Updated to version 20 on 7/27/09 to add TexturePlacement::_swapTextures
00054 
00055 int Palettizer::_min_pi_version = 8;
00056 // Dropped support for versions 7 and below on 7/14/03.
00057 
00058 int Palettizer::_read_pi_version = 0;
00059 
00060 TypeHandle Palettizer::_type_handle;
00061 
00062 ostream &operator << (ostream &out, Palettizer::RemapUV remap) {
00063   switch (remap) {
00064   case Palettizer::RU_never:
00065     return out << "never";
00066 
00067   case Palettizer::RU_group:
00068     return out << "per group";
00069 
00070   case Palettizer::RU_poly:
00071     return out << "per polygon";
00072 
00073   case Palettizer::RU_invalid:
00074     return out << "(invalid)";
00075   }
00076 
00077   return out << "**invalid**(" << (int)remap << ")";
00078 }
00079 
00080 
00081 // This STL function object is used in report_statistics(), below.
00082 class SortGroupsByDependencyOrder {
00083 public:
00084   bool operator ()(PaletteGroup *a, PaletteGroup *b) {
00085     if (a->get_dependency_order() != b->get_dependency_order()) {
00086       return a->get_dependency_order() < b->get_dependency_order();
00087     }
00088     return a->get_name() < b->get_name();
00089   }
00090 };
00091 
00092 // And this one is used in report_pi().
00093 class SortGroupsByPreference {
00094 public:
00095   bool operator ()(PaletteGroup *a, PaletteGroup *b) {
00096     return !a->is_preferred_over(*b);
00097   }
00098 };
00099 
00100 ////////////////////////////////////////////////////////////////////
00101 //     Function: Palettizer::Constructor
00102 //       Access: Public
00103 //  Description:
00104 ////////////////////////////////////////////////////////////////////
00105 Palettizer::
00106 Palettizer() {
00107   _is_valid = true;
00108   _noabs = false;
00109 
00110   _generated_image_pattern = "%g_palette_%p_%i";
00111   _map_dirname = "%g";
00112   _shadow_dirname = "shadow";
00113   _margin = 2;
00114   _omit_solitary = false;
00115   _omit_everything = false;
00116   _coverage_threshold = 2.5;
00117   _aggressively_clean_mapdir = true;
00118   _force_power_2 = true;
00119   _color_type = PNMFileTypeRegistry::get_global_ptr()->get_type_from_extension("png");
00120   _alpha_type = (PNMFileType *)NULL;
00121   _shadow_color_type = (PNMFileType *)NULL;
00122   _shadow_alpha_type = (PNMFileType *)NULL;
00123   _pal_x_size = _pal_y_size = 512;
00124   _background.set(0.0, 0.0, 0.0, 0.0);
00125   _cutout_mode = EggRenderMode::AM_dual;
00126   _cutout_ratio = 0.3;
00127 
00128   _round_uvs = true;
00129   _round_unit = 0.1;
00130   _round_fuzz = 0.01;
00131   _remap_uv = RU_poly;
00132   _remap_char_uv = RU_poly;
00133 
00134   get_palette_group("null");
00135 }
00136 
00137 ////////////////////////////////////////////////////////////////////
00138 //     Function: Palettizer::get_noabs
00139 //       Access: Public
00140 //  Description: Returns the current setting of the noabs flag.  See
00141 //               set_noabs().
00142 ////////////////////////////////////////////////////////////////////
00143 bool Palettizer::
00144 get_noabs() const {
00145   return _noabs;
00146 }
00147 
00148 ////////////////////////////////////////////////////////////////////
00149 //     Function: Palettizer::set_noabs
00150 //       Access: Public
00151 //  Description: Changes the current setting of the noabs flag.
00152 //
00153 //               If this flag is true, then it is an error to process
00154 //               an egg file that contains absolute pathname
00155 //               references.  This flag is intended to help detect egg
00156 //               files that are incorrectly built within a model tree
00157 //               (which should use entirely relative pathnames).
00158 //
00159 //               This flag must be set before any egg files are
00160 //               processed.
00161 ////////////////////////////////////////////////////////////////////
00162 void Palettizer::
00163 set_noabs(bool noabs) {
00164   _noabs = noabs;
00165 }
00166 
00167 ////////////////////////////////////////////////////////////////////
00168 //     Function: Palettizer::is_valid
00169 //       Access: Public
00170 //  Description: Returns true if the palette information file was read
00171 //               correctly, or false if there was some error and the
00172 //               palettization can't continue.
00173 ////////////////////////////////////////////////////////////////////
00174 bool Palettizer::
00175 is_valid() const {
00176   return _is_valid;
00177 }
00178 
00179 ////////////////////////////////////////////////////////////////////
00180 //     Function: Palettizer::report_pi
00181 //       Access: Public
00182 //  Description: Output a verbose description of all the palettization
00183 //               information to standard output, for the user's
00184 //               perusal.
00185 ////////////////////////////////////////////////////////////////////
00186 void Palettizer::
00187 report_pi() const {
00188   // Start out with the cross links and back counts; some of these are
00189   // nice to report.
00190   EggFiles::const_iterator efi;
00191   for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
00192     (*efi).second->build_cross_links();
00193   }
00194 
00195   cout
00196     << "\nparams\n"
00197     << "  generated image pattern: " << _generated_image_pattern << "\n"
00198     << "  map directory: " << _map_dirname << "\n"
00199     << "  shadow directory: "
00200     << FilenameUnifier::make_user_filename(_shadow_dirname) << "\n"
00201     << "  egg relative directory: "
00202     << FilenameUnifier::make_user_filename(_rel_dirname) << "\n"
00203     << "  palettize size: " << _pal_x_size << " by " << _pal_y_size << "\n"
00204     << "  background: " << _background << "\n"
00205     << "  margin: " << _margin << "\n"
00206     << "  coverage threshold: " << _coverage_threshold << "\n"
00207     << "  force textures to power of 2: " << yesno(_force_power_2) << "\n"
00208     << "  aggressively clean the map directory: "
00209     << yesno(_aggressively_clean_mapdir) << "\n"
00210     << "  omit everything: " << yesno(_omit_everything) << "\n"
00211     << "  round UV area: " << yesno(_round_uvs) << "\n";
00212   if (_round_uvs) {
00213     cout << "  round UV area to nearest " << _round_unit << " with fuzz "
00214          << _round_fuzz << "\n";
00215   }
00216   cout << "  remap UV's: " << _remap_uv << "\n"
00217        << "  remap UV's for characters: " << _remap_char_uv << "\n";
00218   cout << "  alpha cutouts: " << _cutout_mode << " " << _cutout_ratio << "\n";
00219 
00220   if (_color_type != (PNMFileType *)NULL) {
00221     cout << "  generate image files of type: "
00222          << _color_type->get_suggested_extension();
00223     if (_alpha_type != (PNMFileType *)NULL) {
00224       cout << "," << _alpha_type->get_suggested_extension();
00225     }
00226     cout << "\n";
00227   }
00228 
00229   if (_shadow_color_type != (PNMFileType *)NULL) {
00230     cout << "  generate shadow palette files of type: "
00231          << _shadow_color_type->get_suggested_extension();
00232     if (_shadow_alpha_type != (PNMFileType *)NULL) {
00233       cout << "," << _shadow_alpha_type->get_suggested_extension();
00234     }
00235     cout << "\n";
00236   }
00237 
00238   cout << "\ntexture source pathnames and assignments\n";
00239   Textures::const_iterator ti;
00240   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00241     TextureImage *texture = (*ti).second;
00242     if (texture->is_used()) {
00243       cout << "  " << texture->get_name() << ":\n";
00244       texture->write_source_pathnames(cout, 4);
00245     }
00246   }
00247 
00248   cout << "\negg files and textures referenced\n";
00249   EggFiles::const_iterator ei;
00250   for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
00251     EggFile *egg_file = (*ei).second;
00252     egg_file->write_description(cout, 2);
00253     egg_file->write_texture_refs(cout, 4);
00254   }
00255 
00256   // Sort the palette groups into order of preference, so that the
00257   // more specific ones appear at the bottom.
00258   pvector<PaletteGroup *> sorted_groups;
00259   Groups::const_iterator gi;
00260   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00261     sorted_groups.push_back((*gi).second);
00262   }
00263   sort(sorted_groups.begin(), sorted_groups.end(),
00264        SortGroupsByPreference());
00265 
00266   cout << "\npalette groups\n";
00267   pvector<PaletteGroup *>::iterator si;
00268   for (si = sorted_groups.begin(); si != sorted_groups.end(); ++si) {
00269     PaletteGroup *group = (*si);
00270     if (si != sorted_groups.begin()) {
00271       cout << "\n";
00272     }
00273     cout << "  " << group->get_name()
00274       //         << " (" << group->get_dirname_order() << "," << group->get_dependency_order() << ")"
00275          << ": " << group->get_groups() << "\n";
00276     group->write_image_info(cout, 4);
00277   }
00278 
00279   cout << "\ntextures\n";
00280   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00281     TextureImage *texture = (*ti).second;
00282     texture->write_scale_info(cout, 2);
00283   }
00284 
00285   cout << "\nsurprises\n";
00286   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00287     TextureImage *texture = (*ti).second;
00288     if (texture->is_surprise()) {
00289       cout << "  " << texture->get_name() << "\n";
00290     }
00291   }
00292   for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
00293     EggFile *egg_file = (*ei).second;
00294     if (egg_file->is_surprise()) {
00295       cout << "  " << egg_file->get_name() << "\n";
00296     }
00297   }
00298 
00299   cout << "\n";
00300 }
00301 
00302 ////////////////////////////////////////////////////////////////////
00303 //     Function: Palettizer::report_statistics
00304 //       Access: Public
00305 //  Description: Output a report of the palettization effectiveness,
00306 //               texture memory utilization, and so on.
00307 ////////////////////////////////////////////////////////////////////
00308 void Palettizer::
00309 report_statistics() const {
00310   // Sort the groups into order by dependency order, for the user's
00311   // convenience.
00312   pvector<PaletteGroup *> sorted_groups;
00313 
00314   Groups::const_iterator gi;
00315   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00316     sorted_groups.push_back((*gi).second);
00317   }
00318 
00319   sort(sorted_groups.begin(), sorted_groups.end(),
00320        SortGroupsByDependencyOrder());
00321 
00322   Placements overall_placements;
00323 
00324   pvector<PaletteGroup *>::const_iterator si;
00325   for (si = sorted_groups.begin();
00326        si != sorted_groups.end();
00327        ++si) {
00328     PaletteGroup *group = (*si);
00329 
00330     Placements placements;
00331     group->get_placements(placements);
00332     if (!placements.empty()) {
00333       group->get_placements(overall_placements);
00334 
00335       cout << "\n" << group->get_name() << ", by itself:\n";
00336       compute_statistics(cout, 2, placements);
00337 
00338       PaletteGroups complete;
00339       complete.make_complete(group->get_groups());
00340 
00341       if (complete.size() > 1) {
00342         Placements complete_placements;
00343         group->get_complete_placements(complete_placements);
00344         if (complete_placements.size() != placements.size()) {
00345           cout << "\n" << group->get_name()
00346                << ", with dependents (" << complete << "):\n";
00347           compute_statistics(cout, 2, complete_placements);
00348         }
00349       }
00350     }
00351   }
00352 
00353   cout << "\nOverall:\n";
00354   compute_statistics(cout, 2, overall_placements);
00355 
00356   cout << "\n";
00357 }
00358 
00359 
00360 ////////////////////////////////////////////////////////////////////
00361 //     Function: Palettizer::read_txa_file
00362 //       Access: Public
00363 //  Description: Reads in the .txa file and keeps it ready for
00364 //               matching textures and egg files.
00365 ////////////////////////////////////////////////////////////////////
00366 void Palettizer::
00367 read_txa_file(istream &txa_file, const string &txa_filename) {
00368   // Clear out the group dependencies, in preparation for reading them
00369   // again from the .txa file.
00370   Groups::iterator gi;
00371   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00372     PaletteGroup *group = (*gi).second;
00373     group->clear_depends();
00374     group->set_dirname("");
00375   }
00376 
00377   // Also reset _shadow_color_type.
00378   _shadow_color_type = (PNMFileType *)NULL;
00379   _shadow_alpha_type = (PNMFileType *)NULL;
00380 
00381   if (!_txa_file.read(txa_file, txa_filename)) {
00382     exit(1);
00383   }
00384 
00385   if (_color_type == (PNMFileType *)NULL) {
00386     nout << "No valid output image file type available; cannot run.\n"
00387          << "Use :imagetype command in .txa file.\n";
00388     exit(1);
00389   }
00390 
00391   // Compute the correct dependency level and order for each group.
00392   // This will help us when we assign the textures to their groups.
00393   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00394     PaletteGroup *group = (*gi).second;
00395     group->reset_dependency_level();
00396   }
00397 
00398   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00399     PaletteGroup *group = (*gi).second;
00400     group->set_dependency_level(1);
00401   }
00402 
00403   bool any_changed;
00404   do {
00405     any_changed = false;
00406     for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00407       PaletteGroup *group = (*gi).second;
00408       if (group->set_dependency_order()) {
00409         any_changed = true;
00410       }
00411     }
00412   } while (any_changed);
00413 }
00414 
00415 ////////////////////////////////////////////////////////////////////
00416 //     Function: Palettizer::all_params_set
00417 //       Access: Public
00418 //  Description: Called after all command line parameters have been
00419 //               set up, this is a hook to do whatever initialization
00420 //               is necessary.
00421 ////////////////////////////////////////////////////////////////////
00422 void Palettizer::
00423 all_params_set() {
00424   // Make sure the palettes have their shadow images set up properly.
00425   Groups::iterator gi;
00426   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00427     PaletteGroup *group = (*gi).second;
00428     group->setup_shadow_images();
00429   }
00430 }
00431 
00432 ////////////////////////////////////////////////////////////////////
00433 //     Function: Palettizer::process_command_line_eggs
00434 //       Access: Public
00435 //  Description: Processes all the textures named in the
00436 //               _command_line_eggs, placing them on the appropriate
00437 //               palettes or whatever needs to be done with them.
00438 //
00439 //               If force_texture_read is true, it forces each texture
00440 //               image file to be read (and thus legitimately checked
00441 //               for grayscaleness etc.) before placing.
00442 ////////////////////////////////////////////////////////////////////
00443 void Palettizer::
00444 process_command_line_eggs(bool force_texture_read, const Filename &state_filename) {
00445   _command_line_textures.clear();
00446 
00447   // Start by scanning all the egg files we read up on the command
00448   // line.
00449   CommandLineEggs::const_iterator ei;
00450   for (ei = _command_line_eggs.begin();
00451        ei != _command_line_eggs.end();
00452        ++ei) {
00453     EggFile *egg_file = (*ei);
00454 
00455     egg_file->scan_textures();
00456     egg_file->get_textures(_command_line_textures);
00457     
00458     egg_file->pre_txa_file();
00459     _txa_file.match_egg(egg_file);
00460     egg_file->post_txa_file();
00461   }
00462 
00463   // Now that all of our egg files are read in, build in all the cross
00464   // links and back pointers and stuff.
00465   EggFiles::const_iterator efi;
00466   for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
00467     (*efi).second->build_cross_links();
00468   }
00469 
00470   // Now match each of the textures mentioned in those egg files
00471   // against a line in the .txa file.
00472   CommandLineTextures::iterator ti;
00473   for (ti = _command_line_textures.begin();
00474        ti != _command_line_textures.end();
00475        ++ti) {
00476     TextureImage *texture = *ti;
00477 
00478     if (force_texture_read || texture->is_newer_than(state_filename)) {
00479       // If we're forcing a redo, or the texture image has changed,
00480       // re-read the complete image.
00481       texture->read_source_image();
00482     } else {
00483       // Otherwise, just the header is sufficient.
00484       texture->read_header();
00485     }
00486 
00487     texture->mark_texture_named();
00488     texture->pre_txa_file();
00489     _txa_file.match_texture(texture);
00490     texture->post_txa_file();
00491   }
00492 
00493   // And now, assign each of the current set of textures to an
00494   // appropriate group or groups.
00495   for (ti = _command_line_textures.begin();
00496        ti != _command_line_textures.end();
00497        ++ti) {
00498     TextureImage *texture = *ti;
00499     texture->assign_groups();
00500   }
00501 
00502   // And then the egg files need to sign up for a particular
00503   // TexturePlacement, so we can determine some more properties about
00504   // how the textures are placed (for instance, how big the UV range
00505   // is for a particular TexturePlacement).
00506   for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
00507     (*efi).second->choose_placements();
00508   }
00509 
00510   // Now that *that's* done, we need to make sure the various
00511   // TexturePlacements require the right size for their textures.
00512   for (ti = _command_line_textures.begin();
00513        ti != _command_line_textures.end();
00514        ++ti) {
00515     TextureImage *texture = *ti;
00516     texture->determine_placement_size();
00517   }
00518 
00519   // Now that each texture has been assigned to a suitable group,
00520   // make sure the textures are placed on specific PaletteImages.
00521   Groups::iterator gi;
00522   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00523     PaletteGroup *group = (*gi).second;
00524     group->update_unknown_textures(_txa_file);
00525     group->place_all();
00526   }
00527 }
00528 
00529 ////////////////////////////////////////////////////////////////////
00530 //     Function: Palettizer::process_all
00531 //       Access: Public
00532 //  Description: Reprocesses all textures known.
00533 //
00534 //               If force_texture_read is true, it forces each texture
00535 //               image file to be read (and thus legitimately checked
00536 //               for grayscaleness etc.) before placing.
00537 ////////////////////////////////////////////////////////////////////
00538 void Palettizer::
00539 process_all(bool force_texture_read, const Filename &state_filename) {
00540   // First, clear all the basic properties on the source texture
00541   // images, so we can reapply them from the complete set of egg files
00542   // and thereby ensure they are up-to-date.
00543   Textures::iterator ti;
00544   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00545     TextureImage *texture = (*ti).second;
00546     texture->clear_source_basic_properties();
00547   }
00548 
00549   // If there *were* any egg files on the command line, deal with
00550   // them.
00551   CommandLineEggs::const_iterator ei;
00552   for (ei = _command_line_eggs.begin();
00553        ei != _command_line_eggs.end();
00554        ++ei) {
00555     EggFile *egg_file = (*ei);
00556 
00557     egg_file->scan_textures();
00558     egg_file->get_textures(_command_line_textures);
00559   }
00560 
00561   // Then match up all the egg files we know about with the .txa file.
00562   EggFiles::const_iterator efi;
00563   for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
00564     EggFile *egg_file = (*efi).second;
00565     egg_file->pre_txa_file();
00566     _txa_file.match_egg(egg_file);
00567     egg_file->post_txa_file();
00568   }
00569 
00570   // Now that all of our egg files are read in, build in all the cross
00571   // links and back pointers and stuff.
00572   for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
00573     (*efi).second->build_cross_links();
00574 
00575     // Also make sure each egg file's properties are applied to the
00576     // source image (since we reset all the source image properties,
00577     // above).
00578     (*efi).second->apply_properties_to_source();
00579   }
00580 
00581   // Now match each of the textures in the world against a line in the
00582   // .txa file.
00583   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00584     TextureImage *texture = (*ti).second;
00585     if (force_texture_read || texture->is_newer_than(state_filename)) {
00586       texture->read_source_image();
00587     }
00588 
00589     texture->mark_texture_named();
00590     texture->pre_txa_file();
00591     _txa_file.match_texture(texture);
00592     texture->post_txa_file();
00593 
00594     // We need to do this to avoid bloating memory.
00595     texture->release_source_image();
00596   }
00597 
00598   // And now, assign each texture to an appropriate group or groups.
00599   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00600     TextureImage *texture = (*ti).second;
00601     texture->assign_groups();
00602   }
00603 
00604   // And then the egg files need to sign up for a particular
00605   // TexturePlacement, so we can determine some more properties about
00606   // how the textures are placed (for instance, how big the UV range
00607   // is for a particular TexturePlacement).
00608   for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
00609     (*efi).second->choose_placements();
00610   }
00611 
00612   // Now that *that's* done, we need to make sure the various
00613   // TexturePlacements require the right size for their textures.
00614   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00615     TextureImage *texture = (*ti).second;
00616     texture->determine_placement_size();
00617   }
00618 
00619   // Now that each texture has been assigned to a suitable group,
00620   // make sure the textures are placed on specific PaletteImages.
00621   Groups::iterator gi;
00622   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00623     PaletteGroup *group = (*gi).second;
00624     group->update_unknown_textures(_txa_file);
00625     group->place_all();
00626   }
00627 }
00628 
00629 ////////////////////////////////////////////////////////////////////
00630 //     Function: Palettizer::optimal_resize
00631 //       Access: Public
00632 //  Description: Attempts to resize each PalettteImage down to its
00633 //               smallest possible size.
00634 ////////////////////////////////////////////////////////////////////
00635 void Palettizer::
00636 optimal_resize() {
00637   Groups::iterator gi;
00638   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00639     PaletteGroup *group = (*gi).second;
00640     group->optimal_resize();
00641   }
00642 }
00643 
00644 ////////////////////////////////////////////////////////////////////
00645 //     Function: Palettizer::reset_images
00646 //       Access: Public
00647 //  Description: Throws away all of the current PaletteImages, so that
00648 //               new ones may be created (and the packing made more
00649 //               optimal).
00650 ////////////////////////////////////////////////////////////////////
00651 void Palettizer::
00652 reset_images() {
00653   Groups::iterator gi;
00654   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00655     PaletteGroup *group = (*gi).second;
00656     group->reset_images();
00657   }
00658 }
00659 
00660 ////////////////////////////////////////////////////////////////////
00661 //     Function: Palettizer::generate_images
00662 //       Access: Public
00663 //  Description: Actually generates the appropriate palette and
00664 //               unplaced texture images into the map directories.  If
00665 //               redo_all is true, this forces a regeneration of each
00666 //               image file.
00667 ////////////////////////////////////////////////////////////////////
00668 void Palettizer::
00669 generate_images(bool redo_all) {
00670   Groups::iterator gi;
00671   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
00672     PaletteGroup *group = (*gi).second;
00673     group->update_images(redo_all);
00674   }
00675 
00676   Textures::iterator ti;
00677   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00678     TextureImage *texture = (*ti).second;
00679     texture->copy_unplaced(redo_all);
00680   }
00681 }
00682 
00683 ////////////////////////////////////////////////////////////////////
00684 //     Function: Palettizer::read_stale_eggs
00685 //       Access: Public
00686 //  Description: Reads in any egg file that is known to be stale, even
00687 //               if it was not listed on the command line, so that it
00688 //               may be updated and written out when write_eggs() is
00689 //               called.  If redo_all is true, this even reads egg
00690 //               files that were not flagged as stale.
00691 //
00692 //               Returns true if successful, or false if there was
00693 //               some error.
00694 ////////////////////////////////////////////////////////////////////
00695 bool Palettizer::
00696 read_stale_eggs(bool redo_all) {
00697   bool okflag = true;
00698 
00699   pvector<EggFiles::iterator> invalid_eggs;
00700 
00701   EggFiles::iterator ei;
00702   for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
00703     EggFile *egg_file = (*ei).second;
00704     if (!egg_file->had_data() &&
00705         (egg_file->is_stale() || redo_all)) {
00706       if (!egg_file->read_egg(_noabs)) {
00707         invalid_eggs.push_back(ei);
00708 
00709       } else {
00710         egg_file->scan_textures();
00711         egg_file->choose_placements();
00712         egg_file->release_egg_data();
00713       }
00714     }
00715   }
00716 
00717   // Now eliminate all the invalid egg files.
00718   pvector<EggFiles::iterator>::iterator ii;
00719   for (ii = invalid_eggs.begin(); ii != invalid_eggs.end(); ++ii) {
00720     EggFiles::iterator ei = (*ii);
00721     EggFile *egg_file = (*ei).second;
00722     if (egg_file->get_source_filename().exists()) {
00723       // If there is an invalid egg file, remove it; hopefully it will
00724       // get rebuilt properly next time.
00725       nout << "Removing invalid egg file: " 
00726            << FilenameUnifier::make_user_filename(egg_file->get_source_filename())
00727            << "\n";
00728 
00729       egg_file->get_source_filename().unlink();
00730       okflag = false;
00731 
00732     } else {
00733       // If the egg file is simply missing, quietly remove any record
00734       // of it from the database.
00735       egg_file->remove_egg();
00736       _egg_files.erase(ei);
00737     }
00738   }
00739 
00740   if (!okflag) {
00741     nout << "\n"
00742          << "Some errors in egg files encountered.\n"
00743          << "Re-run make install or make opt-pal to try to regenerate these.\n\n";
00744   }
00745 
00746   return okflag;
00747 }
00748 
00749 ////////////////////////////////////////////////////////////////////
00750 //     Function: Palettizer::write_eggs
00751 //       Access: Public
00752 //  Description: Adjusts the egg files to reference the newly
00753 //               generated textures, and writes them out.  Returns
00754 //               true if successful, or false if there was some error.
00755 ////////////////////////////////////////////////////////////////////
00756 bool Palettizer::
00757 write_eggs() {
00758   bool okflag = true;
00759 
00760   EggFiles::iterator ei;
00761   for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
00762     EggFile *egg_file = (*ei).second;
00763     if (egg_file->had_data()) {
00764       if (!egg_file->has_data()) {
00765         // Re-read the egg file.
00766         bool read_ok = egg_file->read_egg(_noabs);
00767         if (!read_ok) {
00768           nout << "Error!  Unable to re-read egg file.\n";
00769           okflag = false;
00770         }
00771       }
00772 
00773       if (egg_file->has_data()) {
00774         egg_file->update_egg();
00775         if (!egg_file->write_egg()) {
00776           okflag = false;
00777         }
00778         egg_file->release_egg_data();
00779       }
00780     }
00781   }
00782 
00783   return okflag;
00784 }
00785 
00786 ////////////////////////////////////////////////////////////////////
00787 //     Function: Palettizer::get_egg_file
00788 //       Access: Public
00789 //  Description: Returns the EggFile with the given name.  If there is
00790 //               no EggFile with the indicated name, creates one.
00791 //               This is the key name used to sort the egg files,
00792 //               which is typically the basename of the filename.
00793 ////////////////////////////////////////////////////////////////////
00794 EggFile *Palettizer::
00795 get_egg_file(const string &name) {
00796   EggFiles::iterator ei = _egg_files.find(name);
00797   if (ei != _egg_files.end()) {
00798     return (*ei).second;
00799   }
00800 
00801   EggFile *file = new EggFile;
00802   file->set_name(name);
00803   _egg_files.insert(EggFiles::value_type(name, file));
00804   return file;
00805 }
00806 
00807 ////////////////////////////////////////////////////////////////////
00808 //     Function: Palettizer::remove_egg_file
00809 //       Access: Public
00810 //  Description: Removes the named egg file from the database, if it
00811 //               exists.  Returns true if the egg file was found,
00812 //               false if it was not.
00813 ////////////////////////////////////////////////////////////////////
00814 bool Palettizer::
00815 remove_egg_file(const string &name) {
00816   EggFiles::iterator ei = _egg_files.find(name);
00817   if (ei != _egg_files.end()) {
00818     EggFile *file = (*ei).second;
00819     file->remove_egg();
00820     _egg_files.erase(ei);
00821     return true;
00822   }
00823 
00824   return false;
00825 }
00826 
00827 ////////////////////////////////////////////////////////////////////
00828 //     Function: Palettizer::add_command_line_egg
00829 //       Access: Public
00830 //  Description: Adds the indicated EggFile to the list of eggs that
00831 //               are considered to have been read on the command line.
00832 //               These will be processed by
00833 //               process_command_line_eggs().
00834 ////////////////////////////////////////////////////////////////////
00835 void Palettizer::
00836 add_command_line_egg(EggFile *egg_file) {
00837   _command_line_eggs.push_back(egg_file);
00838 }
00839 
00840 ////////////////////////////////////////////////////////////////////
00841 //     Function: Palettizer::get_palette_group
00842 //       Access: Public
00843 //  Description: Returns the PaletteGroup with the given name.  If
00844 //               there is no PaletteGroup with the indicated name,
00845 //               creates one.
00846 ////////////////////////////////////////////////////////////////////
00847 PaletteGroup *Palettizer::
00848 get_palette_group(const string &name) {
00849   Groups::iterator gi = _groups.find(name);
00850   if (gi != _groups.end()) {
00851     return (*gi).second;
00852   }
00853 
00854   PaletteGroup *group = new PaletteGroup;
00855   group->set_name(name);
00856   _groups.insert(Groups::value_type(name, group));
00857   return group;
00858 }
00859 
00860 ////////////////////////////////////////////////////////////////////
00861 //     Function: Palettizer::test_palette_group
00862 //       Access: Public
00863 //  Description: Returns the PaletteGroup with the given name.  If
00864 //               there is no PaletteGroup with the indicated name,
00865 //               returns NULL.
00866 ////////////////////////////////////////////////////////////////////
00867 PaletteGroup *Palettizer::
00868 test_palette_group(const string &name) const {
00869   Groups::const_iterator gi = _groups.find(name);
00870   if (gi != _groups.end()) {
00871     return (*gi).second;
00872   }
00873 
00874   return (PaletteGroup *)NULL;
00875 }
00876 
00877 ////////////////////////////////////////////////////////////////////
00878 //     Function: Palettizer::get_default_group
00879 //       Access: Public
00880 //  Description: Returns the default group to which an egg file should
00881 //               be assigned if it is not mentioned in the .txa file.
00882 ////////////////////////////////////////////////////////////////////
00883 PaletteGroup *Palettizer::
00884 get_default_group() {
00885   PaletteGroup *default_group = get_palette_group(_default_groupname);
00886   if (!_default_groupdir.empty() && !default_group->has_dirname()) {
00887     default_group->set_dirname(_default_groupdir);
00888   }
00889   return default_group;
00890 }
00891 
00892 ////////////////////////////////////////////////////////////////////
00893 //     Function: Palettizer::get_texture
00894 //       Access: Public
00895 //  Description: Returns the TextureImage with the given name.  If
00896 //               there is no TextureImage with the indicated name,
00897 //               creates one.  This is the key name used to sort the
00898 //               textures, which is typically the basename of the
00899 //               primary filename.
00900 ////////////////////////////////////////////////////////////////////
00901 TextureImage *Palettizer::
00902 get_texture(const string &name) {
00903   // Look first in the same-case name, just in case it happens to be
00904   // there (from an older version of egg-palettize that did this).
00905   Textures::iterator ti = _textures.find(name);
00906   if (ti != _textures.end()) {
00907     return (*ti).second;
00908   }
00909 
00910   // Then look in the downcase name, since we nowadays index textures
00911   // only by their downcase names (to implement case insensitivity).
00912   string downcase_name = downcase(name);
00913   ti = _textures.find(downcase_name);
00914   if (ti != _textures.end()) {
00915     return (*ti).second;
00916   }
00917 
00918   TextureImage *image = new TextureImage;
00919   image->set_name(name);
00920   //  image->set_filename(name);
00921   _textures.insert(Textures::value_type(downcase_name, image));
00922 
00923   return image;
00924 }
00925 
00926 ////////////////////////////////////////////////////////////////////
00927 //     Function: Palettizer::yesno
00928 //       Access: Private, Static
00929 //  Description: A silly function to return "yes" or "no" based on a
00930 //               bool flag for nicely formatted output.
00931 ////////////////////////////////////////////////////////////////////
00932 const char *Palettizer::
00933 yesno(bool flag) {
00934   return flag ? "yes" : "no";
00935 }
00936 
00937 ////////////////////////////////////////////////////////////////////
00938 //     Function: Palettizer::string_remap
00939 //       Access: Public, Static
00940 //  Description: Returns the RemapUV code corresponding to the
00941 //               indicated string, or RU_invalid if the string is
00942 //               invalid.
00943 ////////////////////////////////////////////////////////////////////
00944 Palettizer::RemapUV Palettizer::
00945 string_remap(const string &str) {
00946   if (str == "never") {
00947     return RU_never;
00948 
00949   } else if (str == "group") {
00950     return RU_group;
00951 
00952   } else if (str == "poly") {
00953     return RU_poly;
00954 
00955   } else {
00956     return RU_invalid;
00957   }
00958 }
00959 
00960 ////////////////////////////////////////////////////////////////////
00961 //     Function: Palettizer::compute_statistics
00962 //       Access: Private
00963 //  Description: Determines how much memory, etc. is required by the
00964 //               indicated set of texture placements, and reports this
00965 //               to the indicated output stream.
00966 ////////////////////////////////////////////////////////////////////
00967 void Palettizer::
00968 compute_statistics(ostream &out, int indent_level,
00969                    const Palettizer::Placements &placements) const {
00970   TextureMemoryCounter counter;
00971 
00972   Placements::const_iterator pi;
00973   for (pi = placements.begin(); pi != placements.end(); ++pi) {
00974     TexturePlacement *placement = (*pi);
00975     counter.add_placement(placement);
00976   }
00977 
00978   counter.report(out, indent_level);
00979 }
00980 
00981 ////////////////////////////////////////////////////////////////////
00982 //     Function: Palettizer::register_with_read_factory
00983 //       Access: Public, Static
00984 //  Description: Registers the current object as something that can be
00985 //               read from a Bam file.
00986 ////////////////////////////////////////////////////////////////////
00987 void Palettizer::
00988 register_with_read_factory() {
00989   BamReader::get_factory()->
00990     register_factory(get_class_type(), make_Palettizer);
00991 }
00992 
00993 ////////////////////////////////////////////////////////////////////
00994 //     Function: Palettizer::write_datagram
00995 //       Access: Public, Virtual
00996 //  Description: Fills the indicated datagram up with a binary
00997 //               representation of the current object, in preparation
00998 //               for writing to a Bam file.
00999 ////////////////////////////////////////////////////////////////////
01000 void Palettizer::
01001 write_datagram(BamWriter *writer, Datagram &datagram) {
01002   TypedWritable::write_datagram(writer, datagram);
01003 
01004   datagram.add_int32(_pi_version);
01005   datagram.add_string(_generated_image_pattern);
01006   datagram.add_string(_map_dirname);
01007   datagram.add_string(FilenameUnifier::make_bam_filename(_shadow_dirname));
01008   datagram.add_string(FilenameUnifier::make_bam_filename(_rel_dirname));
01009   datagram.add_int32(_pal_x_size);
01010   datagram.add_int32(_pal_y_size);
01011   datagram.add_float64(_background[0]);
01012   datagram.add_float64(_background[1]);
01013   datagram.add_float64(_background[2]);
01014   datagram.add_float64(_background[3]);
01015   datagram.add_int32(_margin);
01016   datagram.add_bool(_omit_solitary);
01017   datagram.add_bool(_omit_everything);
01018   datagram.add_float64(_coverage_threshold);
01019   datagram.add_bool(_force_power_2);
01020   datagram.add_bool(_aggressively_clean_mapdir);
01021   datagram.add_bool(_round_uvs);
01022   datagram.add_float64(_round_unit);
01023   datagram.add_float64(_round_fuzz);
01024   datagram.add_int32((int)_remap_uv);
01025   datagram.add_int32((int)_remap_char_uv);
01026   datagram.add_uint8((int)_cutout_mode);
01027   datagram.add_float64(_cutout_ratio);
01028 
01029   writer->write_pointer(datagram, _color_type);
01030   writer->write_pointer(datagram, _alpha_type);
01031   writer->write_pointer(datagram, _shadow_color_type);
01032   writer->write_pointer(datagram, _shadow_alpha_type);
01033 
01034   datagram.add_int32(_egg_files.size());
01035   EggFiles::const_iterator ei;
01036   for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
01037     writer->write_pointer(datagram, (*ei).second);
01038   }
01039 
01040   // We don't write _command_line_eggs; that's specific to each
01041   // session.
01042 
01043   datagram.add_int32(_groups.size());
01044   Groups::const_iterator gi;
01045   for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
01046     writer->write_pointer(datagram, (*gi).second);
01047   }
01048 
01049   datagram.add_int32(_textures.size());
01050   Textures::const_iterator ti;
01051   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
01052     writer->write_pointer(datagram, (*ti).second);
01053   }
01054 }
01055 
01056 ////////////////////////////////////////////////////////////////////
01057 //     Function: Palettizer::complete_pointers
01058 //       Access: Public, Virtual
01059 //  Description: Called after the object is otherwise completely read
01060 //               from a Bam file, this function's job is to store the
01061 //               pointers that were retrieved from the Bam file for
01062 //               each pointer object written.  The return value is the
01063 //               number of pointers processed from the list.
01064 ////////////////////////////////////////////////////////////////////
01065 int Palettizer::
01066 complete_pointers(TypedWritable **p_list, BamReader *manager) {
01067   int index = TypedWritable::complete_pointers(p_list, manager);
01068 
01069   if (p_list[index] != (TypedWritable *)NULL) {
01070     DCAST_INTO_R(_color_type, p_list[index], index);
01071   }
01072   index++;
01073 
01074   if (p_list[index] != (TypedWritable *)NULL) {
01075     DCAST_INTO_R(_alpha_type, p_list[index], index);
01076   }
01077   index++;
01078 
01079   if (p_list[index] != (TypedWritable *)NULL) {
01080     DCAST_INTO_R(_shadow_color_type, p_list[index], index);
01081   }
01082   index++;
01083 
01084   if (p_list[index] != (TypedWritable *)NULL) {
01085     DCAST_INTO_R(_shadow_alpha_type, p_list[index], index);
01086   }
01087   index++;
01088 
01089   int i;
01090   for (i = 0; i < _num_egg_files; i++) {
01091     EggFile *egg_file;
01092     DCAST_INTO_R(egg_file, p_list[index], index);
01093     _egg_files.insert(EggFiles::value_type(egg_file->get_name(), egg_file));
01094     index++;
01095   }
01096 
01097   for (i = 0; i < _num_groups; i++) {
01098     PaletteGroup *group;
01099     DCAST_INTO_R(group, p_list[index], index);
01100     _groups.insert(Groups::value_type(group->get_name(), group));
01101     index++;
01102   }
01103 
01104   for (i = 0; i < _num_textures; i++) {
01105     TextureImage *texture;
01106     DCAST_INTO_R(texture, p_list[index], index);
01107 
01108     string name = downcase(texture->get_name());
01109     pair<Textures::iterator, bool> result = _textures.insert(Textures::value_type(name, texture));
01110     if (!result.second) {
01111       // Two textures mapped to the same slot--probably a case error
01112       // (since we just changed this rule).
01113       _texture_conflicts.push_back(texture);
01114     }
01115     index++;
01116   }
01117 
01118   return index;
01119 }
01120 
01121 ////////////////////////////////////////////////////////////////////
01122 //     Function: Palettizer::finalize
01123 //       Access: Public, Virtual
01124 //  Description: Called by the BamReader to perform any final actions
01125 //               needed for setting up the object after all objects
01126 //               have been read and all pointers have been completed.
01127 ////////////////////////////////////////////////////////////////////
01128 void Palettizer::
01129 finalize(BamReader *manager) {
01130   // Walk through the list of texture names that were in conflict.
01131   // These can only happen if there were two different names that
01132   // different only in case, which means the textures.boo file was
01133   // created before we introduced the rule that case is insignificant.
01134   TextureConflicts::iterator ci;
01135   for (ci = _texture_conflicts.begin(); 
01136        ci != _texture_conflicts.end(); 
01137        ++ci) {
01138     TextureImage *texture_b = (*ci);
01139     string downcase_name = downcase(texture_b->get_name());
01140 
01141     Textures::iterator ti = _textures.find(downcase_name);
01142     nassertv(ti != _textures.end());
01143     TextureImage *texture_a = (*ti).second;
01144     _textures.erase(ti);
01145 
01146     if (!texture_b->is_used() || !texture_a->is_used()) {
01147       // If either texture is not used, there's not really a
01148       // conflict--the other one wins.
01149       if (texture_a->is_used()) {
01150         bool inserted1 = _textures.insert(Textures::value_type(downcase_name, texture_a)).second;
01151         nassertd(inserted1) { }
01152 
01153       } else if (texture_b->is_used()) {
01154         bool inserted2 = _textures.insert(Textures::value_type(downcase_name, texture_b)).second;
01155         nassertd(inserted2) { }
01156       }
01157 
01158     } else {
01159       // If both textures are used, there *is* a conflict.
01160       nout << "Texture name conflict: \"" << texture_a->get_name()
01161            << "\" vs. \"" << texture_b->get_name() << "\"\n";
01162       if (texture_a->get_name() != downcase_name &&
01163           texture_b->get_name() != downcase_name) {
01164         // Arbitrarily pick texture_a to get the right case.
01165         bool inserted1 = _textures.insert(Textures::value_type(downcase_name, texture_a)).second;
01166         bool inserted2 = _textures.insert(Textures::value_type(texture_b->get_name(), texture_b)).second;
01167         nassertd(inserted1 && inserted2) { }
01168 
01169       } else {
01170         // One of them is already the right case.
01171         bool inserted1 = _textures.insert(Textures::value_type(texture_a->get_name(), texture_a)).second;
01172         bool inserted2 = _textures.insert(Textures::value_type(texture_b->get_name(), texture_b)).second;
01173         nassertd(inserted1 && inserted2) { }
01174       }
01175     }
01176   }
01177 }
01178 
01179 
01180 ////////////////////////////////////////////////////////////////////
01181 //     Function: Palettizer::make_Palettizer
01182 //       Access: Protected
01183 //  Description: This method is called by the BamReader when an object
01184 //               of this type is encountered in a Bam file; it should
01185 //               allocate and return a new object with all the data
01186 //               read.
01187 ////////////////////////////////////////////////////////////////////
01188 TypedWritable* Palettizer::
01189 make_Palettizer(const FactoryParams &params) {
01190   Palettizer *me = new Palettizer;
01191   DatagramIterator scan;
01192   BamReader *manager;
01193 
01194   parse_params(params, scan, manager);
01195   me->fillin(scan, manager);
01196   manager->register_finalize(me);
01197 
01198   return me;
01199 }
01200 
01201 ////////////////////////////////////////////////////////////////////
01202 //     Function: Palettizer::fillin
01203 //       Access: Protected
01204 //  Description: Reads the binary data from the given datagram
01205 //               iterator, which was written by a previous call to
01206 //               write_datagram().
01207 ////////////////////////////////////////////////////////////////////
01208 void Palettizer::
01209 fillin(DatagramIterator &scan, BamReader *manager) {
01210   TypedWritable::fillin(scan, manager);
01211 
01212   _read_pi_version = scan.get_int32();
01213   if (_read_pi_version > _pi_version || _read_pi_version < _min_pi_version) {
01214     // Oops, we don't know how to read this palette information file.
01215     _is_valid = false;
01216     return;
01217   }
01218   if (_read_pi_version >= 12) {
01219     _generated_image_pattern = scan.get_string();
01220   }
01221   _map_dirname = scan.get_string();
01222   _shadow_dirname = FilenameUnifier::get_bam_filename(scan.get_string());
01223   _rel_dirname = FilenameUnifier::get_bam_filename(scan.get_string());
01224   FilenameUnifier::set_rel_dirname(_rel_dirname);
01225   _pal_x_size = scan.get_int32();
01226   _pal_y_size = scan.get_int32();
01227   if (_read_pi_version >= 13) {
01228     _background[0] = scan.get_float64();
01229     _background[1] = scan.get_float64();
01230     _background[2] = scan.get_float64();
01231     _background[3] = scan.get_float64();
01232   }
01233   _margin = scan.get_int32();
01234   _omit_solitary = scan.get_bool();
01235   if (_read_pi_version >= 14) {
01236     _omit_everything = scan.get_bool();
01237   }
01238   _coverage_threshold = scan.get_float64();
01239   _force_power_2 = scan.get_bool();
01240   _aggressively_clean_mapdir = scan.get_bool();
01241   _round_uvs = scan.get_bool();
01242   _round_unit = scan.get_float64();
01243   _round_fuzz = scan.get_float64();
01244   _remap_uv = (RemapUV)scan.get_int32();
01245   _remap_char_uv = (RemapUV)scan.get_int32();
01246   if (_read_pi_version >= 16) {
01247     _cutout_mode = (EggRenderMode::AlphaMode)scan.get_uint8();
01248     _cutout_ratio = scan.get_float64();
01249   }
01250 
01251   manager->read_pointer(scan);  // _color_type
01252   manager->read_pointer(scan);  // _alpha_type
01253   manager->read_pointer(scan);  // _shadow_color_type
01254   manager->read_pointer(scan);  // _shadow_alpha_type
01255 
01256   _num_egg_files = scan.get_int32();
01257   manager->read_pointers(scan, _num_egg_files);
01258 
01259   _num_groups = scan.get_int32();
01260   manager->read_pointers(scan, _num_groups);
01261 
01262   _num_textures = scan.get_int32();
01263   manager->read_pointers(scan, _num_textures);
01264 }
 All Classes Functions Variables Enumerations