Panda3D
|
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 ¶ms) { 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 }