From 43f23a2a55359a400da2fa168d34f4146b75ab75 Mon Sep 17 00:00:00 2001 From: David Rose Date: Fri, 3 Nov 2000 23:55:40 +0000 Subject: [PATCH] *** empty log message *** --- dtool/Config.Irix.pp | 3 + dtool/Config.Linux.pp | 3 + dtool/Config.Win32.pp | 3 + dtool/LocalSetup.pp | 3 + dtool/pptempl/Depends.pp | 4 + dtool/src/dtoolutil/filename.cxx | 47 + dtool/src/dtoolutil/filename.h | 2 + pandatool/src/egg-palettize/Sources.pp | 20 + pandatool/src/egg-palettize/attribFile.cxx | 1191 +++++++++++++++++ pandatool/src/egg-palettize/attribFile.h | 132 ++ .../egg-palettize/config_egg_palettize.cxx | 14 + pandatool/src/egg-palettize/eggPalettize.cxx | 592 ++++++++ pandatool/src/egg-palettize/eggPalettize.h | 71 + pandatool/src/egg-palettize/imageFile.cxx | 14 + pandatool/src/egg-palettize/imageFile.h | 28 + pandatool/src/egg-palettize/palette.cxx | 544 ++++++++ pandatool/src/egg-palettize/palette.h | 90 ++ pandatool/src/egg-palettize/sourceEgg.cxx | 347 +++++ pandatool/src/egg-palettize/sourceEgg.h | 75 ++ pandatool/src/egg-palettize/string_utils.cxx | 80 ++ pandatool/src/egg-palettize/string_utils.h | 20 + pandatool/src/egg-palettize/texture.cxx | 586 ++++++++ pandatool/src/egg-palettize/texture.h | 131 ++ .../src/egg-palettize/userAttribLine.cxx | 362 +++++ pandatool/src/egg-palettize/userAttribLine.h | 86 ++ pandatool/src/eggbase/Sources.pp | 8 +- pandatool/src/eggbase/eggBase.cxx | 2 +- pandatool/src/eggbase/eggFilter.h | 2 + pandatool/src/eggbase/eggMultiBase.cxx | 104 ++ pandatool/src/eggbase/eggMultiBase.h | 45 + pandatool/src/eggbase/eggMultiFilter.cxx | 160 +++ pandatool/src/eggbase/eggMultiFilter.h | 40 + pandatool/src/eggprogs/eggTrans.cxx | 2 +- pandatool/src/flt/fltHeader.cxx | 8 +- pandatool/src/flt/fltHeader.h | 1 + pandatool/src/fltprogs/fltCopy.cxx | 6 + pandatool/src/fltprogs/fltCopy.h | 5 +- pandatool/src/progbase/programBase.cxx | 49 + pandatool/src/progbase/programBase.h | 1 + 39 files changed, 4873 insertions(+), 8 deletions(-) create mode 100644 pandatool/src/egg-palettize/Sources.pp create mode 100644 pandatool/src/egg-palettize/attribFile.cxx create mode 100644 pandatool/src/egg-palettize/attribFile.h create mode 100644 pandatool/src/egg-palettize/config_egg_palettize.cxx create mode 100644 pandatool/src/egg-palettize/eggPalettize.cxx create mode 100644 pandatool/src/egg-palettize/eggPalettize.h create mode 100644 pandatool/src/egg-palettize/imageFile.cxx create mode 100644 pandatool/src/egg-palettize/imageFile.h create mode 100644 pandatool/src/egg-palettize/palette.cxx create mode 100644 pandatool/src/egg-palettize/palette.h create mode 100644 pandatool/src/egg-palettize/sourceEgg.cxx create mode 100644 pandatool/src/egg-palettize/sourceEgg.h create mode 100644 pandatool/src/egg-palettize/string_utils.cxx create mode 100644 pandatool/src/egg-palettize/string_utils.h create mode 100644 pandatool/src/egg-palettize/texture.cxx create mode 100644 pandatool/src/egg-palettize/texture.h create mode 100644 pandatool/src/egg-palettize/userAttribLine.cxx create mode 100644 pandatool/src/egg-palettize/userAttribLine.h create mode 100644 pandatool/src/eggbase/eggMultiBase.cxx create mode 100644 pandatool/src/eggbase/eggMultiBase.h create mode 100644 pandatool/src/eggbase/eggMultiFilter.cxx create mode 100644 pandatool/src/eggbase/eggMultiFilter.h diff --git a/dtool/Config.Irix.pp b/dtool/Config.Irix.pp index 633e944aa6..c292e40f4c 100644 --- a/dtool/Config.Irix.pp +++ b/dtool/Config.Irix.pp @@ -81,6 +81,9 @@ // Do we have ? #define HAVE_UNISTD_H 1 +// Do we have ? +#define HAVE_UTIME_H 1 + // Do we have (and presumably a Linux-style audio // interface)? #define HAVE_SYS_SOUNDCARD_H diff --git a/dtool/Config.Linux.pp b/dtool/Config.Linux.pp index edc0354fae..081fb907ca 100644 --- a/dtool/Config.Linux.pp +++ b/dtool/Config.Linux.pp @@ -81,6 +81,9 @@ // Do we have ? #define HAVE_UNISTD_H 1 +// Do we have ? +#define HAVE_UTIME_H 1 + // Do we have (and presumably a Linux-style audio // interface)? #define HAVE_SYS_SOUNDCARD_H 1 diff --git a/dtool/Config.Win32.pp b/dtool/Config.Win32.pp index 00457a3bbe..be8f5a7ff3 100644 --- a/dtool/Config.Win32.pp +++ b/dtool/Config.Win32.pp @@ -81,6 +81,9 @@ // Do we have ? #define HAVE_UNISTD_H +// Do we have ? +#define HAVE_UTIME_H + // Do we have (and presumably a Linux-style audio // interface)? #define HAVE_SYS_SOUNDCARD_H diff --git a/dtool/LocalSetup.pp b/dtool/LocalSetup.pp index c15ee18f28..721563b4eb 100644 --- a/dtool/LocalSetup.pp +++ b/dtool/LocalSetup.pp @@ -130,6 +130,9 @@ $[cdefine HAVE_SYS_TYPES] /* Define if you have the header file. */ $[cdefine HAVE_UNISTD_H] +/* Define if you have the header file. */ +$[cdefine HAVE_UTIME_H] + /* Do we have (and presumably a Linux-style audio interface)? */ $[cdefine HAVE_SYS_SOUNDCARD_H] diff --git a/dtool/pptempl/Depends.pp b/dtool/pptempl/Depends.pp index 9ac5d52dc1..ea0b403de1 100644 --- a/dtool/pptempl/Depends.pp +++ b/dtool/pptempl/Depends.pp @@ -31,6 +31,10 @@ Warning: Lib(s) $[nonexisting], referenced in $[DIRNAME]/$[TARGET], not found. #set DEPENDABLE_HEADERS $[DEPENDABLE_HEADERS] $[filter %.h %.I,$[SOURCES]] #end metalib_target static_lib_target ss_lib_target lib_target noinst_lib_target bin_target noinst_bin_target + #forscopes test_bin_target + #set DEPENDABLE_HEADERS $[DEPENDABLE_HEADERS] $[filter %.h %.I,$[SOURCES]] + #end test_bin_target + // Allow the user to define additional EXTRA_DEPENDS targets in each // Sources.pp. #define DEPEND_DIRS \ diff --git a/dtool/src/dtoolutil/filename.cxx b/dtool/src/dtoolutil/filename.cxx index 907369141e..dbfade1af1 100644 --- a/dtool/src/dtoolutil/filename.cxx +++ b/dtool/src/dtoolutil/filename.cxx @@ -18,6 +18,14 @@ #include // For rename() #include +#ifdef HAVE_UTIME_H +#include + +// We assume we have these too. +#include +#include +#endif + #if defined(WIN32) /* begin Win32-specific code */ @@ -787,6 +795,45 @@ open_read_write(fstream &stream) const { return (!stream.fail()); } +//////////////////////////////////////////////////////////////////// +// Function: Filename::touch +// Access: Public +// Description: Updates the modification time of the file to the +// current time. If the file does not already exist, it +// will be created. Returns true if successful, false +// if there is an error. +//////////////////////////////////////////////////////////////////// +bool Filename:: +touch() const { +#ifdef HAVE_UTIME_H + // Most Unix systems can do this explicitly. + string filename = to_os_specific(); + int result = utime(filename.c_str(), NULL); + if (result < 0) { + if (errno == ENOENT) { + // So the file doesn't already exist; create it. + int fd = creat(filename.c_str(), 0666); + if (fd < 0) { + perror(filename.c_str()); + return false; + } + close(fd); + return true; + } + perror(filename.c_str()); + return false; + } + return true; +#else + // Other systems may not have an explicit control over the + // modification time. For these systems, we'll just temporary open + // the file in append mode, then close it again (it gets closed when + // the ofstream goes out of scope). + ofstream file; + return open_append(file); +#endif +} + //////////////////////////////////////////////////////////////////// // Function: Filename::unlink // Access: Public diff --git a/dtool/src/dtoolutil/filename.h b/dtool/src/dtoolutil/filename.h index 347df2b56b..f047f1277f 100644 --- a/dtool/src/dtoolutil/filename.h +++ b/dtool/src/dtoolutil/filename.h @@ -130,6 +130,8 @@ public: bool open_append(ofstream &stream) const; bool open_read_write(fstream &stream) const; + bool touch() const; + bool unlink() const; bool rename_to(const Filename &other) const; diff --git a/pandatool/src/egg-palettize/Sources.pp b/pandatool/src/egg-palettize/Sources.pp new file mode 100644 index 0000000000..ee019be043 --- /dev/null +++ b/pandatool/src/egg-palettize/Sources.pp @@ -0,0 +1,20 @@ +#begin bin_target + #define TARGET egg-palettize + #define LOCAL_LIBS \ + eggbase progbase + #define OTHER_LIBS \ + egg:c linmath:c putil:c express:c pnmimagetypes:c \ + pandaegg:m panda:m pandaexpress:m \ + dtoolutil:c dconfig:c dtool:m pystub + + #define SOURCES \ + attribFile.cxx attribFile.h config_egg_palettize.cxx \ + eggPalettize.cxx eggPalettize.h \ + imageFile.cxx imageFile.h palette.cxx palette.h sourceEgg.cxx \ + sourceEgg.h string_utils.cxx string_utils.h texture.cxx texture.h \ + userAttribLine.cxx userAttribLine.h + + #define INSTALL_HEADERS \ + +#end bin_target + diff --git a/pandatool/src/egg-palettize/attribFile.cxx b/pandatool/src/egg-palettize/attribFile.cxx new file mode 100644 index 0000000000..d3dc1f8ec6 --- /dev/null +++ b/pandatool/src/egg-palettize/attribFile.cxx @@ -0,0 +1,1191 @@ +// Filename: attribFile.cxx +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#include "attribFile.h" +#include "userAttribLine.h" +#include "eggPalettize.h" +#include "string_utils.h" +#include "texture.h" +#include "palette.h" +#include "sourceEgg.h" + +#include +#include +#include +#include +#include +#include + +AttribFile:: +AttribFile(const Filename &filename) { + _name = filename.get_basename_wo_extension(); + _txa_filename = filename; + _txa_filename.set_extension("txa"); + _pi_filename = filename; + _pi_filename.set_extension("pi"); + _txa_fd = -1; + + _optimal = false; + _txa_needs_rewrite = false; + + _palette_prefix = _name + "-palette."; + _pal_xsize = 512; + _pal_ysize = 512; + _default_margin = 2; + _force_power_2 = false; + _aggressively_clean_mapdir = false; +} + +string AttribFile:: +get_name() const { + return _name; +} + +bool AttribFile:: +grab_lock() { + if (!_txa_filename.exists()) { + nout << "Attributes file " << _txa_filename << " does not exist.\n"; + } + + _txa_fd = open(_txa_filename.c_str(), O_RDWR | O_CREAT, 0666); + if (_txa_fd < 0) { + perror(_txa_filename.c_str()); + return false; + } + + struct flock fl; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + if (fcntl(_txa_fd, F_SETLK, &fl) < 0) { + nout << "Waiting for lock on " << _txa_filename << "\n"; + while (fcntl(_txa_fd, F_SETLKW, &fl) < 0) { + if (errno != EINTR) { + perror(_txa_filename.c_str()); + return false; + } + } + } + + _txa_fstrm.attach(_txa_fd); + + return true; +} + +bool AttribFile:: +release_lock() { + // Closing the fstream will close the fd, and thus release all the + // file locks. + _txa_fstrm.close(); + _txa_fd = -1; + + return true; +} + +bool AttribFile:: +read() { + bool okflag = true; + + okflag = read_txa(_txa_fstrm); + + { + if (!_pi_filename.exists()) { + nout << "Palette information file " << _pi_filename << " does not exist.\n"; + } else { + ifstream infile(_pi_filename.c_str()); + if (!infile) { + nout << "Palette information file " << _pi_filename << " exists, but cannot be read.\n"; + return false; + } + + okflag = read_pi(infile); + } + } + + return okflag; +} + +bool AttribFile:: +write() { + bool okflag = true; + + if (_txa_needs_rewrite) { + // Rewind and truncate the file for writing. + _txa_fstrm.clear(); + _txa_fstrm.seekp(0, ios::beg); + ftruncate(_txa_fd, 0); + + okflag = write_txa(_txa_fstrm) && okflag; + _txa_fstrm << flush; + } + + { + ofstream outfile(_pi_filename.c_str(), ios::out, 0666); + if (!outfile) { + nout << "Unable to write file " << _pi_filename << "\n"; + return false; + } + + okflag = write_pi(outfile) && okflag; + } + + return okflag; +} + +void AttribFile:: +update_params(EggPalettize *prog) { + if (prog->_got_map_dirname) { + _map_dirname = prog->_map_dirname; + } + if (prog->_got_palette_size) { + _pal_xsize = prog->_pal_size[0]; + _pal_ysize = prog->_pal_size[1]; + } + if (prog->_got_default_margin) { + _default_margin = prog->_default_margin; + } + if (prog->_got_force_power_2) { + _force_power_2 = prog->_force_power_2; + } + if (prog->_got_aggressively_clean_mapdir) { + _aggressively_clean_mapdir = prog->_aggressively_clean_mapdir; + } +} + +void AttribFile:: +get_req_sizes() { + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *tex = (*ti).second; + tex->clear_req(); + + int margin = _default_margin; + UserLines::const_iterator ui; + + bool matched = false; + for (ui = _user_lines.begin(); + ui != _user_lines.end() && !matched; + ++ui) { + matched = (*ui)->match_texture(tex, margin); + } + + if (matched) { + tex->set_matched_anything(true); + } + } +} + +// Update the unused flags on all textures to accurately reflect +// those that are unused by any egg files. Omit unused textures +// from the palettizing set. +void AttribFile:: +update_texture_flags() { + // First, clear all the flags. + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *tex = (*ti).second; + tex->set_unused(true); + tex->set_uses_alpha(false); + } + + // Then, for each egg file, mark all the textures it's known to be + // using, and update the repeat and alpha flags. + Eggs::const_iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + SourceEgg *egg = (*ei).second; + egg->mark_texture_flags(); + } + + // Now go back through and omit any unused textures. This is also a + // fine time to mark the textures' original packing state, so we can + // check later to see if they've been repacked elsewhere. + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *tex = (*ti).second; + tex->record_orig_state(); + + if (tex->unused()) { + tex->set_omit(Texture::OR_unused); + } + } +} + +// Clear out all the old packing order and start again from the top. +// This should get as nearly optimal a packing as this poor little +// algorithm can manage. +void AttribFile:: +repack_all_textures() { + // First, delete all the existing palettes. + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + // Remove the old palette file? + if (_aggressively_clean_mapdir && + !(*pi)->get_filename().empty()) { + if (access((*pi)->get_filename().c_str(), F_OK) == 0) { + nout << "Deleting " << (*pi)->get_filename() << "\n"; + unlink((*pi)->get_filename().c_str()); + } + } + + delete (*pi); + } + _palettes.clear(); + + // Reorder the textures in descending order by height and width for + // optimal packing. + vector textures; + get_eligible_textures(textures); + + // Now pack all the textures. This will create new palettes. + vector::iterator ti; + for (ti = textures.begin(); ti != textures.end(); ++ti) { + pack_texture(*ti); + } + + _optimal = true; +} + +// Add new textures into the palettes without disturbing whatever was +// already there. This won't generate an optimal palette, but it +// won't require rebuilding every egg file that already uses this +// palette. +void AttribFile:: +repack_some_textures() { + bool empty_before = _palettes.empty(); + bool any_added = false; + + // Reorder the textures in descending order by height and width for + // optimal packing. + vector textures; + get_eligible_textures(textures); + + // Now pack whatever textures are currently unpacked. + vector::iterator ti; + for (ti = textures.begin(); ti != textures.end(); ++ti) { + Texture *tex = (*ti); + if (!tex->is_packed()) { + if (pack_texture(tex)) { + any_added = true; + } + } + } + + _optimal = (empty_before || !any_added); +} + +void AttribFile:: +optimal_resize() { + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + (*pi)->optimal_resize(); + } +} + +void AttribFile:: +finalize_palettes() { + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + (*pi)->finalize_palette(); + } +} + +void AttribFile:: +remove_unused_lines() { + UserLines::iterator read, write; + + read = _user_lines.begin(); + write = _user_lines.begin(); + while (read != _user_lines.end()) { + if ((*read)->was_used()) { + (*write++) = (*read++); + } else { + delete (*read); + _txa_needs_rewrite = true; + read++; + } + } + _user_lines.erase(write, _user_lines.end()); +} + +// Checks that each texture that wants packing has been packed, that +// no textures that don't need packing have been packed, and that all +// textures are packed at their correct sizes. Returns true if no +// changes need to be made, false otherwise. +bool AttribFile:: +check_packing(bool force_optimal) { + bool all_ok = true; + + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *texture = (*ti).second; + + if (texture->get_omit() == Texture::OR_none) { + // Here's a texture that thinks it wants to be packed. Does it? + int xsize, ysize; + if (!texture->get_req(xsize, ysize)) { + // If we don't know the texture's size, we can't place it. + nout << "Warning! Can't determine size of " << texture->get_name() + << "\n"; + texture->set_omit(Texture::OR_unknown); + + } else if ((xsize > _pal_xsize || ysize > _pal_ysize) || + (xsize == _pal_xsize && ysize == _pal_ysize)) { + // If the texture is too big for the palette (or exactly fills the + // palette), we can't place it. + texture->set_omit(Texture::OR_size); + + } else { + // Ok, this texture really does want to be packed. Is it? + int px, py, m; + if (texture->get_packed_size(px, py, m)) { + // The texture is packed. Does it have the right size? + if (px != xsize || py != ysize) { + // Oops, we'll have to repack it. + unpack_texture(texture); + _optimal = false; + all_ok = false; + } + if (m != texture->get_margin()) { + // The margin has changed, although not the size. We + // won't have to repack it, but we do need to update it. + texture->set_changed(true); + } + } else { + // The texture isn't packed. Need to pack it. + all_ok = false; + } + } + } + + if (texture->get_omit() != Texture::OR_none) { + // Here's a texture that doesn't want to be packed. Is it? + if (unpack_texture(texture)) { + // It was! Not any more. + _optimal = false; + all_ok = false; + } + } + } + + if (force_optimal && !_optimal) { + // If the user wants to insist on an optimal packing, we'll have + // to give it to him. + all_ok = false; + } + + return all_ok; +} + + +bool AttribFile:: +pack_texture(Texture *texture) { + // Now try to place it in each of our existing palettes. + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + if ((*pi)->pack_texture(texture)) { + return true; + } + } + + // It didn't place anywhere; create a new palette for it. + Palette *palette = + new Palette(_palettes.size() + 1, _pal_xsize, _pal_ysize, 0, this); + if (!palette->pack_texture(texture)) { + // Hmm, it didn't fit on an empty palette. Must be too big. + texture->set_omit(Texture::OR_size); + delete palette; + return false; + } + _palettes.push_back(palette); + + return true; +} + +bool AttribFile:: +unpack_texture(Texture *texture) { + if (texture->is_packed()) { + bool unpacked = texture->get_palette()->unpack_texture(texture); + assert(unpacked); + return true; + } + + // It wasn't packed. + return false; +} + +// Updates the timestamp on each egg file that will need to be +// rebuilt, so that a future make process will pick it up. This is +// only necessary to update egg files that may not have been included +// on the command line, and which we don't have direct access to. +void AttribFile:: +touch_dirty_egg_files(bool force_redo_all, + bool eggs_include_images) { + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + SourceEgg *egg = (*ei).second; + + if (egg->needs_rebuild(force_redo_all, eggs_include_images)) { + Filename filename = egg->get_egg_filename(); + filename.set_extension("pt"); + nout << "Touching " << filename << "\n"; + if (!filename.touch()) { + nout << "unable to touch " << filename << "\n"; + } + } + } +} + + +Texture *AttribFile:: +get_texture(const string &name) { + Textures::iterator ti; + ti = _textures.find(name); + if (ti != _textures.end()) { + return (*ti).second; + } + + Texture *texture = new Texture(this, name); + _textures[name] = texture; + return texture; +} + +void AttribFile:: +get_eligible_textures(vector &textures) { + // First, copy the texture pointers into this map structure to sort + // them in descending order by size. This is a 2-d map such that + // each map[ysize][xsize] is a set of texture pointers. + typedef map > > TexBySize; + TexBySize tex_by_size; + int num_textures = 0; + + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *texture = (*ti).second; + + if (texture->get_omit() == Texture::OR_none) { + int xsize, ysize; + if (texture->get_req(xsize, ysize)) { + tex_by_size[-ysize][-xsize].insert(texture); + num_textures++; + } + } + } + + // Now walk through this map and get out our textures, nicely sorted + // in descending order by height and width. + textures.clear(); + textures.reserve(num_textures); + + TexBySize::const_iterator t1; + for (t1 = tex_by_size.begin(); t1 != tex_by_size.end(); ++t1) { + map >::const_iterator t2; + for (t2 = (*t1).second.begin(); t2 != (*t1).second.end(); ++t2) { + set::const_iterator t3; + for (t3 = (*t2).second.begin(); t3 != (*t2).second.end(); ++t3) { + textures.push_back(*t3); + } + } + } +} + +SourceEgg *AttribFile:: +get_egg(Filename name) { + Eggs::iterator ei; + ei = _eggs.find(name); + if (ei != _eggs.end()) { + return (*ei).second; + } + + SourceEgg *egg = new SourceEgg(); + egg->resolve_egg_filename(name); + egg->set_egg_filename(name); + _eggs[name] = egg; + return egg; +} + +bool AttribFile:: +generate_palette_images() { + bool okflag = true; + + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + Palette *palette = (*pi); + if (palette->new_palette()) { + // If the palette is a new palette, we'll have to generate a new + // image from scratch. + okflag = palette->generate_image() && okflag; + } else { + // Otherwise, we can probably get by with just updating + // whichever images, if any, have changed. + okflag = palette->refresh_image() && okflag; + } + } + + return okflag; +} + +bool AttribFile:: +transfer_unplaced_images(bool force_redo_all) { + bool okflag = true; + + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *texture = (*ti).second; + + if (texture->get_omit() != Texture::OR_none && + texture->get_omit() != Texture::OR_unused) { + // Here's a texture that needs to be moved to our mapdir. But + // maybe it's already there and hasn't changed recently. + if (force_redo_all || texture->needs_refresh()) { + // Nope, needs to be updated. + okflag = texture->transfer() && okflag; + } + } else { + if (_aggressively_clean_mapdir) { + if (access(texture->get_filename().c_str(), F_OK) == 0) { + nout << "Deleting " << texture->get_filename() << "\n"; + unlink(texture->get_filename().c_str()); + } + } + } + } + + return okflag; +} + + +void AttribFile:: +check_dup_textures(map &textures, + map &dup_textures) const { + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *texture = (*ti).second; + string name = texture->get_name(); + + map::iterator mi = textures.find(name); + if (mi == textures.end()) { + // This texture hasn't been used yet. + textures[name] = texture; + + } else { + // This texture has already been used in another palette. The + // smaller of the two is considered wasted space. + Texture *other = (*mi).second; + + if (!other->is_really_packed() && !texture->is_really_packed()) { + // No, neither one is packed, so it's not wasted space. + + } else { + int txsize, tysize; + int oxsize, oysize; + int wasted_size = 0; + + if (other->is_really_packed() != texture->is_really_packed()) { + // If one texture is packed and the other isn't, the packed + // one is considered wasted space. + if (other->is_really_packed()) { + if (other->get_req(oxsize, oysize)) { + wasted_size = oxsize * oysize; + } + (*mi).second = texture; + } else { + if (texture->get_req(txsize, tysize)) { + wasted_size = txsize * tysize; + } + } + + } else { + // Both textures are packed. The smaller one is considered + // wasted space. + assert(other->is_really_packed() && texture->is_really_packed()); + + if (texture->get_req(txsize, tysize) && + other->get_req(oxsize, oysize)) { + if (txsize * tysize <= oxsize * oysize) { + wasted_size = txsize * tysize; + } else { + wasted_size = oxsize * oysize; + (*mi).second = texture; + } + } + } + + // Now update the wasted space total for this texture. + map::iterator di = dup_textures.find(name); + if (di != dup_textures.end()) { + (*di).second += wasted_size; + } else { + dup_textures[name] = wasted_size; + } + } + } + } +} + +void AttribFile:: +collect_statistics(int &num_textures, int &num_placed, int &num_palettes, + int &orig_size, int &resized_size, + int &palette_size, int &unplaced_size) const { + num_textures = _textures.size(); + num_palettes = _palettes.size(); + num_placed = 0; + orig_size = 0; + resized_size = 0; + palette_size = 0; + unplaced_size = 0; + + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *texture = (*ti).second; + + int xsize, ysize; + int rxsize, rysize; + int rsize = 0; + if (texture->get_size(xsize, ysize) && + texture->get_last_req(rxsize, rysize)) { + orig_size += xsize * ysize; + resized_size += rxsize * rysize; + rsize = rxsize * rysize; + } + + if (texture->is_really_packed()) { + num_placed++; + } else { + unplaced_size += rsize; + } + } + + Palettes::const_iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + Palette *palette = (*pi); + if (palette->get_num_textures() > 1) { + int xsize, ysize; + palette->get_size(xsize, ysize); + palette_size += xsize * ysize; + } + } +} + + +bool AttribFile:: +read_txa(istream &infile) { + string line; + + getline(infile, line); + int line_num = 1; + + while (!infile.eof()) { + UserAttribLine *ul = new UserAttribLine(line, this); + if (!ul->is_valid()) { + nout << "Error at line " << line_num << " of " << _txa_filename << "\n"; + return false; + } + if (ul->is_old_style()) { + _txa_needs_rewrite = true; + } + _user_lines.push_back(ul); + + getline(infile, line); + line_num++; + } + return true; +} + +bool AttribFile:: +read_pi(istream &infile) { + string line; + + getline(infile, line); + int line_num = 1; + + while (!infile.eof()) { + // First, strip off the comment. + if (!line.empty()) { + if (line[0] == '#') { + line = ""; + } else { + size_t pos = line.find(" #"); + if (pos != string::npos) { + line = line.substr(0, pos - 1); + } + } + } + + vector words = extract_words(line); + bool okflag = true; + + if (words.empty()) { + getline(infile, line); + line_num++; + + } else if (words[0] == "params") { + okflag = parse_params(words, infile, line, line_num); + + } else if (words[0] == "packing") { + okflag = parse_packing(words, infile, line, line_num); + + } else if (words[0] == "textures") { + okflag = parse_texture(words, infile, line, line_num); + + } else if (words[0] == "pathnames") { + okflag = parse_pathname(words, infile, line, line_num); + + } else if (words[0] == "egg") { + okflag = parse_egg(words, infile, line, line_num); + + } else if (words[0] == "palette") { + okflag = parse_palette(words, infile, line, line_num); + + } else if (words[0] == "unplaced") { + okflag = parse_unplaced(words, infile, line, line_num); + + } else if (words[0] == "surprises") { + okflag = parse_surprises(words, infile, line, line_num); + + } else { + nout << "Invalid keyword: " << words[0] << "\n"; + okflag = false; + } + + if (!okflag) { + nout << "Error at line " << line_num << " of " << _pi_filename << "\n"; + return false; + } + } + + return true; +} + +bool AttribFile:: +write_txa(ostream &out) const { + UserLines::const_iterator ui; + + for (ui = _user_lines.begin(); ui != _user_lines.end(); ++ui) { + (*ui)->write(out); + } + + if (!out) { + nout << "I/O error when writing to " << _txa_filename << "\n"; + return false; + } + return true; +} + +bool AttribFile:: +write_pi(ostream &out) const { + out << + "# This file was generated by egg-palettize. Edit it at your own peril.\n"; + + out << "\nparams\n" + << " map_directory " << _map_dirname << "\n" + << " pal_xsize " << _pal_xsize << "\n" + << " pal_ysize " << _pal_ysize << "\n" + << " default_margin " << _default_margin << "\n" + << " force_power_2 " << _force_power_2 << "\n" + << " aggressively_clean_mapdir " << _aggressively_clean_mapdir << "\n"; + + if (_optimal) { + out << "\npacking is optimal\n"; + // Well, as nearly as this program can do it, anyway. + } else { + out << "\npacking is suboptimal\n"; + } + + out << "\npathnames\n"; + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + (*ti).second->write_pathname(out); + } + + Eggs::const_iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + out << "\n"; + (*ei).second->write_pi(out); + } + + Palettes::const_iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + out << "\n"; + (*pi)->write(out); + } + + out << "\n"; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + (*ti).second->write_unplaced(out); + } + + // Sort textures in descending order by scale percent. + typedef multimap SortTextures; + SortTextures sort_textures; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *texture = (*ti).second; + sort_textures.insert(SortTextures::value_type(-texture->get_scale_pct(), + texture)); + } + + bool any_surprises = false; + + out << "\ntextures\n"; + SortTextures::const_iterator sti; + for (sti = sort_textures.begin(); sti != sort_textures.end(); ++sti) { + Texture *texture = (*sti).second; + texture->write_size(out); + any_surprises = any_surprises || !texture->matched_anything(); + } + + if (any_surprises) { + // Some textures didn't match any commands; they're "surprise" + // textures. + out << "\nsurprises\n"; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *texture = (*ti).second; + if (!texture->matched_anything()) { + out << " " << texture->get_name() << "\n"; + } + } + } + + if (!out) { + nout << "I/O error when writing to " << _pi_filename << "\n"; + return false; + } + + return true; +} + +bool AttribFile:: +parse_params(const vector &words, istream &infile, + string &line, int &line_num) { + if (words.size() != 1) { + nout << "Unexpected keywords on line.\n"; + return false; + } + + getline(infile, line); + line = trim_right(line); + line_num++; + while (!infile.eof() && !line.empty() && isspace(line[0])) { + string param, value; + extract_param_value(line, param, value); + + if (param == "map_directory") { + _map_dirname = value; + + // These are all deprecated. + } else if (param == "converted_directory") { + } else if (param == "convert_extension") { + } else if (param == "convert_command") { + } else if (param == "palette_prefix") { + } else if (param == "map_prefix") { + + } else if (param == "pal_xsize") { + _pal_xsize = atoi(value.c_str()); + } else if (param == "pal_ysize") { + _pal_ysize = atoi(value.c_str()); + } else if (param == "default_margin") { + _default_margin = atoi(value.c_str()); + } else if (param == "force_power_2") { + _force_power_2 = atoi(value.c_str()); + } else if (param == "aggressively_clean_mapdir") { + _aggressively_clean_mapdir = atoi(value.c_str()); + } else { + nout << "Unexpected keyword: " << param << "\n"; + return false; + } + + getline(infile, line); + line = trim_right(line); + line_num++; + } + + return true; +} + +bool AttribFile:: +parse_packing(const vector &words, istream &infile, + string &line, int &line_num) { + if (words.size() != 3 || words[1] != "is" || + (words[2] != "optimal" && words[2] != "suboptimal")) { + nout << "Expected 'packing is {optimal|suboptimal}'\n"; + return false; + } + + _optimal = (words[2] == "optimal"); + + getline(infile, line); + line_num++; + return true; +} + + +bool AttribFile:: +parse_texture(const vector &words, istream &infile, + string &line, int &line_num) { + if (words.size() != 1) { + nout << "Unexpected words on line.\n"; + return false; + } + + getline(infile, line); + line = trim_right(line); + line_num++; + while (!infile.eof() && !line.empty() && isspace(line[0])) { + vector twords = extract_words(line); + if (twords.size() < 1) { + nout << "Expected texture name and additional parameters.\n"; + return false; + } + Texture *texture = get_texture(twords[0]); + + int kw = 1; + while (kw < (int)twords.size()) { + if (kw + 3 <= (int)twords.size() && twords[kw] == "orig") { + texture->set_size(atoi(twords[kw + 1].c_str()), + atoi(twords[kw + 2].c_str())); + kw += 3; + + } else if (kw + 3 <= (int)twords.size() && twords[kw] == "new") { + texture->set_last_req(atoi(twords[kw + 1].c_str()), + atoi(twords[kw + 2].c_str())); + kw += 3; + + } else if (twords[kw].find('%') != string::npos) { + // Ignore scale percentage. + kw++; + + } else { + nout << "Unexpected keyword: " << twords[kw] << "\n"; + } + } + + getline(infile, line); + line = trim_right(line); + line_num++; + } + + return true; +} + +bool AttribFile:: +parse_pathname(const vector &words, istream &infile, + string &line, int &line_num) { + if (words.size() != 1) { + nout << "Unexpected words on line.\n"; + return false; + } + + getline(infile, line); + line = trim_right(line); + line_num++; + Texture *texture = NULL; + + while (!infile.eof() && !line.empty() && isspace(line[0])) { + vector twords = extract_words(line); + if (twords.size() == 1) { + // Only one word on the line means it's an alternate filename + // for the previous texture. + if (texture == NULL) { + nout << "Expected texture name and pathname.\n"; + return false; + } + texture->add_filename(twords[0]); + + } else if (twords.size() == 2) { + // Two words on the line means it's a texture name and filename. + texture = get_texture(twords[0]); + texture->add_filename(twords[1]); + + } else { + // Anything else is a mistake. + nout << "Expected texture name and pathname.\n"; + return false; + } + + getline(infile, line); + line = trim_right(line); + line_num++; + } + + return true; +} + +bool AttribFile:: +parse_egg(const vector &words, istream &infile, + string &line, int &line_num) { + if (words.size() != 2) { + nout << "Egg filename expected.\n"; + return false; + } + + SourceEgg *egg = get_egg(words[1]); + + getline(infile, line); + line = trim_right(line); + line_num++; + while (!infile.eof() && !line.empty() && isspace(line[0])) { + vector twords = extract_words(line); + if (twords.size() < 1) { + nout << "Expected texture name\n"; + return false; + } + + string name = twords[0]; + bool repeats = false; + bool alpha = false; + + int kw = 1; + while (kw < (int)twords.size()) { + if (twords[kw] == "repeats") { + repeats = true; + kw++; + + } else if (twords[kw] == "alpha") { + alpha = true; + kw++; + + } else { + nout << "Unexpected keyword " << twords[kw] << "\n"; + return false; + } + } + + egg->add_texture(get_texture(name), repeats, alpha); + + getline(infile, line); + line = trim_right(line); + line_num++; + } + + return true; +} + + + +bool AttribFile:: +parse_palette(const vector &words, istream &infile, + string &line, int &line_num) { + if (words.size() != 6) { + nout << "Palette filename, size, and number of components expected.\n"; + return false; + } + + string filename = words[1]; + if (words[2] != "size") { + nout << "Expected keyword 'size'\n"; + return false; + } + int xsize = atoi(words[3].c_str()); + int ysize = atoi(words[4].c_str()); + int components = atoi(words[5].c_str()); + + Palette *palette = new Palette(filename, xsize, ysize, components, this); + _palettes.push_back(palette); + + getline(infile, line); + line = trim_right(line); + line_num++; + while (!infile.eof() && !line.empty() && isspace(line[0])) { + vector twords = extract_words(line); + if (twords.size() != 9) { + nout << "Expected texture placement line.\n"; + return false; + } + + Texture *texture = get_texture(twords[0]); + + if (twords[1] != "at") { + nout << "Expected keyword 'at'\n"; + return false; + } + int left = atoi(twords[2].c_str()); + int top = atoi(twords[3].c_str()); + + if (twords[4] != "size") { + nout << "Expected keyword 'size'\n"; + return false; + } + int xsize = atoi(twords[5].c_str()); + int ysize = atoi(twords[6].c_str()); + + if (twords[7] != "margin") { + nout << "Expected keyword 'margin'\n"; + return false; + } + int margin = atoi(twords[8].c_str()); + + palette->place_texture_at(texture, left, top, xsize, ysize, margin); + + getline(infile, line); + line = trim_right(line); + line_num++; + } + + return true; +} + + + +bool AttribFile:: +parse_unplaced(const vector &words, istream &infile, + string &line, int &line_num) { + if (words.size() != 4) { + nout << "Unplaced texture description expected.\n"; + return false; + } + + Texture *texture = get_texture(words[1]); + + if (words[2] != "because") { + nout << "Expected keyword 'because'\n"; + return false; + } + + if (words[3] == "size") { + texture->set_omit(Texture::OR_size); + } else if (words[3] == "repeats") { + texture->set_omit(Texture::OR_repeats); + } else if (words[3] == "omitted") { + texture->set_omit(Texture::OR_omitted); + } else if (words[3] == "unused") { + texture->set_omit(Texture::OR_unused); + } else if (words[3] == "unknown") { + texture->set_omit(Texture::OR_unknown); + } else if (words[3] == "solitary") { + texture->set_omit(Texture::OR_solitary); + } else if (words[3] == "cmdline") { + texture->set_omit(Texture::OR_cmdline); + } else { + nout << "Unknown keyword " << words[3] << "\n"; + return false; + } + + getline(infile, line); + line_num++; + return true; +} + +bool AttribFile:: +parse_surprises(const vector &words, istream &infile, + string &line, int &line_num) { + if (words.size() != 1) { + nout << "Unexpected words on line.\n"; + return false; + } + + // This is just the list of surprise textures from last time. Its + // only purpose is to inform the user; we can completely ignore it. + + getline(infile, line); + line = trim_right(line); + line_num++; + while (!infile.eof() && !line.empty() && isspace(line[0])) { + getline(infile, line); + line = trim_right(line); + line_num++; + } + + return true; +} diff --git a/pandatool/src/egg-palettize/attribFile.h b/pandatool/src/egg-palettize/attribFile.h new file mode 100644 index 0000000000..a29226139d --- /dev/null +++ b/pandatool/src/egg-palettize/attribFile.h @@ -0,0 +1,132 @@ +// Filename: attribFile.h +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef ATTRIBFILE_H +#define ATTRIBFILE_H + +#include + +#include + +#include +#include + +class UserAttribLine; +class Texture; +class Palette; +class SourceEgg; +class EggPalettize; + +//////////////////////////////////////////////////////////////////// +// Class : AttribFile +// Description : +//////////////////////////////////////////////////////////////////// +class AttribFile { +public: + AttribFile(const Filename &filename); + + string get_name() const; + + bool grab_lock(); + bool release_lock(); + + bool read(); + bool write(); + + void update_params(EggPalettize *prog); + + void get_req_sizes(); + void update_texture_flags(); + + void repack_all_textures(); + void repack_some_textures(); + void optimal_resize(); + void finalize_palettes(); + void remove_unused_lines(); + bool check_packing(bool force_optimal); + bool pack_texture(Texture *texture); + bool unpack_texture(Texture *texture); + + void touch_dirty_egg_files(bool force_redo_all, + bool eggs_include_images); + + Texture *get_texture(const string &name); + void get_eligible_textures(vector &textures); + SourceEgg *get_egg(Filename name); + + bool generate_palette_images(); + bool transfer_unplaced_images(bool force_redo_all); + + void check_dup_textures(map &textures, + map &dup_textures) const; + + void collect_statistics(int &num_textures, int &num_placed, + int &num_palettes, + int &orig_size, int &resized_size, + int &palette_size, int &unplaced_size) const; + +private: + typedef vector UserLines; + UserLines _user_lines; + + typedef map Eggs; + Eggs _eggs; + + typedef vector Palettes; + Palettes _palettes; + + typedef map Textures; + Textures _textures; + + string get_pi_filename(const string &txa_filename) const; + + bool read_txa(istream &outfile); + bool read_pi(istream &outfile); + bool write_txa(ostream &outfile) const; + bool write_pi(ostream &outfile) const; + + bool parse_params(const vector &words, istream &infile, + string &line, int &line_num); + bool parse_packing(const vector &words, istream &infile, + string &line, int &line_num); + bool parse_texture(const vector &words, istream &infile, + string &line, int &line_num); + bool parse_pathname(const vector &words, istream &infile, + string &line, int &line_num); + bool parse_egg(const vector &words, istream &infile, + string &line, int &line_num); + bool parse_palette(const vector &words, istream &infile, + string &line, int &line_num); + bool parse_unplaced(const vector &words, istream &infile, + string &line, int &line_num); + bool parse_surprises(const vector &words, istream &infile, + string &line, int &line_num); + + bool _optimal; + bool _txa_needs_rewrite; + + string _name; + Filename _txa_filename; + Filename _pi_filename; + + +public: + // These parameter values come from the command line, or from the + // .pi file if omitted from the command line. These are the + // parameter values that specifically refer to textures and + // palettes, and thus should be stored in the .pi file for future + // reference. + Filename _map_dirname; + string _palette_prefix; + int _pal_xsize, _pal_ysize; + int _default_margin; + bool _force_power_2; + bool _aggressively_clean_mapdir; + + int _txa_fd; + fstream _txa_fstrm; +}; + +#endif diff --git a/pandatool/src/egg-palettize/config_egg_palettize.cxx b/pandatool/src/egg-palettize/config_egg_palettize.cxx new file mode 100644 index 0000000000..8466eb85a0 --- /dev/null +++ b/pandatool/src/egg-palettize/config_egg_palettize.cxx @@ -0,0 +1,14 @@ +// Filename: config_egg_palettize.cxx +// Created by: drose (02Nov00) +// +//////////////////////////////////////////////////////////////////// + +#include + +#include "sourceEgg.h" + +Configure(config_egg_palettize); + +ConfigureFn(config_egg_palettize) { + SourceEgg::init_type(); +} diff --git a/pandatool/src/egg-palettize/eggPalettize.cxx b/pandatool/src/egg-palettize/eggPalettize.cxx new file mode 100644 index 0000000000..173031a7db --- /dev/null +++ b/pandatool/src/egg-palettize/eggPalettize.cxx @@ -0,0 +1,592 @@ +// Filename: eggPalettize.cxx +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#include "eggPalettize.h" +#include "attribFile.h" +#include "texture.h" +#include "string_utils.h" +#include "sourceEgg.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Function: EggPalettize::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +EggPalettize:: +EggPalettize() : EggMultiFilter(true) { + set_program_description + ("egg-palettize attempts to pack several texture maps from various models " + "together into one or more palette images, for improved rendering performance " + "and ease of texture management. It can also resize textures on the fly, " + "whether or not they are actually placed on a palette.\n\n" + + "egg-palettize reads and writes an AttributesFile, which contains instructions " + "from the user about resizing particular textures, as well as the complete " + "information necessary to reconstruct the palettization from past runs, " + "including references to other egg files that may share this palette. This " + "is designed to allow multiple egg files to use the same palette, without " + "having to process them all at once.\n\n" + + "Note that it is not even necessary to specify any egg files at all on the " + "command line; egg-palettize can be run on an existing AttributesFiles by " + "itself to freshen up a palette when necessary."); + + + clear_runlines(); + add_runline("[opts] attribfile.txa file.egg [file.egg ...]"); + + add_option + ("s", "", 0, + "Do not process anything, but report statistics on all palette " + "information files read.", + &EggPalettize::dispatch_none, &_statistics_only); + redescribe_option + ("d", + "The directory in which to write the palettized egg files. This is " + "only necessary if more than one egg file is processed at the same " + "time; if it is included, each egg file will be processed and written " + "into the indicated directory."); + add_option + ("dm", "dirname", 0, + "The directory in which to place all maps: generated palettes, " + "as well as images which were not placed on palettes " + "(but may have been resized). It is often best if this is a " + "fully-qualified directory name rather than a relative directory name, " + "particularly if -d is used to write the egg files to a directory " + "different than the current directory, as the same name is written " + "into the egg files.", + &EggPalettize::dispatch_filename, &_got_map_dirname, &_map_dirname); + add_option + ("f", "", 0, + "Force an optimal packing. By default, textures are added to " + "existing palettes without disturbing them, which can lead to " + "suboptimal packing. Including this switch forces the palettes " + "to be rebuilt if necessary to optimize the packing, but this " + "may invalidate other egg files which share this palette.", + &EggPalettize::dispatch_none, &_force_optimal); + add_option + ("F", "", 0, + "Force a redo of everything. This is useful in case something " + "has gotten out of sync and the old palettes are just bad.", + &EggPalettize::dispatch_none, &_force_redo_all); + add_option + ("R", "", 0, + "Resize mostly-empty palettes to their minimal size.", + &EggPalettize::dispatch_none, &_optimal_resize); + add_option + ("t", "", 0, + "Touch any additional egg files that share this palette and will " + "need to be refreshed, but were not included on the command " + "line. Presumably a future make pass will cause them to be run " + "through egg-palettize again.", + &EggPalettize::dispatch_none, &_touch_eggs); + add_option + ("T", "", 0, + "When touching egg files, consider an egg file to be invalidated " + "if textures have changed in any way, rather than simply moving " + "around within their palettes. You should use this switch " + "if the texture images themselves are to be stored within bam files " + "generated from the eggs, or some such nonsense.", + &EggPalettize::dispatch_none, &_eggs_include_images); + add_option + ("C", "", 0, + "Aggressively keep the map directory clean by deleting unused " + "textures from previous passes.", + &EggPalettize::dispatch_none, &_got_aggressively_clean_mapdir); + add_option + ("r", "", 0, + "Respect any repeat/clamp flags given in the egg files. The " + "default is to override a repeat flag if a texture's UV's don't " + "exceed the range [0..1].", + &EggPalettize::dispatch_none, &_dont_override_repeats); + add_option + ("z", "fuzz", 0, + "The fuzz factor when applying the above repeat test. UV's are " + "considered to be in the range [0..1] if they are actually in " + "the range [0-fuzz .. 1+fuzz]. The default is 0.01.", + &EggPalettize::dispatch_double, NULL, &_fuzz_factor); + add_option + ("m", "margin", 0, + "Specify the default margin size.", + &EggPalettize::dispatch_int, &_got_default_margin, &_default_margin); + add_option + ("P", "x,y", 0, + "Specify the default palette size.", + &EggPalettize::dispatch_int_pair, &_got_palette_size, _pal_size); + add_option + ("2", "", 0, + "Force textures that have been left out of the palette to a size " + "which is an even power of 2. They will be scaled down to " + "achieve this.", + &EggPalettize::dispatch_none, &_got_force_power_2); + add_option + ("x", "", 0, + "Don't palettize anything, but do resize textures.", + &EggPalettize::dispatch_none, &_dont_palettize); + add_option + ("k", "", 0, + "Kill lines from the attributes file that aren't used on any " + "texture.", + &EggPalettize::dispatch_none, &_remove_unused_lines); + add_option + ("H", "", 0, + "Describe the syntax of the attributes file.", + &EggPalettize::dispatch_none, &_describe_input_file); + + _fuzz_factor = 0.01; + _aggressively_clean_mapdir = false; + _force_power_2 = false; +} + + +//////////////////////////////////////////////////////////////////// +// Function: EggPalettize::handle_args +// Access: Protected, Virtual +// Description: Does something with the additional arguments on the +// command line (after all the -options have been +// parsed). Returns true if the arguments are good, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool EggPalettize:: +handle_args(ProgramBase::Args &args) { + if (_describe_input_file) { + describe_input_file(); + exit(1); + } + + + Args egg_names; + Args txa_names; + + // First, collect all the filenames. + Args::iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + Filename filename = (*ai); + string ext = filename.get_extension(); + + if (ext == "egg") { + egg_names.push_back(filename); + } else if (ext == "txa" || ext == "pi") { + txa_names.push_back(filename); + } else { + nout << "Don't know what kind of file " << filename << " is.\n"; + return false; + } + } + + // Now sanity check them. Either we have zero egg files, and one or + // more txa files, or we have some egg files and exactly one txa + // file. + if (egg_names.empty()) { + if (txa_names.empty()) { + nout << "No files specified.\n"; + return false; + } + + for (ai = txa_names.begin(); ai != txa_names.end(); ++ai) { + AttribFile *af = new AttribFile(*ai); + _attrib_files.push_back(af); + } + + } else { + if (txa_names.empty()) { + nout << "Must include an attribs.txa file.\n"; + return false; + } + if (txa_names.size() != 1) { + nout << "Can only read one attribs.txa file when processing egg files.\n"; + return false; + } + + AttribFile *af = new AttribFile(txa_names[0]); + _attrib_files.push_back(af); + } + + return EggMultiFilter::handle_args(egg_names); +} + +//////////////////////////////////////////////////////////////////// +// Function: EggPalettize::read_egg +// Access: Protected, Virtual +// Description: Allocates and returns a new EggData structure that +// represents the indicated egg file. If the egg file +// cannot be read for some reason, returns NULL. +// +// This can be overridden by derived classes to control +// how the egg files are read, or to extend the +// information stored with each egg structure, by +// deriving from EggData. +//////////////////////////////////////////////////////////////////// +EggData *EggPalettize:: +read_egg(const Filename &filename) { + // We should only call this function if we have exactly one .txa file. + nassertr(_attrib_files.size() == 1, (EggData *)NULL); + + SourceEgg *egg = _attrib_files[0]->get_egg(filename); + if (egg->read(filename)) { + return egg; + } + + // Failure reading. + delete egg; + return (EggData *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: EggPalettize::describe_input_file +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void EggPalettize:: +describe_input_file() { + nout << + "An attributes file consists mostly of lines describing desired sizes of " + "texture maps. The format resembles, but is not identical to, that of " + "the qtess input file. Examples:\n\n" + + " texturename.rgb : 64 64\n" + " texture-a.rgb texture-b.rgb : 32 16 2\n" + " *.rgb : 50%\n" + " eyelids.rgb : 16 16 omit\n\n" + + "In general, each line consists of one or more filenames (and can " + "contain shell globbing characters like '*' or '?'), and a colon " + "followed by a size request. For each texture appearing in an egg " + "file, the input list is scanned from the beginning and the first " + "line that matches the filename defines the size of the texture.\n\n" + + "A size request may be either a pair of numbers, giving a specific x y " + "size of the texture, or it may be a scale factor in the form of a " + "percentage. It may also include an additional number, giving a margin " + "for this particular texture (otherwise the default margin is " + "applied). Finally, the keyword 'omit' may be included along with the " + "size to specify that the texture should not be placed in a palette.\n\n" + + "There are some other special lines that may appear in this second, " + "along with the resize commands. They begin with a colon to " + "distinguish them from the resize commands. They are:\n\n" + + ":palette xsize ysize\n\n" + + "This specifies the size of the palette file(s) to be created. It " + "overrides the -s command-line option.\n\n" + + ":margin msize\n\n" + + "This specifies the size of the default margin for all subsequent " + "resize commands. This may appear several times in a given file.\n\n" + + "Comments may appear freely throughout the file, and are set off by a " + "hash mark (#).\n"; +} + + +//////////////////////////////////////////////////////////////////// +// Function: EggPalettize::format_space +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +string EggPalettize:: +format_space(int size_pixels, bool verbose) { + int size_bytes = size_pixels * 4; + int size_k = (size_bytes + 512) / 1024; + int mm_size_k = (size_bytes * 4 / 3 + 512) / 1024; + char str[128]; + if (verbose) { + sprintf(str, "%dk, w/mm %dk", size_k, mm_size_k); + } else { + sprintf(str, "%dk, %dk", size_k, mm_size_k); + } + assert(strlen(str) < 128); + + return str; +} + +//////////////////////////////////////////////////////////////////// +// Function: EggPalettize::report_statistics +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void EggPalettize:: +report_statistics() { + // Look for textures in common. + map textures; + map dup_textures; + + AttribFiles::iterator afi; + for (afi = _attrib_files.begin(); afi != _attrib_files.end(); ++afi) { + AttribFile &af = *(*afi); + af.check_dup_textures(textures, dup_textures); + } + + if (dup_textures.empty()) { + cout << "\nEach texture appears in only one palette group.\n"; + } else { + cout << "\nThe following textures appear in more than one palette group:\n"; + int net_wasted_size = 0; + map::const_iterator di; + for (di = dup_textures.begin(); di != dup_textures.end(); ++di) { + cout << " " << (*di).first << " (" + << format_space((*di).second) << ")\n"; + net_wasted_size += (*di).second; + } + cout << "Total wasted memory from common textures is " + << format_space(net_wasted_size, true) << "\n"; + } + + int net_palette_size = 0; + int net_num_palettes = 0; + + for (afi = _attrib_files.begin(); afi != _attrib_files.end(); ++afi) { + AttribFile &af = *(*afi); + int num_textures, num_placed, num_palettes; + int orig_size, resized_size, palette_size, unplaced_size; + af.collect_statistics(num_textures, num_placed, num_palettes, + orig_size, resized_size, + palette_size, unplaced_size); + cout << "\nPalette group " << af.get_name() << ":\n" + << " " << num_textures << " textures, " + << num_placed << " of which are packed onto " + << num_palettes << " palettes (" << num_textures - num_placed + << " unplaced)" + << "\n Original texture memory: " + << format_space(orig_size, true) + << "\n After resizing: " + << format_space(resized_size, true) + << "\n After palettizing: " + << format_space(palette_size + unplaced_size, true) + << "\n"; + + net_palette_size += palette_size; + net_num_palettes += num_palettes; + } + + int net_num_textures = textures.size(); + int net_num_placed = 0; + int net_orig_size = 0; + int net_resized_size = 0; + int net_unplaced_size = 0; + typedef map > UnplacedReasons; + UnplacedReasons unplaced_reasons; + + map::iterator ti; + for (ti = textures.begin(); ti != textures.end(); ++ti) { + Texture *texture = (*ti).second; + + int xsize, ysize; + int rxsize, rysize; + int rsize = 0; + if (texture->get_size(xsize, ysize) && + texture->get_last_req(rxsize, rysize)) { + net_orig_size += xsize * ysize; + net_resized_size += rxsize * rysize; + rsize = rxsize * rysize; + } + + + if (texture->is_really_packed()) { + net_num_placed++; + + } else { + net_unplaced_size += rsize; + + // Here's an unplaced texture; add its size to the unplaced + // reasons table. + UnplacedReasons::iterator uri = + unplaced_reasons.find(texture->get_omit()); + if (uri == unplaced_reasons.end()) { + unplaced_reasons.insert + (UnplacedReasons::value_type(texture->get_omit(), + pair(1, rsize))); + } else { + (*uri).second.first++; + (*uri).second.second += rsize; + } + } + } + + cout << "\nOverall:\n" + << " " << net_num_textures << " textures, " + << net_num_placed << " of which are packed onto " + << net_num_palettes << " palettes (" + << net_num_textures - net_num_placed << " unplaced)" + << "\n Original texture memory: " + << format_space(net_orig_size, true) + << "\n After resizing: " + << format_space(net_resized_size, true) + << "\n After palettizing: " + << format_space(net_palette_size + net_unplaced_size, true) + << "\n\n"; + + UnplacedReasons::iterator uri; + for (uri = unplaced_reasons.begin(); + uri != unplaced_reasons.end(); + ++uri) { + Texture::OmitReason reason = (*uri).first; + int count = (*uri).second.first; + int size = (*uri).second.second; + cout << count << " textures (" << format_space(size) + << ") unplaced because "; + switch (reason) { + case Texture::OR_none: + cout << "of no reason--textures should have been placed\n"; + break; + + case Texture::OR_size: + cout << "size was too large for palette\n"; + break; + + case Texture::OR_repeats: + cout << "repeating\n"; + break; + + case Texture::OR_omitted: + cout << "explicitly omitted\n"; + break; + + case Texture::OR_unused: + cout << "unused by any egg file\n"; + break; + + case Texture::OR_unknown: + cout << "texture file is missing\n"; + break; + + case Texture::OR_cmdline: + cout << "-x was given on command line\n"; + break; + + case Texture::OR_solitary: + cout << "texture was alone on a palette\n"; + break; + + default: + cout << "unknown reason\n"; + } + } + + cout << "\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: EggPalettize::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void EggPalettize:: +run() { + _force_power_2 = _got_force_power_2; + _aggressively_clean_mapdir = _got_aggressively_clean_mapdir; + + bool okflag = true; + + // We'll repeat the processing steps for each attrib file. If we + // have multiple attrib files, then we have no egg files, so all of + // the egg loops below fall out. On the other hand, if we do have + // egg files, then we have only one attrib file, so this outer loop + // falls out. + + AttribFiles::iterator afi; + for (afi = _attrib_files.begin(); afi != _attrib_files.end(); ++afi) { + AttribFile &af = *(*afi); + + if (!af.grab_lock()) { + // Failing to grab the write lock on the attribute file is a + // fatal error. + exit(1); + } + + if (_statistics_only) { + nout << "Reading " << af.get_name() << "\n"; + okflag = af.read(); + + } else { + nout << "Processing " << af.get_name() << "\n"; + + if (!af.read()) { + // Failing to read the attribute file is a fatal error. + exit(1); + } + + af.update_params(this); + + // Get all the texture references out of the egg files. + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + SourceEgg *egg = DCAST(SourceEgg, *ei); + egg->get_textures(af, this); + } + + // Apply the user's requested size changes onto the textures. + af.get_req_sizes(); + af.update_texture_flags(); + + if (!af.check_packing(_force_optimal) || _force_redo_all) { + // We need some more palettization work here. + if (_force_redo_all || _force_optimal) { + af.repack_all_textures(); + } else { + af.repack_some_textures(); + } + + } else { + nout << "No changes to palette arrangement are required.\n"; + } + + af.finalize_palettes(); + + if (_optimal_resize) { + af.optimal_resize(); + } + + if (_remove_unused_lines) { + af.remove_unused_lines(); + } + + if (!af.write()) { + // Failing to rewrite the attribute file is a fatal error. + exit(1); + } + + // And rebuild whatever images are necessary. + okflag = af.generate_palette_images() && okflag; + okflag = af.transfer_unplaced_images(_force_redo_all) && okflag; + + // Now apply the palettization effects to the egg files. + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + SourceEgg *egg = DCAST(SourceEgg, *ei); + egg->update_trefs(); + } + + // Write out the egg files. + write_eggs(); + + if (_touch_eggs) { + af.touch_dirty_egg_files(_force_redo_all, _eggs_include_images); + } + } + + okflag = af.release_lock() && okflag; + } + + if (_statistics_only) { + report_statistics(); + } + + if (!okflag) { + exit(1); + } +} + +int +main(int argc, char *argv[]) { + EggPalettize prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/egg-palettize/eggPalettize.h b/pandatool/src/egg-palettize/eggPalettize.h new file mode 100644 index 0000000000..8f38ccefc3 --- /dev/null +++ b/pandatool/src/egg-palettize/eggPalettize.h @@ -0,0 +1,71 @@ +// Filename: eggPalettize.h +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGPALETTIZE_H +#define EGGPALETTIZE_H + +#include + +#include "attribFile.h" +#include + +#include + +class Texture; + +//////////////////////////////////////////////////////////////////// +// Class : EggPalettize +// Description : This is the heart of the program. +//////////////////////////////////////////////////////////////////// +class EggPalettize : public EggMultiFilter { +public: + EggPalettize(); + + virtual bool handle_args(Args &args); + virtual EggData *read_egg(const Filename &filename); + + void describe_input_file(); + + string format_space(int size_pixels, bool verbose = false); + void report_statistics(); + void run(); + + typedef vector AttribFiles; + AttribFiles _attrib_files; + + // The following parameter values specifically relate to textures + // and palettes. These values are store in the .pi file for future + // reference. + Filename _map_dirname; + bool _got_map_dirname; + int _pal_size[2]; + bool _got_palette_size; + int _default_margin; + bool _got_default_margin; + bool _force_power_2; + bool _got_force_power_2; + bool _aggressively_clean_mapdir; + bool _got_aggressively_clean_mapdir; + + // The following values relate specifically to egg files. They're + // not saved for future sessions. + double _fuzz_factor; + bool _dont_override_repeats; + + // The following values control behavior specific to this session. + // They're not saved either. + bool _statistics_only; + bool _force_optimal; + bool _force_redo_all; + bool _optimal_resize; + bool _touch_eggs; + bool _eggs_include_images; + bool _dont_palettize; + bool _remove_unused_lines; + + bool _describe_input_file; +}; + +#endif diff --git a/pandatool/src/egg-palettize/imageFile.cxx b/pandatool/src/egg-palettize/imageFile.cxx new file mode 100644 index 0000000000..a59953d7d9 --- /dev/null +++ b/pandatool/src/egg-palettize/imageFile.cxx @@ -0,0 +1,14 @@ +// Filename: imageFile.cxx +// Created by: drose (07Sep99) +// +//////////////////////////////////////////////////////////////////// + +#include "imageFile.h" + +ImageFile:: +ImageFile() { +} + +ImageFile:: +~ImageFile() { +} diff --git a/pandatool/src/egg-palettize/imageFile.h b/pandatool/src/egg-palettize/imageFile.h new file mode 100644 index 0000000000..e65d1ac415 --- /dev/null +++ b/pandatool/src/egg-palettize/imageFile.h @@ -0,0 +1,28 @@ +// Filename: imageFile.h +// Created by: drose (07Sep99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef IMAGEFILE_H +#define IMAGEFILE_H + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Class : ImageFile +// Description : This is the base class for both Palette and Texture. +//////////////////////////////////////////////////////////////////// +class ImageFile { +public: + ImageFile(); + virtual ~ImageFile(); + + virtual Filename get_filename() const=0; + virtual Filename get_basename() const=0; + +}; + +#endif + diff --git a/pandatool/src/egg-palettize/palette.cxx b/pandatool/src/egg-palettize/palette.cxx new file mode 100644 index 0000000000..7bde306af3 --- /dev/null +++ b/pandatool/src/egg-palettize/palette.cxx @@ -0,0 +1,544 @@ +// Filename: palette.cxx +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#include "palette.h" +#include "texture.h" +#include "attribFile.h" +#include "string_utils.h" + +#include +#include + +bool Palette::TexturePlacement:: +intersects(int hleft, int htop, int xsize, int ysize) const { + int hright = hleft + xsize; + int hbot = htop + ysize; + + int mright = _left + _xsize; + int mbot = _top + _ysize; + + return + !(hleft >= mright || hright <= _left || + htop >= mbot || hbot <= _top); +} + +PNMImage *Palette::TexturePlacement:: +resize_image(PNMImage *source) const { + // Compute need_*size to account for the (interior) margins. + // However, if the size of the texture if very small, we want to + // make exterior margins, so we won't reduce the size past a certain + // point. + int need_xsize = min(_xsize, max(_xsize - 2 * _margin, 8)); + int need_ysize = min(_ysize, max(_ysize - 2 * _margin, 8)); + + if (source->get_x_size() == need_xsize && source->get_y_size() == need_ysize) { + // The image is already the right size. + return source; + } + + PNMImage *new_image = + new PNMImage(need_xsize, need_ysize, source->get_color_type()); + new_image->gaussian_filter_from(0.5, *source); + delete source; + return new_image; +} + +PNMImage *Palette::TexturePlacement:: +add_margins(PNMImage *source) const { + if (_margin == 0) { + // No margins to add. + return source; + } + + int orig_xsize = source->get_x_size(); + int orig_ysize = source->get_y_size(); + + int need_xsize = orig_xsize + _margin * 2; + int need_ysize = orig_ysize + _margin * 2; + + PNMImage *new_image = + new PNMImage(need_xsize, need_ysize, source->get_color_type()); + new_image->copy_sub_image(*source, _margin, _margin); + + // Now extend the margin out to the sides. + for (int i = 0; i < _margin; i++) { + for (int x = 0; x < orig_xsize; x++) { + // top edge + new_image->set_xel(x + _margin, i, source->get_xel(x, 0)); + // bottom edge + new_image->set_xel(x + _margin, need_ysize - 1 - i, + source->get_xel(x, orig_ysize - 1)); + } + for (int y = 0; y < orig_ysize; y++) { + // left edge + new_image->set_xel(i, y + _margin, source->get_xel(0, y)); + // right edge + new_image->set_xel(need_xsize - 1 - i, y + _margin, + source->get_xel(orig_xsize - 1, y)); + } + + for (int j = 0; j < _margin; j++) { + // top-left corner + new_image->set_xel(i, j, source->get_xel(0, 0)); + // top-right corner + new_image->set_xel(need_xsize - 1 - i, j, + source->get_xel(orig_xsize - 1, 0)); + // bottom-left corner + new_image->set_xel(i, need_ysize - 1 - j, + source->get_xel(0, orig_ysize - 1)); + // bottom-right corner + new_image->set_xel(need_xsize - 1 - i, need_ysize - 1 - j, + source->get_xel(orig_xsize - 1, orig_ysize - 1)); + } + } + + if (source->has_alpha()) { + // Now do the same thing in the alpha channel. + for (int i = 0; i < _margin; i++) { + for (int x = 0; x < orig_xsize; x++) { + // top edge + new_image->set_alpha(x + _margin, i, source->get_alpha(x, 0)); + // bottom edge + new_image->set_alpha(x + _margin, need_ysize - 1 - i, + source->get_alpha(x, orig_ysize - 1)); + } + for (int y = 0; y < orig_ysize; y++) { + // left edge + new_image->set_alpha(i, y + _margin, source->get_alpha(0, y)); + // right edge + new_image->set_alpha(need_xsize - 1 - i, y + _margin, + source->get_alpha(orig_xsize - 1, y)); + } + + for (int j = 0; j < _margin; j++) { + // top-left corner + new_image->set_alpha(i, j, source->get_alpha(0, 0)); + // top-right corner + new_image->set_alpha(need_xsize - 1 - i, j, + source->get_alpha(orig_xsize - 1, 0)); + // bottom-left corner + new_image->set_alpha(i, need_ysize - 1 - j, + source->get_alpha(0, orig_ysize - 1)); + // bottom-right corner + new_image->set_alpha(need_xsize - 1 - i, need_ysize - 1 - j, + source->get_alpha(orig_xsize - 1, orig_ysize - 1)); + } + } + } + + delete source; + return new_image; +} + + + +Palette:: +Palette(const Filename &filename, int xsize, int ysize, int components, + AttribFile *af) : + _filename(filename), + _xsize(xsize), + _ysize(ysize), + _components(components), + _attrib_file(af) +{ + _index = -1; + _palette_changed = false; + _new_palette = false; +} + +Palette:: +Palette(int index, int xsize, int ysize, int components, AttribFile *af) : + _index(index), + _xsize(xsize), + _ysize(ysize), + _components(components), + _attrib_file(af) +{ + _palette_changed = false; + _new_palette = true; +} + +Palette:: +~Palette() { + // Unmark any textures we've had packed. + TexPlace::iterator ti; + for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { + (*ti)._texture->mark_unpacked(); + } +} + +Filename Palette:: +get_filename() const { + return _filename; +} + +Filename Palette:: +get_basename() const { + return _basename; +} + +bool Palette:: +changed() const { + return _palette_changed; +} + +bool Palette:: +new_palette() const { + return _new_palette; +} + +int Palette:: +get_num_textures() const { + return _texplace.size(); +} + +bool Palette:: +check_uses_alpha() const { + // Returns true if any texture in the palette uses alpha. + TexPlace::const_iterator ti; + for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { + if ((*ti)._texture->uses_alpha()) { + return true; + } + } + + return false; +} + +void Palette:: +get_size(int &xsize, int &ysize) const { + xsize = _xsize; + ysize = _ysize; +} + + +void Palette:: +place_texture_at(Texture *texture, int left, int top, + int xsize, int ysize, int margin) { + TexturePlacement tp; + tp._texture = texture; + tp._left = left; + tp._top = top; + tp._xsize = xsize; + tp._ysize = ysize; + tp._margin = margin; + _texplace.push_back(tp); + + texture->mark_pack_location(this, left, top, xsize, ysize, margin); +} + +bool Palette:: +pack_texture(Texture *texture) { + int xsize, ysize; + if (!texture->get_req(xsize, ysize)) { + return false; + } + + int left, top; + if (find_home(left, top, xsize, ysize)) { + place_texture_at(texture, left, top, xsize, ysize, texture->get_margin()); + _palette_changed = true; + return true; + } + return false; +} + +bool Palette:: +unpack_texture(Texture *texture) { + TexPlace::iterator ti; + for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { + if ((*ti)._texture == texture) { + _texplace.erase(ti); + texture->mark_unpacked(); + return true; + } + } + return false; +} + +// Attempt to shrink the palette down to the smallest possible (power +// of 2) size that includes all its textures. +void Palette:: +optimal_resize() { + if (_texplace.size() < 2) { + // If we don't have at least two textures, there's no point in + // shrinking the palette because we won't be using it anyway. + // Might as well keep it full-sized so we can better pack future + // textures. + return; + } + + int max_y = get_max_y(); + assert(max_y > 0); + bool resized = false; + + while (max_y <= _ysize / 2) { + // If at least half the palette is empty, we can try resizing. + if (max_y <= _ysize / 4) { + // Wow, three-quarters of the palette is empty. + Palette *np = try_resize(_xsize / 2, _ysize / 2); + if (np != NULL) { + // Excellent! We just reduced the palette size by half in + // both dimensions. + _texplace = np->_texplace; + _xsize = _xsize / 2; + _ysize = _ysize / 2; + _palette_changed = true; + delete np; + } else { + // Well, we couldn't reduce in both dimensions, but we can + // always reduce twice in the y dimension. + _ysize = _ysize / 4; + } + } else { + _ysize = _ysize / 2; + } + resized = true; + max_y = get_max_y(); + } + + assert(get_max_y() <= _ysize); + + if (resized) { + nout << "Resizing " << get_basename() << " to " << _xsize << " " + << _ysize << "\n"; + // If we've resized the palette, all the egg files that reference + // these textures will need to be rebuilt. + TexPlace::iterator ti; + for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { + (*ti)._texture->set_changed(true); + } + + // And we'll have to mark the palette as a new image. + _new_palette = true; + } +} + + +void Palette:: +finalize_palette() { + if (_filename.empty()) { + // Generate a filename based on the index number. + char index_str[128]; + sprintf(index_str, "%03d", _index); + _basename = _attrib_file->_palette_prefix + index_str + ".rgb"; + _filename = _basename; + _filename.set_dirname(_attrib_file->_map_dirname); + } else { + _basename = _filename.get_basename(); + } + + _components = check_uses_alpha() ? 4 : 3; + + if (_texplace.size() == 1) { + // If we packed exactly one texture, never mind. + Texture *texture = (*_texplace.begin())._texture; + + // This is a little odd: we mark the texture as being omitted, but + // we don't actually unpack it. That way it will still be + // recorded as belonging to this palette (for future + // palettizations), but it will also be copied to the map + // directory, and any egg files that reference it will use the + // texture and not the palette. + texture->set_omit(Texture::OR_solitary); + } +} + +void Palette:: +write(ostream &out) const { + out << "palette " << _filename + << " size " << _xsize << " " << _ysize << " " << _components + << "\n"; + TexPlace::const_iterator ti; + for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { + out << " " << (*ti)._texture->get_name() + << " at " << (*ti)._left << " " << (*ti)._top + << " size " << (*ti)._xsize << " " << (*ti)._ysize + << " margin " << (*ti)._margin + << "\n"; + } +} + +bool Palette:: +generate_image() { + bool okflag = true; + + PNMImage palette(_xsize, _ysize, _components); + + nout << "Generating " << _filename << "\n"; + + TexPlace::const_iterator ti; + for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { + Texture *texture = (*ti)._texture; + nout << " " << texture->get_name() << "\n"; + okflag = copy_texture_image(palette, *ti) && okflag; + } + + nout << "Writing " << _filename << "\n"; + if (!palette.write(_filename)) { + nout << "Error in writing.\n"; + okflag = false; + } + + return okflag; +} + +bool Palette:: +refresh_image() { + if (!_filename.exists()) { + nout << "Palette image " << _filename << " does not exist, rebuilding.\n"; + return generate_image(); + } + + bool okflag = true; + + PNMImage palette; + if (!palette.read(_filename)) { + nout << "Unable to read old palette image " << _filename + << ", rebuilding.\n"; + return generate_image(); + } + + bool any_changed = false; + + if (_components == 4 && !palette.has_alpha()) { + palette.add_alpha(); + palette.alpha_fill(1.0); + any_changed = true; + + } else if (_components == 3 && palette.has_alpha()) { + palette.remove_alpha(); + any_changed = true; + } + + TexPlace::const_iterator ti; + for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { + Texture *texture = (*ti)._texture; + if (texture->needs_refresh()) { + if (!any_changed) { + nout << "Refreshing " << _filename << "\n"; + any_changed = true; + } + + nout << " " << texture->get_name() << "\n"; + okflag = copy_texture_image(palette, *ti) && okflag; + } + } + + if (any_changed) { + nout << "Writing " << _filename << "\n"; + if (!palette.write(_filename)) { + nout << "Error in writing.\n"; + okflag = false; + } + } + + return okflag; +} + + +Palette *Palette:: +try_resize(int new_xsize, int new_ysize) const { + Palette *np = + new Palette(_index, new_xsize, new_ysize, + _components, _attrib_file); + + bool okflag = true; + TexPlace::const_iterator ti; + for (ti = _texplace.begin(); + ti != _texplace.end() && okflag; + ++ti) { + okflag = np->pack_texture((*ti)._texture); + } + + if (okflag) { + return np; + } else { + delete np; + return NULL; + } +} + +int Palette:: +get_max_y() const { + int max_y = 0; + + TexPlace::const_iterator ti; + for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { + max_y = max(max_y, (*ti)._top + (*ti)._ysize); + } + + return max_y; +} + +bool Palette:: +find_home(int &left, int &top, int xsize, int ysize) const { + top = 0; + while (top + ysize <= _ysize) { + int next_y = _ysize; + // Scan along the row at 'top'. + left = 0; + while (left + xsize <= _xsize) { + int next_x = left; + // Consider the spot at left, top. + + // Can we place it here? + bool place_ok = true; + TexPlace::const_iterator ti; + for (ti = _texplace.begin(); + ti != _texplace.end() && place_ok; + ++ti) { + if ((*ti).intersects(left, top, xsize, ysize)) { + // Nope. + place_ok = false; + next_x = (*ti)._left + (*ti)._xsize; + next_y = min(next_y, (*ti)._top + (*ti)._ysize); + } + } + + if (place_ok) { + // Hooray! + return true; + } + + assert(next_x > left); + left = next_x; + } + + assert(next_y > top); + top = next_y; + } + + // Nope, wouldn't fit anywhere. + return false; +} + +bool Palette:: +copy_texture_image(PNMImage &palette, const TexturePlacement &tp) { + bool okflag = true; + PNMImage *image = tp._texture->read_image(); + if (image == NULL) { + nout << " *** Unable to read " << tp._texture->get_name() << "\n"; + okflag = false; + + // Create a solid red texture for images we can't read. + image = new PNMImage(tp._xsize, tp._ysize); + image->fill(1.0, 0.0, 0.0); + + } else { + image = tp.add_margins(tp.resize_image(image)); + } + + if (_components == 4 && !image->has_alpha()) { + // We need to add an alpha channel for the image if the + // palette has an alpha channel. + image->add_alpha(); + image->alpha_fill(1.0); + } + palette.copy_sub_image(*image, tp._left, tp._top); + delete image; + + return okflag; +} diff --git a/pandatool/src/egg-palettize/palette.h b/pandatool/src/egg-palettize/palette.h new file mode 100644 index 0000000000..266393de24 --- /dev/null +++ b/pandatool/src/egg-palettize/palette.h @@ -0,0 +1,90 @@ +// Filename: palette.h +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PALETTE_H +#define PALETTE_H + +#include + +#include "imageFile.h" + +#include + +#include + +class Texture; +class PNMImage; +class AttribFile; + +//////////////////////////////////////////////////////////////////// +// Class : Palette +// Description : +//////////////////////////////////////////////////////////////////// +class Palette : public ImageFile { +public: + Palette(const Filename &filename, int xsize, int ysize, int components, + AttribFile *af); + Palette(int index, int xsize, int ysize, int components, + AttribFile *af); + ~Palette(); + + virtual Filename get_filename() const; + virtual Filename get_basename() const; + + bool changed() const; + bool new_palette() const; + int get_num_textures() const; + + bool check_uses_alpha() const; + + void get_size(int &xsize, int &ysize) const; + + void place_texture_at(Texture *texture, int left, int top, + int xsize, int ysize, int margin); + + bool pack_texture(Texture *texture); + bool unpack_texture(Texture *texture); + + void optimal_resize(); + + void finalize_palette(); + + void write(ostream &out) const; + + bool generate_image(); + bool refresh_image(); + +private: + class TexturePlacement { + public: + bool intersects(int left, int top, int xsize, int ysize) const; + PNMImage *resize_image(PNMImage *source) const; + PNMImage *add_margins(PNMImage *source) const; + + Texture *_texture; + int _left, _top; + int _xsize, _ysize, _margin; + }; + + Palette *try_resize(int new_xsize, int new_ysize) const; + int get_max_y() const; + bool find_home(int &left, int &top, int xsize, int ysize) const; + bool copy_texture_image(PNMImage &palette, const TexturePlacement &tp); + + + typedef vector TexPlace; + TexPlace _texplace; + + Filename _filename; + Filename _basename; + int _index; + int _xsize, _ysize, _components; + bool _palette_changed; + bool _new_palette; + + AttribFile *_attrib_file; +}; + +#endif diff --git a/pandatool/src/egg-palettize/sourceEgg.cxx b/pandatool/src/egg-palettize/sourceEgg.cxx new file mode 100644 index 0000000000..3860479749 --- /dev/null +++ b/pandatool/src/egg-palettize/sourceEgg.cxx @@ -0,0 +1,347 @@ +// Filename: sourceEgg.cxx +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#include "sourceEgg.h" +#include "texture.h" +#include "eggPalettize.h" +#include "string_utils.h" +#include "palette.h" + +#include +#include +#include +#include +#include + +TypeHandle SourceEgg::_type_handle; + +SourceEgg::TextureRef:: +TextureRef(Texture *texture, bool repeats, bool alpha) : + _texture(texture), + _repeats(repeats), + _alpha(alpha) +{ + _eggtex = NULL; +} + +SourceEgg:: +SourceEgg() { +} + +SourceEgg::TextureRef &SourceEgg:: +add_texture(Texture *texture, bool repeats, bool alpha) { + _texrefs.push_back(TextureRef(texture, repeats, alpha)); + return _texrefs.back(); +} + +void SourceEgg:: +get_textures(AttribFile &af, EggPalettize *prog) { + _texrefs.clear(); + + EggTextureCollection tc; + tc.find_used_textures(this); + + EggTextureCollection::iterator ti; + for (ti = tc.begin(); ti != tc.end(); ++ti) { + EggTexture *eggtex = (*ti); + string name = eggtex->get_basename(); + + Texture *texture = af.get_texture(name); + texture->add_filename(*eggtex); + + if (prog->_dont_palettize) { + // If the user specified -x, it means to omit all textures + // processed in this run, forever. + texture->set_omit(Texture::OR_cmdline); + } else { + // Or until we next see it without -x. + if (texture->get_omit() == Texture::OR_cmdline) { + texture->set_omit(Texture::OR_none); + } + } + + bool repeats = + eggtex->get_wrap_mode() == EggTexture::WM_repeat || + eggtex->get_wrap_u() == EggTexture::WM_repeat || + eggtex->get_wrap_v() == EggTexture::WM_repeat; + + bool repeat_unspecified = + eggtex->get_wrap_mode() == EggTexture::WM_unspecified && + eggtex->get_wrap_u() == EggTexture::WM_unspecified && + eggtex->get_wrap_v() == EggTexture::WM_unspecified; + + bool alpha = true; //eggtex->uses_alpha(); + + // Check the range of UV's actually used within the egg file. + bool any_uvs = false; + TexCoordd min_uv, max_uv; + get_uv_range(this, eggtex, any_uvs, min_uv, max_uv); + + // Now we need to apply the texture matrix, if there is one, to + // our bounding box UV's. + + if (eggtex->has_transform()) { + // Transforming a bounding box by a matrix requires transforming + // all four corners. + + TexCoordd a(min_uv[0], min_uv[1]); + TexCoordd b(min_uv[0], max_uv[1]); + TexCoordd c(max_uv[0], max_uv[1]); + TexCoordd d(max_uv[0], min_uv[1]); + + LMatrix3d transform = eggtex->get_transform(); + + a = a * transform; + b = b * transform; + c = c * transform; + d = d * transform; + + // Now boil down these four corners into a new bounding box. + + min_uv.set(min(min(a[0], b[0]), min(c[0], d[0])), + min(min(a[1], b[1]), min(c[1], d[1]))); + max_uv.set(max(max(a[0], b[0]), max(c[0], d[0])), + max(max(a[1], b[1]), max(c[1], d[1]))); + } + + bool truly_repeats = + (max_uv[0] > 1.0 + prog->_fuzz_factor || + min_uv[0] < 0.0 - prog->_fuzz_factor || + max_uv[1] > 1.0 + prog->_fuzz_factor || + min_uv[1] < 0.0 - prog->_fuzz_factor); + + if (repeat_unspecified) { + // If the egg file didn't give us any advice regarding + // repeating, we can go entirely based on what we saw in the + // UV's. + repeats = truly_repeats; + + } else { + if (repeats && !truly_repeats) { + // The egg file specified that the texture repeats, but we + // discovered that it doesn't really. Quietly mark it so. + if (!prog->_dont_override_repeats) { + repeats = false; + } + + } else if (!repeats && truly_repeats) { + // The egg file specified that the texture doesn't repeat, but + // its UV's were outside the normal range! That's almost + // certainly a modeling error. + + // We won't trouble the user with this kind of spammy message, + // though. + /* + nout << "Warning: texture " << texture->get_name() << " (tref " + << eggtex->name << ") marked clamped, but UV's range from (" + << min_uv << ") to (" << max_uv << ")\n"; + */ + + } else if (repeats && truly_repeats) { + // Well, it really does repeat. Or does it? + if (fabs(max_uv[0] - min_uv[0]) <= 1.0 + prog->_fuzz_factor && + fabs(max_uv[1] - min_uv[1]) <= 1.0 + prog->_fuzz_factor) { + // It really shouldn't! The UV's fit totally within a unit + // square, just not the square (0,0) - (1,1). This is a + // modeling problem; inform the user. + nout << "Warning: texture " << texture->get_name() + << " cannot be clamped because UV's range from (" + << min_uv << ") to (" << max_uv << ")\n"; + } + } + } + + TextureRef &texref = add_texture(texture, repeats, alpha); + texref._eggtex = eggtex; + } +} + +// Updates each Texture with the flags stored in the various egg +// files. Also marks textures as used. +void SourceEgg:: +mark_texture_flags() { + TexRefs::iterator ti; + for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { + Texture *texture = (*ti)._texture; + texture->set_unused(false); + if ((*ti)._alpha) { + texture->set_uses_alpha(true); + } + if ((*ti)._repeats) { + texture->set_omit(Texture::OR_repeats); + } + } +} + +// Updates the egg file to point to the new palettes. +void SourceEgg:: +update_trefs() { + TexRefs::iterator ti; + for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { + Texture *texture = (*ti)._texture; + EggTexture *eggtex = (*ti)._eggtex; + + if (eggtex != NULL) { + // Make the alpha mode explicit if it isn't already. + + /* + if (eggtex->get_alpha_mode == EggTexture::AM_unspecified) { + eggtex->set_alpha = eggtex->UsesAlpha() ? + EggTexture::AM_on : EggTexture::AM_off; + } + */ + + if (!texture->is_packed() || + texture->get_omit() != Texture::OR_none) { + // This texture wasn't palettized, so just rename the + // reference to the new one. + eggtex->set_fullpath(texture->get_filename()); + + } else { + // This texture was palettized, so redirect the tref to point + // within the palette. + Palette *palette = texture->get_palette(); + + eggtex->set_fullpath(palette->get_filename()); + + // Set the texture attributes to be uniform across all palettes. + eggtex->set_minfilter(EggTexture::FT_mipmap_trilinear); + eggtex->set_magfilter(EggTexture::FT_bilinear); + eggtex->set_format(EggTexture::F_rgba8); + eggtex->set_wrap_mode(EggTexture::WM_clamp); + eggtex->set_wrap_u(EggTexture::WM_clamp); + eggtex->set_wrap_v(EggTexture::WM_clamp); + + // Build a matrix that will scale the UV's to their new place + // on the palette. + int left, top, xsize, ysize, margin; + texture->get_packed_location(left, top); + texture->get_packed_size(xsize, ysize, margin); + + // Shrink the box to be within the margins. + top += margin; + left += margin; + xsize -= margin*2; + ysize -= margin*2; + + // Now determine the relative size and position within the + // palette. + int bottom = top + ysize; + int palx, paly; + palette->get_size(palx, paly); + LVecBase2d t((double)left / (double)palx, + (double)(paly - bottom) / (double)paly); + + LVecBase2d s((double)xsize / (double)palx, + (double)ysize / (double)paly); + + LMatrix3d texmat + (s[0], 0.0, 0.0, + 0.0, s[1], 0.0, + t[0], t[1], 1.0); + + // Do we already have a texture matrix? If so, compose them. + if (eggtex->has_transform()) { + eggtex->set_transform(eggtex->get_transform() * texmat); + } else { + // Otherwise, just store it. + eggtex->set_transform(texmat); + } + } + } + } +} + +// Returns true if any of the textures referenced by the egg file have +// been adjusted this pass, implying that the egg file will have to be +// re-run through egg-palettize, and/or re-pfb'ed. +bool SourceEgg:: +needs_rebuild(bool force_redo_all, + bool eggs_include_images) const { + // if (!_wrote_egg) { + TexRefs::const_iterator ti; + for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { + bool dirty = + eggs_include_images ? + (*ti)._texture->needs_refresh() : + (*ti)._texture->packing_changed(); + if (force_redo_all || dirty) { + return true; + } + } + // } + + return false; +} + +void SourceEgg:: +write_pi(ostream &out) const { + out << "egg " << get_egg_filename() << "\n"; + TexRefs::const_iterator ti; + for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { + out << " " << (*ti)._texture->get_name(); + if ((*ti)._repeats) { + out << " repeats"; + } + if ((*ti)._alpha) { + out << " alpha"; + } + out << "\n"; + } +} + +void SourceEgg:: +get_uv_range(EggGroupNode *group, EggTexture *tref, + bool &any_uvs, TexCoordd &min_uv, TexCoordd &max_uv) { + EggGroupNode::iterator ci; + + for (ci = group->begin(); ci != group->end(); ci++) { + EggNode *child = (*ci); + if (child->is_of_type(EggNurbsSurface::get_class_type())) { + EggNurbsSurface *nurbs = DCAST(EggNurbsSurface, child); + if (nurbs->has_texture() && nurbs->get_texture() == tref) { + // Here's a NURBS surface that references the texture. Unlike + // other kinds of geometries, NURBS don't store UV's; they're + // implicit in the surface. NURBS UV's will always run in the + // range (0,0) - (1,1). + if (any_uvs) { + min_uv.set(min(min_uv[0], 0.0), min(min_uv[1], 0.0)); + max_uv.set(max(max_uv[0], 1.0), max(max_uv[1], 1.0)); + } else { + min_uv.set(0.0, 0.0); + max_uv.set(1.0, 1.0); + any_uvs = true; + } + } + + } else if (child->is_of_type(EggPrimitive::get_class_type())) { + EggPrimitive *geom = DCAST(EggPrimitive, child); + if (geom->has_texture() && geom->get_texture() == tref) { + // Here's a piece of geometry that references this texture. + // Walk through its vertices at get its UV's. + EggPrimitive::iterator pi; + for (pi = geom->begin(); pi != geom->end(); ++pi) { + EggVertex *vtx = (*pi); + if (vtx->has_uv()) { + const TexCoordd &uv = vtx->get_uv(); + if (any_uvs) { + min_uv.set(min(min_uv[0], uv[0]), min(min_uv[1], uv[1])); + max_uv.set(max(max_uv[0], uv[0]), max(max_uv[1], uv[1])); + } else { + // The first UV. + min_uv = uv; + max_uv = uv; + any_uvs = true; + } + } + } + } + + } else if (child->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *cg = DCAST(EggGroupNode, child); + get_uv_range(cg, tref, any_uvs, min_uv, max_uv); + } + } +} diff --git a/pandatool/src/egg-palettize/sourceEgg.h b/pandatool/src/egg-palettize/sourceEgg.h new file mode 100644 index 0000000000..6247c3922e --- /dev/null +++ b/pandatool/src/egg-palettize/sourceEgg.h @@ -0,0 +1,75 @@ +// Filename: sourceEgg.h +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef SOURCEGG_H +#define SOURCEGG_H + +#include + +#include +#include + + +class Texture; +class AttribFile; +class EggPalettize; +class EggTexture; +class EggGroup; + +class SourceEgg : public EggData { +public: + class TextureRef; + + SourceEgg(); + + TextureRef &add_texture(Texture *texture, bool repeats, bool alpha); + void get_textures(AttribFile &af, EggPalettize *prog); + + void mark_texture_flags(); + void update_trefs(); + + bool needs_rebuild(bool force_redo_all, + bool eggs_include_images) const; + + void write_pi(ostream &out) const; + + + class TextureRef { + public: + TextureRef(Texture *texture, bool repeats, bool alpha); + + Texture *_texture; + bool _repeats; + bool _alpha; + + EggTexture *_eggtex; + }; + +private: + void get_uv_range(EggGroupNode *group, EggTexture *tref, + bool &any_uvs, TexCoordd &min_uv, TexCoordd &max_uv); + + typedef vector TexRefs; + TexRefs _texrefs; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggData::init_type(); + register_type(_type_handle, "SourceEgg", + EggData::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/egg-palettize/string_utils.cxx b/pandatool/src/egg-palettize/string_utils.cxx new file mode 100644 index 0000000000..ffa92a4cdd --- /dev/null +++ b/pandatool/src/egg-palettize/string_utils.cxx @@ -0,0 +1,80 @@ +// Filename: string_utils.cxx +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#include "string_utils.h" + + +string +trim_left(const string &str) { + size_t begin = 0; + while (begin < str.size() && isspace(str[begin])) { + begin++; + } + + return str.substr(begin); +} + +string +trim_right(const string &str) { + size_t begin = 0; + size_t end = str.size(); + while (end > begin && isspace(str[end - 1])) { + end--; + } + + return str.substr(begin, end - begin); +} + +vector +extract_words(const string &str) { + vector result; + + size_t pos = 0; + while (pos < str.length() && isspace(str[pos])) { + pos++; + } + while (pos < str.length()) { + size_t word_start = pos; + while (pos < str.length() && !isspace(str[pos])) { + pos++; + } + result.push_back(str.substr(word_start, pos - word_start)); + + while (pos < str.length() && isspace(str[pos])) { + pos++; + } + } + + return result; +} + +// Extracts the first word of the string into param, and the remainder +// of the line into value. +void +extract_param_value(const string &str, string ¶m, string &value) { + size_t i = 0; + + // First, skip all whitespace at the beginning. + while (i < str.length() && isspace(str[i])) { + i++; + } + + size_t start = i; + + // Now skip to the end of the whitespace. + while (i < str.length() && !isspace(str[i])) { + i++; + } + + size_t end = i; + + param = str.substr(start, end - start); + + // Skip a little bit further to the start of the value. + while (i < str.length() && isspace(str[i])) { + i++; + } + value = trim_right(str.substr(i)); +} diff --git a/pandatool/src/egg-palettize/string_utils.h b/pandatool/src/egg-palettize/string_utils.h new file mode 100644 index 0000000000..0e3dd35c8c --- /dev/null +++ b/pandatool/src/egg-palettize/string_utils.h @@ -0,0 +1,20 @@ +// Filename: string_utils.h +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STRING_UTILS_H +#define STRING_UTILS_H + +#include + +#include + +string trim_left(const string &str); +string trim_right(const string &str); + +vector extract_words(const string &str); +void extract_param_value(const string &str, string ¶m, string &value); + +#endif + diff --git a/pandatool/src/egg-palettize/texture.cxx b/pandatool/src/egg-palettize/texture.cxx new file mode 100644 index 0000000000..8552325092 --- /dev/null +++ b/pandatool/src/egg-palettize/texture.cxx @@ -0,0 +1,586 @@ +// Filename: texture.cxx +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#include "texture.h" +#include "palette.h" +#include "attribFile.h" + +#include +#include + + +Texture:: +Texture(AttribFile *af, const Filename &name) : + _name(name), + _attrib_file(af) +{ + _got_filename = false; + _file_exists = false; + _texture_changed = false; + _unused = true; + _matched_anything = false; + _got_size = false; + _got_req = false; + _got_last_req = false; + _margin = _attrib_file->_default_margin; + _read_header = false; + _omit = OR_none; + _uses_alpha = false; + _is_packed = false; + _palette = NULL; +} + +Filename Texture:: +get_name() const { + return _name; +} + +void Texture:: +add_filename(const Filename &filename) { + if (!filename.exists()) { + // Store the filename, even though it doesn't exist. + if (!_got_filename) { + _got_filename = true; + _filename = filename; + _file_exists = false; + } + + } else { + bool inserted = _filenames.insert(filename).second; + + if (!_got_filename || !_file_exists) { + // This was the first filename we encountered; no sweat. + _got_filename = true; + _file_exists = true; + _filename = filename; + + check_size(); + + } else if (inserted) { + // We had been using a different filename previously. Now we've + // got a new one. Maybe this one is better? + + // First, read the headers. We'll consider the larger image to + // be the better choice. + if (!_got_size) { + read_header(); + } + + int oxsize, oysize; + bool got_other_size = + read_image_header(filename, oxsize, oysize); + + if (got_other_size) { + if (!_got_size || oxsize * oysize > _xsize * _ysize) { + _filename = filename; + _xsize = oxsize; + _ysize = oysize; + _got_size = true; + _texture_changed = true; + + } else if (oxsize * oysize == _xsize * _ysize) { + // If the sizes are the same, we'll consider the newer image + // to be the better choice. + if (filename.compare_timestamps(_filename, false, false) > 0) { + _filename = filename; + _texture_changed = true; + } + } + } + } + } +} + +Filename Texture:: +get_filename() const { + Filename filename = _name; + filename.set_dirname(_attrib_file->_map_dirname); + return filename; +} + +Filename Texture:: +get_basename() const { + return _name; +} + +bool Texture:: +get_size(int &xsize, int &ysize) { + if (!_got_size) { + read_header(); + } + + if (!_got_size) { + return false; + } + + xsize = _xsize; + ysize = _ysize; + return true; +} + +void Texture:: +set_size(int xsize, int ysize) { + // If we've already read the file header, don't let anyone tell us + // differently. + if (!_read_header) { + _xsize = xsize; + _ysize = ysize; + _got_size = true; + } +} + +bool Texture:: +get_req(int &xsize, int &ysize) { + if (!_got_req) { + return get_size(xsize, ysize); + } + xsize = _req_xsize; + ysize = _req_ysize; + return true; +} + +bool Texture:: +get_last_req(int &xsize, int &ysize) { + if (!_got_last_req) { + return get_size(xsize, ysize); + } + xsize = _last_req_xsize; + ysize = _last_req_ysize; + return true; +} + +void Texture:: +set_last_req(int xsize, int ysize) { + _last_req_xsize = xsize; + _last_req_ysize = ysize; + _got_last_req = true; +} + +void Texture:: +reset_req(int xsize, int ysize) { + if (_got_last_req && + (_last_req_xsize != xsize || _last_req_ysize != ysize)) { + // We've changed the requested size from the last time we ran. + // That constitutes a change to the texture. + _texture_changed = true; + } + + _req_xsize = xsize; + _req_ysize = ysize; + _got_req = true; +} + +void Texture:: +scale_req(double scale_pct) { + if (!_got_size) { + read_header(); + } + if (_got_size) { + reset_req(_xsize * scale_pct / 100.0, + _ysize * scale_pct / 100.0); + } +} + +void Texture:: +clear_req() { + _got_req = false; + _margin = _attrib_file->_default_margin; + + if (_omit != OR_cmdline) { + // If we previously omitted this texture from the command line, + // preserve that property. + _omit = OR_none; + } +} + +double Texture:: +get_scale_pct() { + if (!_got_size) { + read_header(); + } + if (_got_size && _got_req) { + return + 100.0 * (((double)_req_xsize / (double)_xsize) + + ((double)_req_ysize / (double)_ysize)) / 2.0; + } else { + return 100.0; + } +} + +int Texture:: +get_margin() const { + return _margin; +} + +void Texture:: +set_margin(int margin) { + _margin = margin; +} + +Texture::OmitReason Texture:: +get_omit() const { + return _omit; +} + +void Texture:: +set_omit(OmitReason omit) { + _omit = omit; +} + +bool Texture:: +needs_refresh() { + if (!_texture_changed) { + // We consider the texture to be out-of-date if it's moved around + // in the palette. + _texture_changed = packing_changed(); + } + + if (!_texture_changed && _file_exists) { + // Compare the texture's timestamp to that of its palette (or + // resized copy). If it's newer, it's changed and must be + // replaced. + + Filename target_filename; + if (is_packed() && _omit == OR_none) { + // Compare to the palette file. + target_filename = _palette->get_filename(); + if (_palette->new_palette()) { + // It's a brand new palette; don't even bother comparing + // timestamps. + _texture_changed = true; + } + + } else { + // Compare to the resized file. + target_filename = get_filename(); + } + + if (!_texture_changed) { + _texture_changed = + (target_filename.compare_timestamps(_filename, true, false) < 0); + } + } + + return _texture_changed; +} + +void Texture:: +set_changed(bool changed) { + _texture_changed = changed; +} + +bool Texture:: +unused() const { + return _unused; +} + +void Texture:: +set_unused(bool unused) { + _unused = unused; +} + +bool Texture:: +matched_anything() const { + return _matched_anything; +} + +void Texture:: +set_matched_anything(bool matched_anything) { + _matched_anything = matched_anything; +} + +bool Texture:: +uses_alpha() const { + return _uses_alpha; +} + +void Texture:: +set_uses_alpha(bool uses_alpha) { + _uses_alpha = uses_alpha; +} + +void Texture:: +mark_pack_location(Palette *palette, int left, int top, + int xsize, int ysize, int margin) { + _is_packed = true; + _palette = palette; + _pleft = left; + _ptop = top; + _pxsize = xsize; + _pysize = ysize; + _pmargin = margin; +} + +void Texture:: +mark_unpacked() { + _is_packed = false; +} + +bool Texture:: +is_packed() const { + return _is_packed; +} + +// Returns the same thing as is_packed(), except it doesn't consider a +// texture that has been left alone on a palette to be packed. +bool Texture:: +is_really_packed() const { + return _is_packed && _omit != OR_solitary; +} + +Palette *Texture:: +get_palette() const { + return _is_packed ? _palette : (Palette *)NULL; +} + +bool Texture:: +get_packed_location(int &left, int &top) const { + left = _pleft; + top = _ptop; + return _is_packed; +} + +bool Texture:: +get_packed_size(int &xsize, int &ysize, int &margin) const { + xsize = _pxsize; + ysize = _pysize; + margin = _pmargin; + return _is_packed; +} + +void Texture:: +record_orig_state() { + // Records the current packing state, storing it aside as the state + // at load time. Later, when the packing state may have changed, + // packing_changed() will return true if it has or false if it + // has not. + _orig_is_packed = _is_packed; + if (_is_packed) { + _orig_palette_name = _palette->get_filename(); + _opleft = _pleft; + _optop = _ptop; + _opxsize = _pxsize; + _opysize = _pysize; + _opmargin = _pmargin; + } +} + +bool Texture:: +packing_changed() const { + if (_orig_is_packed != _is_packed) { + return true; + } + if (_is_packed) { + return _orig_palette_name != _palette->get_filename() || + _opleft != _pleft || + _optop != _ptop || + _opxsize != _pxsize || + _opysize != _pysize || + _opmargin != _pmargin; + } + return false; +} + +void Texture:: +write_size(ostream &out) { + if (_omit != OR_unused) { + if (!_got_size) { + read_header(); + } + out << " " << _name; + if (_got_size) { + out << " orig " << _xsize << " " << _ysize; + } + if (_got_req) { + out << " new " << _req_xsize << " " << _req_ysize; + } + out << " " << get_scale_pct() << "%\n"; + } +} + +void Texture:: +write_pathname(ostream &out) const { + if (_got_filename && _omit != OR_unused) { + if (!_file_exists) { + nout << "Warning: texture " << _filename << " does not exist.\n"; + } + + out << " " << _name << " " << _filename << "\n"; + + // Also write out all the alternate filenames. + Filenames::const_iterator fi; + for (fi = _filenames.begin(); fi != _filenames.end(); ++fi) { + if ((*fi) != _filename) { + // Indent by the same number of spaces to line up the filenames. + for (int i = 0; i < (int)_name.length() + 3; i++) { + out << ' '; + } + out << (*fi) << "\n"; + } + } + } +} + +void Texture:: +write_unplaced(ostream &out) const { + if (_omit != OR_none && _omit != OR_unused) { + out << "unplaced " << get_name() << " because "; + switch (_omit) { + case OR_size: + out << "size"; + break; + case OR_repeats: + out << "repeats"; + break; + case OR_omitted: + out << "omitted"; + break; + case OR_unused: + out << "unused"; + break; + case OR_unknown: + out << "unknown"; + break; + case OR_solitary: + out << "solitary"; + break; + case OR_cmdline: + out << "cmdline"; + break; + default: + nout << "Invalid type: " << (int)_omit << "\n"; + abort(); + } + out << "\n"; + } +} + +bool Texture:: +transfer() { + bool okflag = true; + + Filename new_filename = get_filename(); + if (new_filename == _filename) { + nout << "*** Texture " << _name << " is already in the map directory!\n" + << " Cannot modify texture in place!\n"; + return false; + } + + int nx, ny; + if (!get_req(nx, ny)) { + nout << "Unknown size for image " << _name << "\n"; + nx = 16; + ny = 16; + } + + if (_attrib_file->_force_power_2) { + int newx = to_power_2(nx); + int newy = to_power_2(ny); + if (newx != nx || newy != ny) { + nx = newx; + ny = newy; + } + } + + PNMImage *image = read_image(); + if (image == NULL) { + nout << "*** Unable to read " << _name << "\n"; + okflag = false; + + // Create a solid red texture for images we can't read. + image = new PNMImage(nx, ny); + image->fill(1.0, 0.0, 0.0); + + } else { + // Should we scale it? + if (nx != image->get_x_size() && ny != image->get_y_size()) { + nout << "Resizing " << new_filename << " to " + << nx << " " << ny << "\n"; + PNMImage *new_image = + new PNMImage(nx, ny, image->get_color_type()); + new_image->gaussian_filter_from(0.5, *image); + delete image; + image = new_image; + + } else { + nout << "Copying " << new_filename + << " (size " << nx << " " << ny << ")\n"; + } + } + + if (!image->write(new_filename)) { + nout << "Error in writing.\n"; + okflag = false; + } + delete image; + + return okflag; +} + +PNMImage *Texture:: +read_image() { + if (!_got_filename || !_file_exists) { + return NULL; + } + + PNMImage *image = new PNMImage; + if (image->read(_filename)) { + return image; + } + + // Hmm, it wasn't able to read the image successfully. Oh well. + delete image; + return NULL; +} + +void Texture:: +check_size() { + // Make sure the file has the size it claims to have. + if (_got_size) { + int old_xsize = _xsize; + int old_ysize = _ysize; + read_header(); + if (_xsize != old_xsize && _ysize != old_ysize) { + nout << "Source texture " << _name << " has changed size, from " + << old_xsize << " " << old_ysize << " to " + << _xsize << " " << _ysize << "\n"; + _texture_changed = true; + } + } +} + +void Texture:: +read_header() { + // Open the file and read its header to determine its size. + if (_got_filename && _file_exists) { + if (!_read_header) { + _read_header = true; + _got_size = read_image_header(_filename, _xsize, _ysize); + } + _read_header = true; + } +} + +bool Texture:: +read_image_header(const Filename &filename, int &xsize, int &ysize) { + PNMImageHeader header; + if (!header.read_header(filename)) { + nout << "Warning: cannot read texture " << filename << "\n"; + return false; + } + + xsize = header.get_x_size(); + ysize = header.get_y_size(); + return true; +} + +int Texture:: +to_power_2(int value) { + int x = 1; + while ((x << 1) <= value) { + x = (x << 1); + } + return x; +} diff --git a/pandatool/src/egg-palettize/texture.h b/pandatool/src/egg-palettize/texture.h new file mode 100644 index 0000000000..7d005955fd --- /dev/null +++ b/pandatool/src/egg-palettize/texture.h @@ -0,0 +1,131 @@ +// Filename: texture.h +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TEXTURE_H +#define TEXTURE_H + +#include + +#include "imageFile.h" + +#include + +class Palette; +class PNMImage; +class AttribFile; + +//////////////////////////////////////////////////////////////////// +// Class : Texture +// Description : +//////////////////////////////////////////////////////////////////// +class Texture : public ImageFile { +public: + enum OmitReason { + OR_none, + OR_size, OR_repeats, OR_omitted, OR_unused, OR_unknown, + OR_cmdline, OR_solitary + }; + + Texture(AttribFile *af, const Filename &name); + + Filename get_name() const; + + void add_filename(const Filename &filename); + + virtual Filename get_filename() const; + virtual Filename get_basename() const; + + bool get_size(int &xsize, int &ysize); + void set_size(int xsize, int ysize); + + bool get_req(int &xsize, int &ysize); + bool get_last_req(int &xsize, int &ysize); + void set_last_req(int xsize, int ysize); + void reset_req(int xsize, int ysize); + void scale_req(double scale_pct); + void clear_req(); + double get_scale_pct(); + + int get_margin() const; + void set_margin(int margin); + + OmitReason get_omit() const; + void set_omit(OmitReason omit); + + bool needs_refresh(); + void set_changed(bool changed); + + bool unused() const; + void set_unused(bool unused); + + bool matched_anything() const; + void set_matched_anything(bool matched_anything); + + bool uses_alpha() const; + void set_uses_alpha(bool uses_alpha); + + void mark_pack_location(Palette *palette, int left, int top, + int xsize, int ysize, int margin); + void mark_unpacked(); + bool is_packed() const; + bool is_really_packed() const; + Palette *get_palette() const; + bool get_packed_location(int &left, int &top) const; + bool get_packed_size(int &xsize, int &ysize, int &margin) const; + void record_orig_state(); + bool packing_changed() const; + + void write_size(ostream &out); + void write_pathname(ostream &out) const; + void write_unplaced(ostream &out) const; + + bool transfer(); + + PNMImage *read_image(); + +private: + void check_size(); + void read_header(); + bool read_image_header(const Filename &filename, int &xsize, int &ysize); + static int to_power_2(int value); + + Filename _name; + + typedef set Filenames; + Filenames _filenames; + + bool _got_filename; + Filename _filename; + bool _file_exists; + bool _texture_changed; + bool _unused; + bool _matched_anything; + bool _uses_alpha; + + bool _got_size; + int _xsize, _ysize; + + bool _got_req; + int _req_xsize, _req_ysize; + bool _got_last_req; + int _last_req_xsize, _last_req_ysize; + int _margin; + OmitReason _omit; + + bool _is_packed; + Palette *_palette; + int _pleft, _ptop, _pxsize, _pysize, _pmargin; + + bool _orig_is_packed; + Filename _orig_palette_name; + int _opleft, _optop, _opxsize, _opysize, _opmargin; + + bool _read_header; + + AttribFile *_attrib_file; +}; + + +#endif diff --git a/pandatool/src/egg-palettize/userAttribLine.cxx b/pandatool/src/egg-palettize/userAttribLine.cxx new file mode 100644 index 0000000000..9e48be4169 --- /dev/null +++ b/pandatool/src/egg-palettize/userAttribLine.cxx @@ -0,0 +1,362 @@ +// Filename: userAttribLine.cxx +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#include "userAttribLine.h" +#include "string_utils.h" +#include "texture.h" +#include "attribFile.h" + +#include + +#include +#include + +UserAttribLine::TextureName:: +TextureName(const string &pattern) : _pattern(pattern) { +} + +UserAttribLine:: +UserAttribLine(const string &cline, AttribFile *af) : _attrib_file(af) { + _is_old_style = false; + + // By default, all lines are marked 'was_used'. That means they'll + // be preserved across a -k on the command line. Lines that name + // textures will have _was_used set false until a texture actually + // matches that line. + _was_used = true; + + string line = cline; + + // First, strip off the comment. + if (!line.empty()) { + if (line[0] == '#') { + _comment = line; + line = ""; + } else { + size_t pos = line.find(" #"); + if (pos != string::npos) { + while (pos > 0 && isspace(line[pos])) { + pos--; + } + _comment = line.substr(pos + 1); + line = line.substr(0, pos); + } + } + } + + // Now, analyze the line. + _line_type = LT_invalid; + _xsize = 0; + _ysize = 0; + _scale_pct = 0.0; + _msize = -1; + _omit = false; + + bool is_valid = true; + if (line.empty()) { + _line_type = LT_comment; + + } else if (line[0] == ':') { + is_valid = keyword_line(line); + + } else { + is_valid = texture_line(line); + } + + if (!is_valid) { + _line_type = LT_invalid; + } +} + +UserAttribLine:: +~UserAttribLine() { +} + +bool UserAttribLine:: +is_valid() const { + return _line_type != LT_invalid; +} + +bool UserAttribLine:: +is_old_style() const { + return _is_old_style; +} + +bool UserAttribLine:: +was_used() const { + return _was_used; +} + +void UserAttribLine:: +write(ostream &out) const { + switch (_line_type) { + case LT_invalid: + out << "*** invalid line ***\n"; + break; + + case LT_comment: + break; + + case LT_margin: + out << ":margin " << _msize; + break; + + case LT_palette: + out << ":palette " << _xsize << " " << _ysize; + break; + + case LT_size: + list_textures(out) << " : " << _xsize << " " << _ysize; + if (_msize > 0) { + out << " " << _msize; + } + if (_omit) { + out << " omit"; + } + break; + + case LT_scale: + list_textures(out) << " : " << _scale_pct << "%"; + if (_msize > 0) { + out << " " << _msize; + } + if (_omit) { + out << " omit"; + } + break; + + case LT_name: + list_textures(out) << " :"; + if (_omit) { + out << " omit"; + } + break; + + default: + nout << "Unexpected type: " << (int)_line_type << "\n"; + abort(); + }; + + out << _comment << "\n"; +} + +bool UserAttribLine:: +match_texture(Texture *texture, int &margin) { + // See if the texture name matches any of the filename patterns on + // this line. + bool matched_any = false; + TextureNames::const_iterator tni; + for (tni = _texture_names.begin(); + tni != _texture_names.end() && !matched_any; + ++tni) { + if (fnmatch((*tni)._pattern.c_str(), texture->get_name().c_str(), 0) == 0) { + matched_any = true; + } + } + + if (matched_any) { + _was_used = true; + + // It does! So do the right thing with this line. + switch (_line_type) { + case LT_invalid: + case LT_comment: + case LT_palette: + return false; + + case LT_margin: + margin = _msize; + return false; + + case LT_size: + texture->reset_req(_xsize, _ysize); + texture->set_margin(_msize < 0 ? margin : _msize); + if (_omit) { + texture->set_omit(Texture::OR_omitted); + } + return true; + + case LT_scale: + texture->scale_req(_scale_pct); + texture->set_margin(_msize < 0 ? margin : _msize); + if (_omit) { + texture->set_omit(Texture::OR_omitted); + } + return true; + + case LT_name: + if (_omit) { + texture->set_omit(Texture::OR_omitted); + } + return true; + + default: + nout << "Unexpected type: " << (int)_line_type << "\n"; + abort(); + } + } + + return false; +} + +ostream &UserAttribLine:: +list_textures(ostream &out) const { + if (!_texture_names.empty()) { + out << _texture_names[0]._pattern; + for (int i = 1; i < (int)_texture_names.size(); i++) { + out << " " << _texture_names[i]._pattern; + } + } + return out; +} + +bool UserAttribLine:: +keyword_line(const string &line) { + vector words = extract_words(line); + assert(!words.empty()); + + if (words[0] == ":margin") { + _line_type = LT_margin; + if (words.size() != 2) { + nout << "Expected margin size.\n"; + return false; + } + + _msize = atoi(words[1].c_str()); + + } else if (words[0] == ":palette") { + _line_type = LT_palette; + if (words.size() != 3) { + nout << "Expected xsize ysize of palette.\n"; + return false; + } + _xsize = atoi(words[1].c_str()); + _ysize = atoi(words[2].c_str()); + _attrib_file->_pal_xsize = _xsize; + _attrib_file->_pal_ysize = _ysize; + + } else { + nout << "Unknown keyword: " << words[0] << "\n"; + return false; + } + + return true; +} + +bool UserAttribLine:: +texture_line(const string &line) { + _was_used = false; + + // Scan for a colon followed by a space. + + size_t colon = line.find(": "); + if (colon == string::npos) { + return old_style_line(line); + } + + // Split the line into two parts at the colon. + vector names = extract_words(line.substr(0, colon)); + vector params = extract_words(line.substr(colon + 2)); + + vector::const_iterator ni; + for (ni = names.begin(); ni != names.end(); ++ni) { + _texture_names.push_back(TextureName(*ni)); + } + + if (!params.empty() && params[params.size() - 1] == "omit") { + // If the last word is "omit", set the omit flag and remove the + // word. + _omit = true; + params.pop_back(); + } + + if (params.empty()) { + _line_type = LT_name; + return true; + } + + // Is it a percentage? + if (!params[0].empty() && params[0][params[0].size() - 1] == '%') { + _line_type = LT_scale; + _scale_pct = atof(params[0].c_str()); + if (_scale_pct <= 0.0) { + nout << "Invalid scale percentage for "; + copy(names.begin(), names.end(), ostream_iterator(nout, " ")); + nout << ": " << _scale_pct << "%\n"; + return false; + } + return true; + } + + // It must be xsize ysize [margin] + if (params.size() == 2) { + _line_type = LT_size; + _xsize = atoi(params[0].c_str()); + _ysize = atoi(params[1].c_str()); + if (_xsize <= 0 || _ysize <= 0) { + nout << "Invalid texture size for "; + copy(names.begin(), names.end(), ostream_iterator(nout, " ")); + nout << ": " << _xsize << " " << _ysize << "\n"; + return false; + } + return true; + } + + if (params.size() == 3) { + _line_type = LT_size; + _xsize = atoi(params[0].c_str()); + _ysize = atoi(params[1].c_str()); + _msize = atoi(params[2].c_str()); + if (_xsize <= 0 || _ysize <= 0) { + nout << "Invalid texture size for "; + copy(names.begin(), names.end(), ostream_iterator(nout, " ")); + nout << ": " << _xsize << " " << _ysize << "\n"; + return false; + } + return true; + } + + nout << "Expected xsize ysize [msize]\n"; + return false; +} + + +bool UserAttribLine:: +old_style_line(const string &line) { + vector words = extract_words(line); + assert(!words.empty()); + + if (words.size() != 3 && words.size() != 4) { + nout << "Colon omitted.\n"; + return false; + } + + _is_old_style = true; + _line_type = LT_size; + _texture_names.push_back(TextureName(words[0])); + _xsize = atoi(words[1].c_str()); + _ysize = atoi(words[2].c_str()); + if (words.size() > 3) { + _msize = atoi(words[3].c_str()); + + if (_msize < 0) { + _msize = -1; + _omit = true; + + } else if (_msize == _attrib_file->_default_margin) { + _msize = -1; + } + } else { + _msize = -1; + } + + if (_xsize <= 0 || _ysize <= 0) { + nout << "Invalid texture size for " << words[0] << ": " + << _xsize << " " << _ysize << "\n"; + return false; + } + + return true; +} diff --git a/pandatool/src/egg-palettize/userAttribLine.h b/pandatool/src/egg-palettize/userAttribLine.h new file mode 100644 index 0000000000..aa4ac80b18 --- /dev/null +++ b/pandatool/src/egg-palettize/userAttribLine.h @@ -0,0 +1,86 @@ +// Filename: userAttribLine.h +// Created by: drose (02Sep99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef USERATTRIBLINE_H +#define USERATTRIBLINE_H + +#include + +#include + +class AttribFile; +class Texture; + +//////////////////////////////////////////////////////////////////// +// Class : UserAttribLine +// Description : A single entry in the user part (the beginning) of +// the attrib file, this defines how the user would like +// some particular texture to be scaled. +//////////////////////////////////////////////////////////////////// + +// +// This might be a line of any of the following forms: +// +// (blank line) +// # Comment +// :margin msize +// :palette xsize ysize +// texturename xsize ysize msize +// texturename [texturename ...] : xsize ysize [msize] [omit] +// texturename [texturename ...] : scale% [msize] [omit] +// texturename [texturename ...] : [omit] +// + +class UserAttribLine { +public: + UserAttribLine(const string &line, AttribFile *af); + ~UserAttribLine(); + + bool is_valid() const; + bool is_old_style() const; + bool was_used() const; + + void write(ostream &out) const; + + bool match_texture(Texture *texture, int &margin); + +private: + enum LineType { + LT_invalid, + LT_comment, + LT_margin, LT_palette, + LT_size, LT_scale, LT_name + }; + class TextureName { + public: + TextureName(const string &pattern); + TextureName(const TextureName ©) : + _pattern(copy._pattern) { } + + string _pattern; + }; + + typedef vector TextureNames; + TextureNames _texture_names; + + ostream &list_textures(ostream &out) const; + bool keyword_line(const string &line); + bool texture_line(const string &line); + bool old_style_line(const string &line); + + string _comment; + LineType _line_type; + int _xsize, _ysize; + double _scale_pct; + int _msize; + bool _omit; + bool _is_old_style; + bool _was_used; + + AttribFile *_attrib_file; +}; + + +#endif diff --git a/pandatool/src/eggbase/Sources.pp b/pandatool/src/eggbase/Sources.pp index 33291dfa79..6f7aa8429e 100644 --- a/pandatool/src/eggbase/Sources.pp +++ b/pandatool/src/eggbase/Sources.pp @@ -7,13 +7,15 @@ #define SOURCES \ eggBase.cxx eggBase.h eggConverter.cxx eggConverter.h eggFilter.cxx \ - eggFilter.h eggReader.cxx eggReader.h eggToSomething.cxx \ + eggFilter.h eggMultiBase.cxx eggMultiBase.h \ + eggMultiFilter.cxx eggMultiFilter.h \ + eggReader.cxx eggReader.h eggToSomething.cxx \ eggToSomething.h eggWriter.cxx eggWriter.h somethingToEgg.cxx \ somethingToEgg.h #define INSTALL_HEADERS \ - eggBase.h eggConverter.h eggFilter.h eggReader.h eggToSomething.h \ - eggWriter.h somethingToEgg.h + eggBase.h eggConverter.h eggFilter.h eggMultiBase.h eggMultiFilter.h \ + eggReader.h eggToSomething.h eggWriter.h somethingToEgg.h #end ss_lib_target diff --git a/pandatool/src/eggbase/eggBase.cxx b/pandatool/src/eggbase/eggBase.cxx index 8bb4d1ff95..9b0f84ec1b 100644 --- a/pandatool/src/eggbase/eggBase.cxx +++ b/pandatool/src/eggbase/eggBase.cxx @@ -89,5 +89,5 @@ append_command_comment(EggData &data) { } } - data.insert(_data.begin(), new EggComment("", comment)); + data.insert(data.begin(), new EggComment("", comment)); } diff --git a/pandatool/src/eggbase/eggFilter.h b/pandatool/src/eggbase/eggFilter.h index 2eb41abae3..539d2c29ea 100644 --- a/pandatool/src/eggbase/eggFilter.h +++ b/pandatool/src/eggbase/eggFilter.h @@ -20,6 +20,8 @@ class EggFilter : public EggReader, public EggWriter { public: EggFilter(bool allow_last_param = false, bool allow_stdout = true); + +protected: virtual bool handle_args(Args &args); }; diff --git a/pandatool/src/eggbase/eggMultiBase.cxx b/pandatool/src/eggbase/eggMultiBase.cxx new file mode 100644 index 0000000000..cc5c24d368 --- /dev/null +++ b/pandatool/src/eggbase/eggMultiBase.cxx @@ -0,0 +1,104 @@ +// Filename: eggMultiBase.cxx +// Created by: drose (02Nov00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggMultiBase.h" + +#include +#include +#include + +//////////////////////////////////////////////////////////////////// +// Function: EggMultiBase::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +EggMultiBase:: +EggMultiBase() { + add_option + ("cs", "coordinate-system", 80, + "Specify the coordinate system to operate in. This may be one of " + "'y-up', 'z-up', 'y-up-left', or 'z-up-left'.", + &EggMultiBase::dispatch_coordinate_system, + &_got_coordinate_system, &_coordinate_system); +} + + +//////////////////////////////////////////////////////////////////// +// Function: EggMultiBase::append_command_comment +// Access: Protected +// Description: Inserts a comment into the beginning of the indicated +// egg file corresponding to the command line that +// invoked this program. +// +// Normally this function is called automatically when +// appropriate by EggMultiFilter, and it's not necessary +// to call it explicitly. +//////////////////////////////////////////////////////////////////// +void EggMultiBase:: +append_command_comment(EggData &data) { + string comment; + + comment = _program_name; + Args::const_iterator ai; + for (ai = _program_args.begin(); ai != _program_args.end(); ++ai) { + const string &arg = (*ai); + + // First, check to see if the string is shell-acceptable. + bool legal = true; + string::const_iterator si; + for (si = arg.begin(); legal && si != arg.end(); ++si) { + switch (*si) { + case ' ': + case '\n': + case '\t': + case '*': + case '?': + case '\\': + case '(': + case ')': + case '|': + case '&': + case '<': + case '>': + case '"': + case ';': + case '$': + legal = false; + } + } + + if (legal) { + comment += " " + arg; + } else { + comment += " '" + arg + "'"; + } + } + + data.insert(data.begin(), new EggComment("", comment)); +} + +//////////////////////////////////////////////////////////////////// +// Function: EggMultiBase::read_egg +// Access: Protected, Virtual +// Description: Allocates and returns a new EggData structure that +// represents the indicated egg file. If the egg file +// cannot be read for some reason, returns NULL. +// +// This can be overridden by derived classes to control +// how the egg files are read, or to extend the +// information stored with each egg structure, by +// deriving from EggData. +//////////////////////////////////////////////////////////////////// +EggData *EggMultiBase:: +read_egg(const Filename &filename) { + EggData *data = new EggData; + if (data->read(filename)) { + return data; + } + + // Failure reading. + delete data; + return (EggData *)NULL; +} diff --git a/pandatool/src/eggbase/eggMultiBase.h b/pandatool/src/eggbase/eggMultiBase.h new file mode 100644 index 0000000000..0b30b2fb74 --- /dev/null +++ b/pandatool/src/eggbase/eggMultiBase.h @@ -0,0 +1,45 @@ +// Filename: eggMultiBase.h +// Created by: drose (02Nov00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGMULTIBASE_H +#define EGGMULTIBASE_H + +#include + +#include +#include + +class Filename; +class EggData; + +//////////////////////////////////////////////////////////////////// +// Class : EggMultiBase +// Description : This specialization of ProgramBase is intended for +// programs that read and/or write multiple egg files. +// +// See also EggMultiFilter, for a class that also knows +// how to read a bunch of egg files in and write them +// out again. +//////////////////////////////////////////////////////////////////// +class EggMultiBase : public ProgramBase { +public: + EggMultiBase(); + +protected: + void append_command_comment(EggData &_data); + + virtual EggData *read_egg(const Filename &filename); + +protected: + bool _got_coordinate_system; + CoordinateSystem _coordinate_system; + + typedef vector Eggs; + Eggs _eggs; +}; + +#endif + + diff --git a/pandatool/src/eggbase/eggMultiFilter.cxx b/pandatool/src/eggbase/eggMultiFilter.cxx new file mode 100644 index 0000000000..754a1ea0c9 --- /dev/null +++ b/pandatool/src/eggbase/eggMultiFilter.cxx @@ -0,0 +1,160 @@ +// Filename: eggMultiFilter.cxx +// Created by: drose (02Nov00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggMultiFilter.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Function: EggMultiFilter::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +EggMultiFilter:: +EggMultiFilter(bool allow_empty) : _allow_empty(allow_empty) { + clear_runlines(); + add_runline("-o output.egg [opts] input.egg"); + add_runline("-d dirname [opts] file.egg [file.egg ...]"); + add_runline("-inplace [opts] file.egg [file.egg ...]"); + + add_option + ("o", "filename", 50, + "Specify the filename to which the resulting egg file will be written. " + "This is only valid when there is only one input egg file on the command " + "line. If you want to process multiple files simultaneously, you must " + "use either -d or -inplace.", + &EggMultiFilter::dispatch_filename, &_got_output_filename, &_output_filename); + + add_option + ("d", "dirname", 50, + "Specify the name of the directory in which to write the resulting egg " + "files. If you are processing only one egg file, this may be omitted " + "in lieu of the -o option. If you are processing multiple egg files, " + "this may be omitted only if you specify -inplace instead.", + &EggMultiFilter::dispatch_filename, &_got_output_dirname, &_output_dirname); + + add_option + ("inplace", "", 50, + "If this option is given, the input files will be rewritten in place with " + "the results. This obviates the need to specify -d for an output " + "directory; however, it's risky because the original input " + "files are lost.", + &EggMultiFilter::dispatch_none, &_inplace); +} + + +//////////////////////////////////////////////////////////////////// +// Function: EggMultiFilter::handle_args +// Access: Protected, Virtual +// Description: Does something with the additional arguments on the +// command line (after all the -options have been +// parsed). Returns true if the arguments are good, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool EggMultiFilter:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + if (!_allow_empty) { + nout << "You must specify the egg file(s) to read on the command line.\n"; + return false; + } + } else { + // These only apply if we have specified any egg files. + + if (_got_output_filename && args.size() == 1) { + if (_got_output_dirname) { + nout << "Cannot specify both -o and -d.\n"; + return false; + } else if (_inplace) { + nout << "Cannot specify both -o and -inplace.\n"; + return false; + } + + } else { + if (_got_output_filename) { + nout << "Cannot use -o when multiple egg files are specified.\n"; + return false; + } + + if (_got_output_dirname && _inplace) { + nout << "Cannot specify both -inplace and -d.\n"; + return false; + + } else if (!_got_output_dirname && !_inplace) { + nout << "You must specify either -inplace or -d.\n"; + return false; + } + } + } + + + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + EggData *data = read_egg(*ai); + if (data == (EggData *)NULL) { + // Rather than returning false, we simply exit here, so the + // ProgramBase won't try to tell the user how to run the program + // just because we got a bad egg file. + exit(1); + } + + _eggs.push_back(data); + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: EggMultiFilter::post_command_line +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool EggMultiFilter:: +post_command_line() { + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + EggData *data = (*ei); + if (_got_coordinate_system) { + data->set_coordinate_system(_coordinate_system); + } + append_command_comment(*data); + } + + return EggMultiBase::post_command_line(); +} + +//////////////////////////////////////////////////////////////////// +// Function: EggMultiFilter::write_eggs +// Access: Protected +// Description: Writes out all of the egg files in the _eggs vector, +// to the output directory if one is specified, or over +// the input files if -inplace was specified. +//////////////////////////////////////////////////////////////////// +void EggMultiFilter:: +write_eggs() { + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + EggData *data = (*ei); + Filename filename = data->get_egg_filename(); + + if (_got_output_filename) { + nassertv(!_inplace && !_got_output_dirname && _eggs.size() == 1); + filename = _output_filename; + + } else if (_got_output_dirname) { + nassertv(!_inplace); + filename.set_dirname(_output_dirname); + + } else { + nassertv(_inplace); + } + + if (!data->write_egg(filename)) { + // Error writing an egg file; abort. + exit(1); + } + } +} diff --git a/pandatool/src/eggbase/eggMultiFilter.h b/pandatool/src/eggbase/eggMultiFilter.h new file mode 100644 index 0000000000..8685e32d7b --- /dev/null +++ b/pandatool/src/eggbase/eggMultiFilter.h @@ -0,0 +1,40 @@ +// Filename: eggMultiFilter.h +// Created by: drose (02Nov00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGMULTIFILTER_H +#define EGGMULTIFILTER_H + +#include + +#include "eggMultiBase.h" + +//////////////////////////////////////////////////////////////////// +// Class : EggMultiFilter +// Description : This is a base class for a program that reads in a +// number of egg files, operates on them, and writes +// them out again (presumably to a different directory). +//////////////////////////////////////////////////////////////////// +class EggMultiFilter : public EggMultiBase { +public: + EggMultiFilter(bool allow_empty = false); + +protected: + virtual bool handle_args(Args &args); + virtual bool post_command_line(); + + void write_eggs(); + +protected: + bool _allow_empty; + bool _got_output_filename; + Filename _output_filename; + bool _got_output_dirname; + Filename _output_dirname; + bool _inplace; +}; + +#endif + + diff --git a/pandatool/src/eggprogs/eggTrans.cxx b/pandatool/src/eggprogs/eggTrans.cxx index 5a2e05c9f0..d9b1003627 100644 --- a/pandatool/src/eggprogs/eggTrans.cxx +++ b/pandatool/src/eggprogs/eggTrans.cxx @@ -13,7 +13,7 @@ EggTrans:: EggTrans() { set_program_description - ("This program reads an egg file and writes an essentially equivalent " + ("egg-trans reads an egg file and writes an essentially equivalent " "egg file to the standard output, or to the file specified with -o. " "Some simple operations on the egg file are supported."); } diff --git a/pandatool/src/flt/fltHeader.cxx b/pandatool/src/flt/fltHeader.cxx index db43464449..bb434a4b12 100644 --- a/pandatool/src/flt/fltHeader.cxx +++ b/pandatool/src/flt/fltHeader.cxx @@ -56,6 +56,7 @@ FltHeader() : FltBeadID(this) { _vertex_lookups_stale = false; _current_vertex_offset = 0; + _got_color_palette = false; _got_eyepoint_trackplane_palette = false; _auto_attr_update = AU_if_missing; @@ -1264,6 +1265,11 @@ extract_color_palette(FltRecordReader &reader) { nassertr(reader.get_opcode() == FO_color_palette, false); DatagramIterator &iterator = reader.get_iterator(); + if (_got_color_palette) { + nout << "Warning: multiple color palettes found.\n"; + } + _got_color_palette = true; + static const int expected_color_entries = 1024; iterator.skip_bytes(128); @@ -1559,7 +1565,7 @@ write_eyepoint_palette(FltRecordWriter &writer) const { return FE_ok; } - writer.set_opcode(FO_color_palette); + writer.set_opcode(FO_eyepoint_palette); Datagram &datagram = writer.update_datagram(); datagram.pad_bytes(4); diff --git a/pandatool/src/flt/fltHeader.h b/pandatool/src/flt/fltHeader.h index 5ba67640a8..180f3bf3e2 100644 --- a/pandatool/src/flt/fltHeader.h +++ b/pandatool/src/flt/fltHeader.h @@ -236,6 +236,7 @@ private: // Support for the color palette. + bool _got_color_palette; typedef vector Colors; typedef map ColorNames; Colors _colors; diff --git a/pandatool/src/fltprogs/fltCopy.cxx b/pandatool/src/fltprogs/fltCopy.cxx index 889d91978f..34b4b65e16 100644 --- a/pandatool/src/fltprogs/fltCopy.cxx +++ b/pandatool/src/fltprogs/fltCopy.cxx @@ -141,6 +141,11 @@ copy_flt_file(const Filename &source, const Filename &dest, } } + // Remove all the textures from the palette, and then add back only + // those we found in use. This way we don't copy a file that + // references bogus textures. + header->clear_textures(); + Textures::const_iterator ti; for (ti = textures.begin(); ti != textures.end(); ++ti) { FltTexture *tex = (*ti); @@ -164,6 +169,7 @@ copy_flt_file(const Filename &source, const Filename &dest, // filename, relative to the flt file. tex->_filename = dir->get_rel_to(texture_dir) + "/" + texture_filename.get_basename(); + header->add_texture(tex); } } diff --git a/pandatool/src/fltprogs/fltCopy.h b/pandatool/src/fltprogs/fltCopy.h index 0fe3659285..efa2cf4cdf 100644 --- a/pandatool/src/fltprogs/fltCopy.h +++ b/pandatool/src/fltprogs/fltCopy.h @@ -11,6 +11,7 @@ #include "cvsCopy.h" #include +#include #include @@ -54,8 +55,8 @@ private: bool new_file); - typedef set Refs; - typedef set Textures; + typedef set Refs; + typedef set Textures; void scan_flt(FltRecord *record, Refs &refs, Textures &textures); diff --git a/pandatool/src/progbase/programBase.cxx b/pandatool/src/progbase/programBase.cxx index 39ced555fe..0f39b1b4d5 100644 --- a/pandatool/src/progbase/programBase.cxx +++ b/pandatool/src/progbase/programBase.cxx @@ -567,6 +567,55 @@ dispatch_int(const string &opt, const string &arg, void *var) { return true; } +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::dispatch_int_pair +// Access: Protected +// Description: Standard dispatch function for an option that takes +// a pair of integer parameters. The data pointer is to +// an array of two integers. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +dispatch_int_pair(const string &opt, const string &arg, void *var) { + if (arg.empty()) { + nout << "-" << opt + << " requires an pair of integers separated by a comma.\n"; + return false; + } + + size_t comma = arg.find(','); + if (comma == string::npos) { + nout << "-" << opt + << " requires an pair of integers separated by a comma.\n"; + return false; + } + + string first = arg.substr(0, comma); + string second = arg.substr(comma + 1); + + int *ip = (int *)var; + char *endptr; + + const char *first_str = first.c_str(); + ip[0] = strtol(first_str, &endptr, 0); + + if (*endptr != '\0') { + nout << "Invalid integer parameter for -" << opt << ": " + << first << "\n"; + return false; + } + + const char *second_str = second.c_str(); + ip[1] = strtol(second_str, &endptr, 0); + + if (*endptr != '\0') { + nout << "Invalid integer parameter for -" << opt << ": " + << second << "\n"; + return false; + } + + return true; +} + //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_double // Access: Protected diff --git a/pandatool/src/progbase/programBase.h b/pandatool/src/progbase/programBase.h index 5b94285533..54ed0369e8 100644 --- a/pandatool/src/progbase/programBase.h +++ b/pandatool/src/progbase/programBase.h @@ -61,6 +61,7 @@ protected: bool dispatch_none(const string &opt, const string &arg, void *); bool dispatch_count(const string &opt, const string &arg, void *var); bool dispatch_int(const string &opt, const string &arg, void *var); + bool dispatch_int_pair(const string &opt, const string &arg, void *var); bool dispatch_double(const string &opt, const string &arg, void *var); bool dispatch_string(const string &opt, const string &arg, void *var); bool dispatch_filename(const string &opt, const string &arg, void *var);