*** empty log message ***

This commit is contained in:
David Rose 2000-11-03 23:55:40 +00:00
parent a338413da4
commit 43f23a2a55
39 changed files with 4873 additions and 8 deletions

View File

@ -81,6 +81,9 @@
// Do we have <unistd.h>? // Do we have <unistd.h>?
#define HAVE_UNISTD_H 1 #define HAVE_UNISTD_H 1
// Do we have <utime.h>?
#define HAVE_UTIME_H 1
// Do we have <sys/soundcard.h> (and presumably a Linux-style audio // Do we have <sys/soundcard.h> (and presumably a Linux-style audio
// interface)? // interface)?
#define HAVE_SYS_SOUNDCARD_H #define HAVE_SYS_SOUNDCARD_H

View File

@ -81,6 +81,9 @@
// Do we have <unistd.h>? // Do we have <unistd.h>?
#define HAVE_UNISTD_H 1 #define HAVE_UNISTD_H 1
// Do we have <utime.h>?
#define HAVE_UTIME_H 1
// Do we have <sys/soundcard.h> (and presumably a Linux-style audio // Do we have <sys/soundcard.h> (and presumably a Linux-style audio
// interface)? // interface)?
#define HAVE_SYS_SOUNDCARD_H 1 #define HAVE_SYS_SOUNDCARD_H 1

View File

@ -81,6 +81,9 @@
// Do we have <unistd.h>? // Do we have <unistd.h>?
#define HAVE_UNISTD_H #define HAVE_UNISTD_H
// Do we have <utime.h>?
#define HAVE_UTIME_H
// Do we have <sys/soundcard.h> (and presumably a Linux-style audio // Do we have <sys/soundcard.h> (and presumably a Linux-style audio
// interface)? // interface)?
#define HAVE_SYS_SOUNDCARD_H #define HAVE_SYS_SOUNDCARD_H

View File

@ -130,6 +130,9 @@ $[cdefine HAVE_SYS_TYPES]
/* Define if you have the <unistd.h> header file. */ /* Define if you have the <unistd.h> header file. */
$[cdefine HAVE_UNISTD_H] $[cdefine HAVE_UNISTD_H]
/* Define if you have the <utime.h> header file. */
$[cdefine HAVE_UTIME_H]
/* Do we have <sys/soundcard.h> (and presumably a Linux-style audio /* Do we have <sys/soundcard.h> (and presumably a Linux-style audio
interface)? */ interface)? */
$[cdefine HAVE_SYS_SOUNDCARD_H] $[cdefine HAVE_SYS_SOUNDCARD_H]

View File

@ -31,6 +31,10 @@ Warning: Lib(s) $[nonexisting], referenced in $[DIRNAME]/$[TARGET], not found.
#set DEPENDABLE_HEADERS $[DEPENDABLE_HEADERS] $[filter %.h %.I,$[SOURCES]] #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 #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 // Allow the user to define additional EXTRA_DEPENDS targets in each
// Sources.pp. // Sources.pp.
#define DEPEND_DIRS \ #define DEPEND_DIRS \

View File

@ -18,6 +18,14 @@
#include <stdio.h> // For rename() #include <stdio.h> // For rename()
#include <sys/stat.h> #include <sys/stat.h>
#ifdef HAVE_UTIME_H
#include <utime.h>
// We assume we have these too.
#include <errno.h>
#include <fcntl.h>
#endif
#if defined(WIN32) #if defined(WIN32)
/* begin Win32-specific code */ /* begin Win32-specific code */
@ -787,6 +795,45 @@ open_read_write(fstream &stream) const {
return (!stream.fail()); 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 // Function: Filename::unlink
// Access: Public // Access: Public

View File

@ -130,6 +130,8 @@ public:
bool open_append(ofstream &stream) const; bool open_append(ofstream &stream) const;
bool open_read_write(fstream &stream) const; bool open_read_write(fstream &stream) const;
bool touch() const;
bool unlink() const; bool unlink() const;
bool rename_to(const Filename &other) const; bool rename_to(const Filename &other) const;

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
// Filename: attribFile.h
// Created by: drose (02Sep99)
//
////////////////////////////////////////////////////////////////////
#ifndef ATTRIBFILE_H
#define ATTRIBFILE_H
#include <pandatoolbase.h>
#include <filename.h>
#include <map>
#include <vector>
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<Texture *> &textures);
SourceEgg *get_egg(Filename name);
bool generate_palette_images();
bool transfer_unplaced_images(bool force_redo_all);
void check_dup_textures(map<string, Texture *> &textures,
map<string, int> &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<UserAttribLine *> UserLines;
UserLines _user_lines;
typedef map<string, SourceEgg *> Eggs;
Eggs _eggs;
typedef vector<Palette *> Palettes;
Palettes _palettes;
typedef map<string, Texture *> 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<string> &words, istream &infile,
string &line, int &line_num);
bool parse_packing(const vector<string> &words, istream &infile,
string &line, int &line_num);
bool parse_texture(const vector<string> &words, istream &infile,
string &line, int &line_num);
bool parse_pathname(const vector<string> &words, istream &infile,
string &line, int &line_num);
bool parse_egg(const vector<string> &words, istream &infile,
string &line, int &line_num);
bool parse_palette(const vector<string> &words, istream &infile,
string &line, int &line_num);
bool parse_unplaced(const vector<string> &words, istream &infile,
string &line, int &line_num);
bool parse_surprises(const vector<string> &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

View File

@ -0,0 +1,14 @@
// Filename: config_egg_palettize.cxx
// Created by: drose (02Nov00)
//
////////////////////////////////////////////////////////////////////
#include <dconfig.h>
#include "sourceEgg.h"
Configure(config_egg_palettize);
ConfigureFn(config_egg_palettize) {
SourceEgg::init_type();
}

View File

@ -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 <pnmImage.h>
#include <stdio.h>
////////////////////////////////////////////////////////////////////
// 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<string, Texture *> textures;
map<string, int> 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<string, int>::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<Texture::OmitReason, pair<int, int> > UnplacedReasons;
UnplacedReasons unplaced_reasons;
map<string, Texture *>::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<int, int>(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;
}

View File

@ -0,0 +1,71 @@
// Filename: eggPalettize.h
// Created by: drose (02Sep99)
//
////////////////////////////////////////////////////////////////////
#ifndef EGGPALETTIZE_H
#define EGGPALETTIZE_H
#include <pandatoolbase.h>
#include "attribFile.h"
#include <eggMultiFilter.h>
#include <vector>
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<AttribFile *> 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

View File

@ -0,0 +1,14 @@
// Filename: imageFile.cxx
// Created by: drose (07Sep99)
//
////////////////////////////////////////////////////////////////////
#include "imageFile.h"
ImageFile::
ImageFile() {
}
ImageFile::
~ImageFile() {
}

View File

@ -0,0 +1,28 @@
// Filename: imageFile.h
// Created by: drose (07Sep99)
//
////////////////////////////////////////////////////////////////////
#ifndef IMAGEFILE_H
#define IMAGEFILE_H
#include <pandatoolbase.h>
#include <filename.h>
////////////////////////////////////////////////////////////////////
// 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

View File

@ -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 <notify.h>
#include <pnmImage.h>
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;
}

View File

@ -0,0 +1,90 @@
// Filename: palette.h
// Created by: drose (02Sep99)
//
////////////////////////////////////////////////////////////////////
#ifndef PALETTE_H
#define PALETTE_H
#include <pandatoolbase.h>
#include "imageFile.h"
#include <filename.h>
#include <vector>
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<TexturePlacement> TexPlace;
TexPlace _texplace;
Filename _filename;
Filename _basename;
int _index;
int _xsize, _ysize, _components;
bool _palette_changed;
bool _new_palette;
AttribFile *_attrib_file;
};
#endif

View File

@ -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 <eggAttributes.h>
#include <eggVertex.h>
#include <eggNurbsSurface.h>
#include <eggPrimitive.h>
#include <eggTextureCollection.h>
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);
}
}
}

View File

@ -0,0 +1,75 @@
// Filename: sourceEgg.h
// Created by: drose (02Sep99)
//
////////////////////////////////////////////////////////////////////
#ifndef SOURCEGG_H
#define SOURCEGG_H
#include <pandatoolbase.h>
#include <eggData.h>
#include <luse.h>
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<TextureRef> 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

View File

@ -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<string>
extract_words(const string &str) {
vector<string> 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 &param, 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));
}

View File

@ -0,0 +1,20 @@
// Filename: string_utils.h
// Created by: drose (02Sep99)
//
////////////////////////////////////////////////////////////////////
#ifndef STRING_UTILS_H
#define STRING_UTILS_H
#include <pandatoolbase.h>
#include <vector>
string trim_left(const string &str);
string trim_right(const string &str);
vector<string> extract_words(const string &str);
void extract_param_value(const string &str, string &param, string &value);
#endif

View File

@ -0,0 +1,586 @@
// Filename: texture.cxx
// Created by: drose (02Sep99)
//
////////////////////////////////////////////////////////////////////
#include "texture.h"
#include "palette.h"
#include "attribFile.h"
#include <pnmImage.h>
#include <pnmReader.h>
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;
}

View File

@ -0,0 +1,131 @@
// Filename: texture.h
// Created by: drose (02Sep99)
//
////////////////////////////////////////////////////////////////////
#ifndef TEXTURE_H
#define TEXTURE_H
#include <pandatoolbase.h>
#include "imageFile.h"
#include <set>
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<Filename> 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

View File

@ -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 <notify.h>
#include <ctype.h>
#include <fnmatch.h>
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<string> 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<string> names = extract_words(line.substr(0, colon));
vector<string> params = extract_words(line.substr(colon + 2));
vector<string>::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<string>(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<string>(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<string>(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<string> 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;
}

View File

@ -0,0 +1,86 @@
// Filename: userAttribLine.h
// Created by: drose (02Sep99)
//
////////////////////////////////////////////////////////////////////
#ifndef USERATTRIBLINE_H
#define USERATTRIBLINE_H
#include <pandatoolbase.h>
#include <vector>
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 &copy) :
_pattern(copy._pattern) { }
string _pattern;
};
typedef vector<TextureName> 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

View File

@ -7,13 +7,15 @@
#define SOURCES \ #define SOURCES \
eggBase.cxx eggBase.h eggConverter.cxx eggConverter.h eggFilter.cxx \ 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 \ eggToSomething.h eggWriter.cxx eggWriter.h somethingToEgg.cxx \
somethingToEgg.h somethingToEgg.h
#define INSTALL_HEADERS \ #define INSTALL_HEADERS \
eggBase.h eggConverter.h eggFilter.h eggReader.h eggToSomething.h \ eggBase.h eggConverter.h eggFilter.h eggMultiBase.h eggMultiFilter.h \
eggWriter.h somethingToEgg.h eggReader.h eggToSomething.h eggWriter.h somethingToEgg.h
#end ss_lib_target #end ss_lib_target

View File

@ -89,5 +89,5 @@ append_command_comment(EggData &data) {
} }
} }
data.insert(_data.begin(), new EggComment("", comment)); data.insert(data.begin(), new EggComment("", comment));
} }

View File

@ -20,6 +20,8 @@
class EggFilter : public EggReader, public EggWriter { class EggFilter : public EggReader, public EggWriter {
public: public:
EggFilter(bool allow_last_param = false, bool allow_stdout = true); EggFilter(bool allow_last_param = false, bool allow_stdout = true);
protected:
virtual bool handle_args(Args &args); virtual bool handle_args(Args &args);
}; };

View File

@ -0,0 +1,104 @@
// Filename: eggMultiBase.cxx
// Created by: drose (02Nov00)
//
////////////////////////////////////////////////////////////////////
#include "eggMultiBase.h"
#include <eggData.h>
#include <eggComment.h>
#include <filename.h>
////////////////////////////////////////////////////////////////////
// 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;
}

View File

@ -0,0 +1,45 @@
// Filename: eggMultiBase.h
// Created by: drose (02Nov00)
//
////////////////////////////////////////////////////////////////////
#ifndef EGGMULTIBASE_H
#define EGGMULTIBASE_H
#include <pandatoolbase.h>
#include <programBase.h>
#include <coordinateSystem.h>
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<EggData *> Eggs;
Eggs _eggs;
};
#endif

View File

@ -0,0 +1,160 @@
// Filename: eggMultiFilter.cxx
// Created by: drose (02Nov00)
//
////////////////////////////////////////////////////////////////////
#include "eggMultiFilter.h"
#include <notify.h>
#include <eggData.h>
////////////////////////////////////////////////////////////////////
// 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);
}
}
}

View File

@ -0,0 +1,40 @@
// Filename: eggMultiFilter.h
// Created by: drose (02Nov00)
//
////////////////////////////////////////////////////////////////////
#ifndef EGGMULTIFILTER_H
#define EGGMULTIFILTER_H
#include <pandatoolbase.h>
#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

View File

@ -13,7 +13,7 @@
EggTrans:: EggTrans::
EggTrans() { EggTrans() {
set_program_description 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. " "egg file to the standard output, or to the file specified with -o. "
"Some simple operations on the egg file are supported."); "Some simple operations on the egg file are supported.");
} }

View File

@ -56,6 +56,7 @@ FltHeader() : FltBeadID(this) {
_vertex_lookups_stale = false; _vertex_lookups_stale = false;
_current_vertex_offset = 0; _current_vertex_offset = 0;
_got_color_palette = false;
_got_eyepoint_trackplane_palette = false; _got_eyepoint_trackplane_palette = false;
_auto_attr_update = AU_if_missing; _auto_attr_update = AU_if_missing;
@ -1264,6 +1265,11 @@ extract_color_palette(FltRecordReader &reader) {
nassertr(reader.get_opcode() == FO_color_palette, false); nassertr(reader.get_opcode() == FO_color_palette, false);
DatagramIterator &iterator = reader.get_iterator(); 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; static const int expected_color_entries = 1024;
iterator.skip_bytes(128); iterator.skip_bytes(128);
@ -1559,7 +1565,7 @@ write_eyepoint_palette(FltRecordWriter &writer) const {
return FE_ok; return FE_ok;
} }
writer.set_opcode(FO_color_palette); writer.set_opcode(FO_eyepoint_palette);
Datagram &datagram = writer.update_datagram(); Datagram &datagram = writer.update_datagram();
datagram.pad_bytes(4); datagram.pad_bytes(4);

View File

@ -236,6 +236,7 @@ private:
// Support for the color palette. // Support for the color palette.
bool _got_color_palette;
typedef vector<FltPackedColor> Colors; typedef vector<FltPackedColor> Colors;
typedef map<int, string> ColorNames; typedef map<int, string> ColorNames;
Colors _colors; Colors _colors;

View File

@ -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; Textures::const_iterator ti;
for (ti = textures.begin(); ti != textures.end(); ++ti) { for (ti = textures.begin(); ti != textures.end(); ++ti) {
FltTexture *tex = (*ti); FltTexture *tex = (*ti);
@ -164,6 +169,7 @@ copy_flt_file(const Filename &source, const Filename &dest,
// filename, relative to the flt file. // filename, relative to the flt file.
tex->_filename = dir->get_rel_to(texture_dir) + "/" + tex->_filename = dir->get_rel_to(texture_dir) + "/" +
texture_filename.get_basename(); texture_filename.get_basename();
header->add_texture(tex);
} }
} }

View File

@ -11,6 +11,7 @@
#include "cvsCopy.h" #include "cvsCopy.h"
#include <dSearchPath.h> #include <dSearchPath.h>
#include <pointerTo.h>
#include <set> #include <set>
@ -54,8 +55,8 @@ private:
bool new_file); bool new_file);
typedef set<FltExternalReference *> Refs; typedef set<PT(FltExternalReference)> Refs;
typedef set<FltTexture *> Textures; typedef set<PT(FltTexture)> Textures;
void scan_flt(FltRecord *record, Refs &refs, Textures &textures); void scan_flt(FltRecord *record, Refs &refs, Textures &textures);

View File

@ -567,6 +567,55 @@ dispatch_int(const string &opt, const string &arg, void *var) {
return true; 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 // Function: ProgramBase::dispatch_double
// Access: Protected // Access: Protected

View File

@ -61,6 +61,7 @@ protected:
bool dispatch_none(const string &opt, const string &arg, void *); bool dispatch_none(const string &opt, const string &arg, void *);
bool dispatch_count(const string &opt, const string &arg, void *var); 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(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_double(const string &opt, const string &arg, void *var);
bool dispatch_string(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); bool dispatch_filename(const string &opt, const string &arg, void *var);