1520 lines
52 KiB
C++
1520 lines
52 KiB
C++
// Filename: textureImage.cxx
|
|
// Created by: drose (29Nov00)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d-general@lists.sourceforge.net .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "textureImage.h"
|
|
#include "sourceTextureImage.h"
|
|
#include "destTextureImage.h"
|
|
#include "eggFile.h"
|
|
#include "paletteGroup.h"
|
|
#include "paletteImage.h"
|
|
#include "texturePlacement.h"
|
|
#include "filenameUnifier.h"
|
|
#include "string_utils.h"
|
|
#include "indent.h"
|
|
#include "datagram.h"
|
|
#include "datagramIterator.h"
|
|
#include "bamReader.h"
|
|
#include "bamWriter.h"
|
|
#include "pnmFileType.h"
|
|
#include "indirectCompareNames.h"
|
|
#include "pvector.h"
|
|
|
|
TypeHandle TextureImage::_type_handle;
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
TextureImage::
|
|
TextureImage() {
|
|
_preferred_source = (SourceTextureImage *)NULL;
|
|
_read_source_image = false;
|
|
_allow_release_source_image = true;
|
|
_is_surprise = true;
|
|
_ever_read_image = false;
|
|
_forced_grayscale = false;
|
|
_alpha_bits = 0;
|
|
_alpha_mode = EggRenderMode::AM_unspecified;
|
|
_txa_wrap_u = EggTexture::WM_unspecified;
|
|
_txa_wrap_v = EggTexture::WM_unspecified;
|
|
_texture_named = false;
|
|
_got_txa_file = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::note_egg_file
|
|
// Access: Public
|
|
// Description: Records that a particular egg file references this
|
|
// texture. This is essential to know when deciding how
|
|
// to assign the TextureImage to the various
|
|
// PaletteGroups.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
note_egg_file(EggFile *egg_file) {
|
|
nassertv(!egg_file->get_complete_groups().empty());
|
|
_egg_files.insert(egg_file);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::assign_groups
|
|
// Access: Public
|
|
// Description: Assigns the texture to all of the PaletteGroups the
|
|
// various egg files that use it need. Attempts to
|
|
// choose the minimum set of PaletteGroups that
|
|
// satisfies all of the egg files.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
assign_groups() {
|
|
if (_egg_files.empty()) {
|
|
// If we're not referenced by any egg files any more, assign us to
|
|
// no groups.
|
|
PaletteGroups empty;
|
|
assign_to_groups(empty);
|
|
return;
|
|
}
|
|
|
|
PaletteGroups definitely_in;
|
|
|
|
// First, we need to eliminate from consideration all the egg files
|
|
// that are already taken care of by the user's explicit group
|
|
// assignments for this texture.
|
|
WorkingEggs needed_eggs;
|
|
|
|
if (_explicitly_assigned_groups.empty()) {
|
|
// If we have no explicit group assignments, we must consider all
|
|
// the egg files.
|
|
copy(_egg_files.begin(), _egg_files.end(), back_inserter(needed_eggs));
|
|
|
|
} else {
|
|
// Otherwise, we only need to consider the egg files that don't
|
|
// have any groups in common with our explicit assignments.
|
|
|
|
EggFiles::const_iterator ei;
|
|
for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
|
|
PaletteGroups intersect;
|
|
intersect.make_intersection(_explicitly_assigned_groups, (*ei)->get_complete_groups());
|
|
if (!intersect.empty()) {
|
|
// This egg file is satisfied by one of the texture's explicit
|
|
// assignments.
|
|
|
|
// We must use at least one of the explicitly-assigned groups
|
|
// that satisfied the egg file. We don't need to use all of
|
|
// them, however, and we choose the first one arbitrarily.
|
|
definitely_in.insert(*intersect.begin());
|
|
|
|
} else {
|
|
// This egg file was not satisfied by any of the texture's
|
|
// explicit assignments. Therefore, we'll need to choose some
|
|
// additional group to assign the texture to, to make the egg
|
|
// file happy. Defer this a bit.
|
|
needed_eggs.push_back(*ei);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (!needed_eggs.empty()) {
|
|
// We need to know the complete set of groups that we need to
|
|
// consider adding the texture to. This is the union of all the egg
|
|
// files' requested groups.
|
|
PaletteGroups total;
|
|
WorkingEggs::const_iterator ei;
|
|
for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) {
|
|
total.make_union(total, (*ei)->get_complete_groups());
|
|
}
|
|
|
|
// Now, find the group that will satisfy the most egg files. If
|
|
// two groups satisfy the same number of egg files, choose (a) the
|
|
// most specific one, i.e. with the lowest dirname_level, or the
|
|
// lowest dependency_level if the dirname_levels are equal, and
|
|
// (b) the one that has the fewest egg files sharing it.
|
|
nassertv(!total.empty());
|
|
PaletteGroups::iterator gi = total.begin();
|
|
PaletteGroup *best = (*gi);
|
|
int best_egg_count = compute_egg_count(best, needed_eggs);
|
|
++gi;
|
|
while (gi != total.end()) {
|
|
PaletteGroup *group = (*gi);
|
|
|
|
// Do we prefer this group to our current 'best'?
|
|
bool prefer_group = false;
|
|
int group_egg_count = compute_egg_count(group, needed_eggs);
|
|
if (group_egg_count != best_egg_count) {
|
|
prefer_group = (group_egg_count > best_egg_count);
|
|
|
|
} else {
|
|
prefer_group = group->is_preferred_over(*best);
|
|
}
|
|
|
|
if (prefer_group) {
|
|
best = group;
|
|
best_egg_count = group_egg_count;
|
|
}
|
|
++gi;
|
|
}
|
|
|
|
// Okay, now we've picked the best group. Eliminate all the eggs
|
|
// from consideration that are satisfied by this group, and repeat.
|
|
definitely_in.insert(best);
|
|
|
|
WorkingEggs next_needed_eggs;
|
|
for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) {
|
|
if ((*ei)->get_complete_groups().count(best) == 0) {
|
|
// This one wasn't eliminated.
|
|
next_needed_eggs.push_back(*ei);
|
|
}
|
|
}
|
|
needed_eggs.swap(next_needed_eggs);
|
|
}
|
|
|
|
// Finally, now that we've computed the set of groups we need to
|
|
// assign the texture to, we need to reconcile this with the set of
|
|
// groups we've assigned the texture to previously.
|
|
assign_to_groups(definitely_in);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_groups
|
|
// Access: Public
|
|
// Description: Once assign_groups() has been called, this returns
|
|
// the actual set of groups the TextureImage has been
|
|
// assigned to.
|
|
////////////////////////////////////////////////////////////////////
|
|
const PaletteGroups &TextureImage::
|
|
get_groups() const {
|
|
return _actual_assigned_groups;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_placement
|
|
// Access: Public
|
|
// Description: Gets the TexturePlacement object which represents the
|
|
// assignment of this texture to the indicated group.
|
|
// If the texture has not been assigned to the indicated
|
|
// group, returns NULL.
|
|
////////////////////////////////////////////////////////////////////
|
|
TexturePlacement *TextureImage::
|
|
get_placement(PaletteGroup *group) const {
|
|
Placement::const_iterator pi;
|
|
pi = _placement.find(group);
|
|
if (pi == _placement.end()) {
|
|
return (TexturePlacement *)NULL;
|
|
}
|
|
|
|
return (*pi).second;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::force_replace
|
|
// Access: Public
|
|
// Description: Removes the texture from any PaletteImages it is
|
|
// assigned to, but does not remove it from the groups.
|
|
// It will be re-placed within each group when
|
|
// PaletteGroup::place_all() is called.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
force_replace() {
|
|
Placement::iterator pi;
|
|
for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
|
|
(*pi).second->force_replace();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::mark_eggs_stale
|
|
// Access: Public
|
|
// Description: Marks all the egg files that reference this texture
|
|
// stale. Should be called only when the texture
|
|
// properties change in some catastrophic way that will
|
|
// require every egg file referencing it to be
|
|
// regenerated, even if it is not palettized.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
mark_eggs_stale() {
|
|
Placement::iterator pi;
|
|
for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
|
|
(*pi).second->mark_eggs_stale();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::mark_texture_named
|
|
// Access: Public
|
|
// Description: Indicates that this particular texture has been named
|
|
// by the user for processing this session, normally by
|
|
// listing an egg file on the command line that
|
|
// references it.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
mark_texture_named() {
|
|
_texture_named = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::is_texture_named
|
|
// Access: Public
|
|
// Description: Returns true if this particular texture has been
|
|
// named by the user for procession this session, for
|
|
// instance by listing an egg file on the command line
|
|
// that references it.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextureImage::
|
|
is_texture_named() const {
|
|
return _texture_named;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::pre_txa_file
|
|
// Access: Public
|
|
// Description: Updates any internal state prior to reading the .txa
|
|
// file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
pre_txa_file() {
|
|
// Save our current properties, so we can note if they change.
|
|
_pre_txa_properties = _properties;
|
|
|
|
// Get our properties from the actual image for this texture. It's
|
|
// possible the .txa file will update them further.
|
|
SourceTextureImage *source = get_preferred_source();
|
|
if (source != (SourceTextureImage *)NULL) {
|
|
_properties = source->get_properties();
|
|
}
|
|
|
|
_pre_txa_alpha_mode = _alpha_mode;
|
|
_alpha_mode = EggRenderMode::AM_unspecified;
|
|
|
|
_request.pre_txa_file();
|
|
_is_surprise = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::post_txa_file
|
|
// Access: Public
|
|
// Description: Once the .txa file has been read and the TextureImage
|
|
// matched against it, considers applying the requested
|
|
// size change. Updates the TextureImage's size with
|
|
// the size the texture ought to be, if this can be
|
|
// determined.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
post_txa_file() {
|
|
_got_txa_file = true;
|
|
|
|
// First, get the actual size of the texture.
|
|
SourceTextureImage *source = get_preferred_source();
|
|
if (source != (SourceTextureImage *)NULL) {
|
|
if (source->get_size()) {
|
|
_size_known = true;
|
|
_x_size = source->get_x_size();
|
|
_y_size = source->get_y_size();
|
|
_properties.set_num_channels(source->get_num_channels());
|
|
}
|
|
}
|
|
|
|
// Now update this with a particularly requested size.
|
|
if (_request._got_size) {
|
|
_size_known = true;
|
|
_x_size = _request._x_size;
|
|
_y_size = _request._y_size;
|
|
}
|
|
|
|
if (_txa_wrap_u != _request._wrap_u ||
|
|
_txa_wrap_v != _request._wrap_v) {
|
|
_txa_wrap_u = _request._wrap_u;
|
|
_txa_wrap_v = _request._wrap_v;
|
|
|
|
// If the explicit wrap mode changes, we may need to regenerate
|
|
// the egg files, and/or refill the palettes.
|
|
mark_eggs_stale();
|
|
|
|
Placement::iterator pi;
|
|
for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
|
|
TexturePlacement *placement = (*pi).second;
|
|
placement->mark_unfilled();
|
|
}
|
|
}
|
|
|
|
if (_properties.has_num_channels() && !_request._keep_format) {
|
|
int num_channels = _properties.get_num_channels();
|
|
// Examine the image to determine if we can downgrade the number
|
|
// of channels, for instance from color to grayscale.
|
|
if (num_channels == 3 || num_channels == 4) {
|
|
consider_grayscale();
|
|
}
|
|
|
|
// Also consider the alpha properties, and whether we should
|
|
// downgrade from alpha to non-alpha.
|
|
if (num_channels == 2 || num_channels == 4) {
|
|
consider_alpha();
|
|
}
|
|
}
|
|
|
|
// However, if we got an explicit request for channels, honor that.
|
|
if (_request._got_num_channels) {
|
|
_properties.set_num_channels(_request._num_channels);
|
|
}
|
|
|
|
_properties._generic_format = _request._generic_format;
|
|
_properties._keep_format = _request._keep_format;
|
|
|
|
if (_request._format != EggTexture::F_unspecified) {
|
|
_properties._format = _request._format;
|
|
_properties._force_format = _request._force_format;
|
|
}
|
|
|
|
if (_request._minfilter != EggTexture::FT_unspecified) {
|
|
_properties._minfilter = _request._minfilter;
|
|
}
|
|
if (_request._magfilter != EggTexture::FT_unspecified) {
|
|
_properties._magfilter = _request._magfilter;
|
|
}
|
|
|
|
_properties._anisotropic_degree = _request._anisotropic_degree;
|
|
|
|
if (_properties._color_type == (PNMFileType *)NULL) {
|
|
_properties._color_type = _request._properties._color_type;
|
|
_properties._alpha_type = _request._properties._alpha_type;
|
|
}
|
|
|
|
// Finally, make sure our properties are fully defined.
|
|
_properties.fully_define();
|
|
|
|
// Now, if our properties have changed in all that from our previous
|
|
// session, we need to re-place ourself in all palette groups.
|
|
if (_properties != _pre_txa_properties) {
|
|
force_replace();
|
|
|
|
// The above will mark the egg files stale when the texture is
|
|
// palettized (since the UV's will certainly need to be
|
|
// recomputed), but sometimes we need to mark the egg files stale
|
|
// even when the texture is not palettized (if a critical property
|
|
// has changed). The following accomplishes this:
|
|
if (!_properties.egg_properties_match(_pre_txa_properties)) {
|
|
mark_eggs_stale();
|
|
}
|
|
}
|
|
|
|
// The alpha mode isn't stored in the properties, because it doesn't
|
|
// affect which textures may be associated into a common palette.
|
|
if (_request._alpha_mode != EggRenderMode::AM_unspecified) {
|
|
_alpha_mode = _request._alpha_mode;
|
|
}
|
|
|
|
// On the other hand, if we don't have an alpha channel, we
|
|
// shouldn't have an alpha mode.
|
|
if (_properties.has_num_channels()) {
|
|
int num_channels = _properties.get_num_channels();
|
|
if (num_channels == 1 || num_channels == 3) {
|
|
_alpha_mode = EggRenderMode::AM_unspecified;
|
|
}
|
|
}
|
|
|
|
// If we've changed the alpha mode, we should also mark the eggs
|
|
// stale.
|
|
if (_pre_txa_alpha_mode != _alpha_mode) {
|
|
mark_eggs_stale();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::got_txa_file
|
|
// Access: Public
|
|
// Description: Returns true if this TextureImage has been looked up
|
|
// in the .txa file this session, false otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextureImage::
|
|
got_txa_file() const {
|
|
return _got_txa_file;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::determine_placement_size
|
|
// Access: Public
|
|
// Description: Calls determine_size() on each TexturePlacement for
|
|
// the texture, to ensure that each TexturePlacement is
|
|
// still requesting the best possible size for the
|
|
// texture.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
determine_placement_size() {
|
|
Placement::iterator pi;
|
|
for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
|
|
TexturePlacement *placement = (*pi).second;
|
|
placement->determine_size();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_omit
|
|
// Access: Public
|
|
// Description: Returns true if the user specifically requested to
|
|
// omit this texture via the "omit" keyword in the .txa
|
|
// file, or false otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextureImage::
|
|
get_omit() const {
|
|
return _request._omit;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_coverage_threshold
|
|
// Access: Public
|
|
// Description: Returns the appropriate coverage threshold for this
|
|
// texture. This is either the
|
|
// Palettizer::_coverage_threshold parameter, given
|
|
// globally via -r, or a particular value for this
|
|
// texture as supplied by the "coverage" keyword in the
|
|
// .txa file.
|
|
////////////////////////////////////////////////////////////////////
|
|
double TextureImage::
|
|
get_coverage_threshold() const {
|
|
return _request._coverage_threshold;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_margin
|
|
// Access: Public
|
|
// Description: Returns the appropriate margin for this texture.
|
|
// This is either the Palettizer::_margin parameter, or
|
|
// a particular value for this texture as supplied by
|
|
// the "margin" keyword in the .txa file.
|
|
////////////////////////////////////////////////////////////////////
|
|
int TextureImage::
|
|
get_margin() const {
|
|
return _request._margin;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::is_surprise
|
|
// Access: Public
|
|
// Description: Returns true if this particular texture is a
|
|
// 'surprise', i.e. it wasn't matched by a line in the
|
|
// .txa file that didn't include the keyword 'cont'.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextureImage::
|
|
is_surprise() const {
|
|
if (_placement.empty()) {
|
|
// A texture that is not actually placed anywhere is not
|
|
// considered a surprise.
|
|
return false;
|
|
}
|
|
|
|
return _is_surprise;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::is_used
|
|
// Access: Public
|
|
// Description: Returns true if this particular texture has been
|
|
// placed somewhere, anywhere, or false if it is not
|
|
// used.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextureImage::
|
|
is_used() const {
|
|
return !_placement.empty();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_alpha_mode
|
|
// Access: Public
|
|
// Description: Returns the alpha mode that should be used to render
|
|
// objects with this texture, as specified by the user
|
|
// or as determined from examining the texture's alpha
|
|
// channel.
|
|
////////////////////////////////////////////////////////////////////
|
|
EggRenderMode::AlphaMode TextureImage::
|
|
get_alpha_mode() const {
|
|
return _alpha_mode;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_txa_wrap_u
|
|
// Access: Public
|
|
// Description: Returns the wrap mode specified in the u direction in
|
|
// the txa file, or WM_unspecified.
|
|
////////////////////////////////////////////////////////////////////
|
|
EggTexture::WrapMode TextureImage::
|
|
get_txa_wrap_u() const {
|
|
return _txa_wrap_u;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_txa_wrap_v
|
|
// Access: Public
|
|
// Description: Returns the wrap mode specified in the v direction in
|
|
// the txa file, or WM_unspecified.
|
|
////////////////////////////////////////////////////////////////////
|
|
EggTexture::WrapMode TextureImage::
|
|
get_txa_wrap_v() const {
|
|
return _txa_wrap_v;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_source
|
|
// Access: Public
|
|
// Description: Returns the SourceTextureImage corresponding to the
|
|
// given filename(s). If the given filename has never
|
|
// been used as a SourceTexture for this particular
|
|
// texture, creates a new SourceTextureImage and returns
|
|
// that.
|
|
////////////////////////////////////////////////////////////////////
|
|
SourceTextureImage *TextureImage::
|
|
get_source(const Filename &filename, const Filename &alpha_filename,
|
|
int alpha_file_channel) {
|
|
string key = get_source_key(filename, alpha_filename, alpha_file_channel);
|
|
|
|
Sources::iterator si;
|
|
si = _sources.find(key);
|
|
if (si != _sources.end()) {
|
|
return (*si).second;
|
|
}
|
|
|
|
SourceTextureImage *source =
|
|
new SourceTextureImage(this, filename, alpha_filename, alpha_file_channel);
|
|
_sources.insert(Sources::value_type(key, source));
|
|
|
|
// Clear out the preferred source image to force us to rederive this
|
|
// next time someone asks.
|
|
_preferred_source = (SourceTextureImage *)NULL;
|
|
_read_source_image = false;
|
|
|
|
return source;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_preferred_source
|
|
// Access: Public
|
|
// Description: Determines the preferred source image for examining
|
|
// size and reading pixels, etc. This is the largest
|
|
// and most recent of all the available source images.
|
|
////////////////////////////////////////////////////////////////////
|
|
SourceTextureImage *TextureImage::
|
|
get_preferred_source() {
|
|
if (_preferred_source != (SourceTextureImage *)NULL) {
|
|
return _preferred_source;
|
|
}
|
|
|
|
// Now examine all of the various source images available to us and
|
|
// pick the most suitable. We base this on the following criteria:
|
|
|
|
// (1) A suitable source image must be referenced by at least one
|
|
// egg file, unless no source images are referenced by any egg file.
|
|
|
|
// (2) A larger source image is preferable to a smaller one.
|
|
|
|
// (3) Given two source images of the same size, the more recent one
|
|
// is preferable.
|
|
|
|
// Are any source images referenced by an egg file?
|
|
|
|
bool any_referenced = false;
|
|
Sources::iterator si;
|
|
for (si = _sources.begin(); si != _sources.end() && !any_referenced; ++si) {
|
|
SourceTextureImage *source = (*si).second;
|
|
if (source->get_egg_count() > 0) {
|
|
any_referenced = true;
|
|
}
|
|
}
|
|
|
|
SourceTextureImage *best = (SourceTextureImage *)NULL;
|
|
int best_size = 0;
|
|
|
|
for (si = _sources.begin(); si != _sources.end(); ++si) {
|
|
SourceTextureImage *source = (*si).second;
|
|
|
|
if (source->get_egg_count() > 0 || !any_referenced) {
|
|
// Rule (1) passes.
|
|
|
|
if (source->exists() && source->get_size()) {
|
|
int source_size = source->get_x_size() * source->get_y_size();
|
|
if (best == (SourceTextureImage *)NULL) {
|
|
best = source;
|
|
best_size = source_size;
|
|
|
|
} else if (source_size > best_size) {
|
|
// Rule (2) passes.
|
|
best = source;
|
|
best_size = source_size;
|
|
|
|
} else if (source_size == best_size &&
|
|
source->get_filename().compare_timestamps(best->get_filename()) > 0) {
|
|
// Rule (3) passes.
|
|
best = source;
|
|
best_size = source_size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (best == (SourceTextureImage *)NULL && !_sources.empty()) {
|
|
// If we didn't pick any that pass, it must be that all of them
|
|
// are unreadable. In this case, it really doesn't matter which
|
|
// one we pick, but we should at least pick one that has an egg
|
|
// reference, if any of them do.
|
|
if (any_referenced) {
|
|
for (si = _sources.begin();
|
|
si != _sources.end() && best == (SourceTextureImage *)NULL;
|
|
++si) {
|
|
SourceTextureImage *source = (*si).second;
|
|
if (source->get_egg_count() > 0) {
|
|
best = source;
|
|
}
|
|
}
|
|
} else {
|
|
best = (*_sources.begin()).second;
|
|
}
|
|
}
|
|
|
|
_preferred_source = best;
|
|
return _preferred_source;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::clear_source_basic_properties
|
|
// Access: Public
|
|
// Description: Calls clear_basic_properties() on each source texture
|
|
// image used by this texture, to reset the properties
|
|
// in preparation for re-applying them from the set of
|
|
// all known egg files.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
clear_source_basic_properties() {
|
|
Sources::iterator si;
|
|
for (si = _sources.begin(); si != _sources.end(); ++si) {
|
|
SourceTextureImage *source = (*si).second;
|
|
source->clear_basic_properties();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::copy_unplaced
|
|
// Access: Public
|
|
// Description: Copies the texture to whichever destination
|
|
// directories are appropriate for the groups in which
|
|
// it has been unplaced. Also removes the old filenames
|
|
// for previous sessions where it was unplaced, but is
|
|
// no longer.
|
|
//
|
|
// If redo_all is true, this recopies the texture
|
|
// whether it needed to or not.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
copy_unplaced(bool redo_all) {
|
|
// First, we need to build up the set of DestTextureImages that
|
|
// represents the files we need to generate.
|
|
Dests generate;
|
|
|
|
// Go through all the TexturePlacements and note the ones for which
|
|
// we're unplaced. We check get_omit_reason() and not is_placed(),
|
|
// because we want to consider solitary images to be unplaced in
|
|
// this case.
|
|
Placement::iterator pi;
|
|
for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
|
|
TexturePlacement *placement = (*pi).second;
|
|
if (placement->get_omit_reason() != OR_none &&
|
|
placement->get_omit_reason() != OR_unknown) {
|
|
DestTextureImage *dest = new DestTextureImage(placement);
|
|
Filename filename = dest->get_filename();
|
|
FilenameUnifier::make_canonical(filename);
|
|
|
|
pair<Dests::iterator, bool> insert_result = generate.insert
|
|
(Dests::value_type(filename, dest));
|
|
if (!insert_result.second) {
|
|
// At least two DestTextureImages map to the same filename, no
|
|
// sweat.
|
|
delete dest;
|
|
dest = (*insert_result.first).second;
|
|
}
|
|
|
|
placement->set_dest(dest);
|
|
|
|
} else {
|
|
placement->set_dest((DestTextureImage *)NULL);
|
|
}
|
|
}
|
|
|
|
if (redo_all) {
|
|
// If we're redoing everything, we remove everything first and
|
|
// then recopy it again.
|
|
Dests empty;
|
|
remove_old_dests(empty, _dests);
|
|
copy_new_dests(generate, empty);
|
|
|
|
} else {
|
|
// Otherwise, we only remove and recopy the things that changed
|
|
// between this time and last time.
|
|
remove_old_dests(generate, _dests);
|
|
copy_new_dests(generate, _dests);
|
|
}
|
|
|
|
// Clean up the old set.
|
|
Dests::iterator di;
|
|
for (di = _dests.begin(); di != _dests.end(); ++di) {
|
|
delete (*di).second;
|
|
}
|
|
|
|
_dests.swap(generate);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::read_source_image
|
|
// Access: Public
|
|
// Description: Reads in the original image, if it has not already
|
|
// been read, and returns it.
|
|
////////////////////////////////////////////////////////////////////
|
|
const PNMImage &TextureImage::
|
|
read_source_image() {
|
|
if (!_read_source_image) {
|
|
SourceTextureImage *source = get_preferred_source();
|
|
if (source != (SourceTextureImage *)NULL) {
|
|
source->read(_source_image);
|
|
}
|
|
_read_source_image = true;
|
|
_allow_release_source_image = true;
|
|
_ever_read_image = true;
|
|
}
|
|
|
|
return _source_image;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::release_source_image
|
|
// Access: Public
|
|
// Description: Frees the memory that was allocated by a previous
|
|
// call to read_source_image(). The next time
|
|
// read_source_image() is called, it will have to read
|
|
// the disk again.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
release_source_image() {
|
|
if (_read_source_image && _allow_release_source_image) {
|
|
_source_image.clear();
|
|
_read_source_image = false;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::set_source_image
|
|
// Access: Public
|
|
// Description: Accepts the indicated source image as if it had been
|
|
// read from disk. This image is copied into the
|
|
// structure, and will be returned by future calls to
|
|
// read_source_image().
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
set_source_image(const PNMImage &image) {
|
|
_source_image = image;
|
|
_allow_release_source_image = false;
|
|
_read_source_image = true;
|
|
_ever_read_image = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::read_header
|
|
// Access: Public
|
|
// Description: Causes the header part of the image to be reread,
|
|
// usually to confirm that its image properties (size,
|
|
// number of channels, etc.) haven't changed.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
read_header() {
|
|
if (!_read_source_image) {
|
|
SourceTextureImage *source = get_preferred_source();
|
|
if (source != (SourceTextureImage *)NULL) {
|
|
source->read_header();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::is_newer_than
|
|
// Access: Public
|
|
// Description: Returns true if the source image is newer than the
|
|
// indicated file, false otherwise. If the image has
|
|
// already been read, this always returns false.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextureImage::
|
|
is_newer_than(const Filename &reference_filename) {
|
|
if (!_read_source_image) {
|
|
SourceTextureImage *source = get_preferred_source();
|
|
if (source != (SourceTextureImage *)NULL) {
|
|
const Filename &source_filename = source->get_filename();
|
|
return source_filename.compare_timestamps(reference_filename) >= 0;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::write_source_pathnames
|
|
// Access: Public
|
|
// Description: Writes the list of source pathnames that might
|
|
// contribute to this texture to the indicated output
|
|
// stream, one per line.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
write_source_pathnames(ostream &out, int indent_level) const {
|
|
Sources::const_iterator si;
|
|
for (si = _sources.begin(); si != _sources.end(); ++si) {
|
|
SourceTextureImage *source = (*si).second;
|
|
|
|
if (source->get_egg_count() > 0) {
|
|
indent(out, indent_level);
|
|
source->output_filename(out);
|
|
if (!source->is_size_known()) {
|
|
out << " (unknown size)";
|
|
|
|
} else {
|
|
out << " " << source->get_x_size() << " "
|
|
<< source->get_y_size();
|
|
|
|
if (source->get_properties().has_num_channels()) {
|
|
out << " " << source->get_properties().get_num_channels();
|
|
}
|
|
}
|
|
out << "\n";
|
|
}
|
|
}
|
|
|
|
if (_is_cutout) {
|
|
indent(out, indent_level)
|
|
<< "Cutout image (ratio " << _mid_pixel_ratio << ")";
|
|
}
|
|
|
|
// Now write out the group assignments.
|
|
if (!_egg_files.empty()) {
|
|
// Sort the egg files into order by name for output.
|
|
pvector<EggFile *> egg_vector;
|
|
egg_vector.reserve(_egg_files.size());
|
|
EggFiles::const_iterator ei;
|
|
for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
|
|
egg_vector.push_back(*ei);
|
|
}
|
|
sort(egg_vector.begin(), egg_vector.end(),
|
|
IndirectCompareNames<EggFile>());
|
|
|
|
indent(out, indent_level)
|
|
<< "Used by:\n";
|
|
pvector<EggFile *>::const_iterator evi;
|
|
for (evi = egg_vector.begin(); evi != egg_vector.end(); ++evi) {
|
|
EggFile *egg = (*evi);
|
|
indent(out, indent_level + 2)
|
|
<< egg->get_name() << " (";
|
|
if (egg->get_explicit_groups().empty()) {
|
|
out << *egg->get_default_group();
|
|
} else {
|
|
out << egg->get_explicit_groups();
|
|
}
|
|
out << ")\n";
|
|
}
|
|
}
|
|
if (!_explicitly_assigned_groups.empty()) {
|
|
indent(out, indent_level)
|
|
<< "Explicitly assigned to " << _explicitly_assigned_groups << " in .txa\n";
|
|
}
|
|
|
|
if (_placement.empty()) {
|
|
indent(out, indent_level)
|
|
<< "Not used.\n";
|
|
} else {
|
|
indent(out, indent_level)
|
|
<< "Assigned to " << _actual_assigned_groups << "\n";
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::write_scale_info
|
|
// Access: Public
|
|
// Description: Writes the information about the texture's size and
|
|
// placement.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
write_scale_info(ostream &out, int indent_level) {
|
|
SourceTextureImage *source = get_preferred_source();
|
|
indent(out, indent_level) << get_name();
|
|
|
|
// Write the list of groups we're placed in.
|
|
if (_placement.empty()) {
|
|
out << " (not used)";
|
|
} else {
|
|
Placement::const_iterator pi;
|
|
pi = _placement.begin();
|
|
out << " (" << (*pi).second->get_group()->get_name();
|
|
++pi;
|
|
while (pi != _placement.end()) {
|
|
out << " " << (*pi).second->get_group()->get_name();
|
|
++pi;
|
|
}
|
|
out << ")";
|
|
}
|
|
|
|
out << " orig ";
|
|
|
|
if (source == (SourceTextureImage *)NULL ||
|
|
!source->is_size_known()) {
|
|
out << "unknown";
|
|
} else {
|
|
out << source->get_x_size() << " " << source->get_y_size()
|
|
<< " " << source->get_num_channels();
|
|
}
|
|
|
|
if (!_placement.empty() && is_size_known()) {
|
|
out << " new " << get_x_size() << " " << get_y_size()
|
|
<< " " << get_num_channels();
|
|
|
|
if (source != (SourceTextureImage *)NULL &&
|
|
source->is_size_known()) {
|
|
double scale =
|
|
100.0 * (((double)get_x_size() / (double)source->get_x_size()) +
|
|
((double)get_y_size() / (double)source->get_y_size())) / 2.0;
|
|
out << " scale " << scale << "%";
|
|
}
|
|
}
|
|
out << "\n";
|
|
|
|
// Also cross-reference the placed and unplaced information.
|
|
Placement::iterator pi;
|
|
for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
|
|
TexturePlacement *placement = (*pi).second;
|
|
if (placement->get_omit_reason() == OR_none) {
|
|
PaletteImage *image = placement->get_image();
|
|
nassertv(image != (PaletteImage *)NULL);
|
|
indent(out, indent_level + 2)
|
|
<< "placed on "
|
|
<< FilenameUnifier::make_user_filename(image->get_filename())
|
|
<< "\n";
|
|
|
|
} else if (placement->get_omit_reason() == OR_unknown) {
|
|
indent(out, indent_level + 2)
|
|
<< "not placed because unknown.\n";
|
|
|
|
} else {
|
|
DestTextureImage *image = placement->get_dest();
|
|
nassertv(image != (DestTextureImage *)NULL);
|
|
indent(out, indent_level + 2)
|
|
<< "copied to "
|
|
<< FilenameUnifier::make_user_filename(image->get_filename());
|
|
if (image->is_size_known() && is_size_known() &&
|
|
(image->get_x_size() != get_x_size() ||
|
|
image->get_y_size() != get_y_size())) {
|
|
out << " at size " << image->get_x_size() << " "
|
|
<< image->get_y_size();
|
|
if (source != (SourceTextureImage *)NULL &&
|
|
source->is_size_known()) {
|
|
double scale =
|
|
100.0 * (((double)image->get_x_size() / (double)source->get_x_size()) +
|
|
((double)image->get_y_size() / (double)source->get_y_size())) / 2.0;
|
|
out << " scale " << scale << "%";
|
|
}
|
|
}
|
|
out << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::compute_egg_count
|
|
// Access: Private
|
|
// Description: Counts the number of egg files in the indicated set
|
|
// that will be satisfied if a texture is assigned to
|
|
// the indicated group.
|
|
////////////////////////////////////////////////////////////////////
|
|
int TextureImage::
|
|
compute_egg_count(PaletteGroup *group,
|
|
const TextureImage::WorkingEggs &egg_files) {
|
|
int count = 0;
|
|
|
|
WorkingEggs::const_iterator ei;
|
|
for (ei = egg_files.begin(); ei != egg_files.end(); ++ei) {
|
|
if ((*ei)->get_complete_groups().count(group) != 0) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::assign_to_groups
|
|
// Access: Private
|
|
// Description: Assigns the texture to the indicated set of groups.
|
|
// If the texture was previously assigned to any of
|
|
// these groups, keeps the same TexturePlacement object
|
|
// for the assignment; at the same time, deletes any
|
|
// TexturePlacement objects that represent groups we are
|
|
// no longer assigned to.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
assign_to_groups(const PaletteGroups &groups) {
|
|
PaletteGroups::const_iterator gi;
|
|
Placement::const_iterator pi;
|
|
|
|
Placement new_placement;
|
|
|
|
gi = groups.begin();
|
|
pi = _placement.begin();
|
|
|
|
while (gi != groups.end() && pi != _placement.end()) {
|
|
PaletteGroup *a = (*gi);
|
|
PaletteGroup *b = (*pi).first;
|
|
|
|
if (a < b) {
|
|
// Here's a group we're now assigned to that we weren't assigned
|
|
// to previously.
|
|
TexturePlacement *place = a->prepare(this);
|
|
new_placement.insert
|
|
(new_placement.end(), Placement::value_type(a, place));
|
|
++gi;
|
|
|
|
} else if (b < a) {
|
|
// Here's a group we're no longer assigned to.
|
|
TexturePlacement *place = (*pi).second;
|
|
delete place;
|
|
++pi;
|
|
|
|
} else { // b == a
|
|
// Here's a group we're still assigned to.
|
|
TexturePlacement *place = (*pi).second;
|
|
new_placement.insert
|
|
(new_placement.end(), Placement::value_type(a, place));
|
|
++gi;
|
|
++pi;
|
|
}
|
|
}
|
|
|
|
while (gi != groups.end()) {
|
|
// Here's a group we're now assigned to that we weren't assigned
|
|
// to previously.
|
|
PaletteGroup *a = (*gi);
|
|
TexturePlacement *place = a->prepare(this);
|
|
new_placement.insert
|
|
(new_placement.end(), Placement::value_type(a, place));
|
|
++gi;
|
|
}
|
|
|
|
while (pi != _placement.end()) {
|
|
// Here's a group we're no longer assigned to.
|
|
TexturePlacement *place = (*pi).second;
|
|
delete place;
|
|
++pi;
|
|
}
|
|
|
|
_placement.swap(new_placement);
|
|
_actual_assigned_groups = groups;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::consider_grayscale
|
|
// Access: Private
|
|
// Description: Examines the actual contents of the image to
|
|
// determine if it should maybe be considered a
|
|
// grayscale image (even though it has separate rgb
|
|
// components).
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
consider_grayscale() {
|
|
// Since this isn't likely to change for a particular texture after
|
|
// its creation, we save a bit of time by not performing this check
|
|
// unless this is the first time we've ever seen this texture. This
|
|
// will save us from having to load the texture images each time we
|
|
// look at them. On the other hand, if we've already loaded up the
|
|
// image, then go ahead.
|
|
if (!_read_source_image && _ever_read_image) {
|
|
if (_forced_grayscale) {
|
|
_properties.force_grayscale();
|
|
}
|
|
return;
|
|
}
|
|
|
|
const PNMImage &source = read_source_image();
|
|
if (!source.is_valid()) {
|
|
return;
|
|
}
|
|
|
|
for (int y = 0; y < source.get_y_size(); y++) {
|
|
for (int x = 0; x < source.get_x_size(); x++) {
|
|
const xel &v = source.get_xel_val(x, y);
|
|
if (PPM_GETR(v) != PPM_GETG(v) || PPM_GETR(v) != PPM_GETB(v)) {
|
|
// Here's a colored pixel. We can't go grayscale.
|
|
_forced_grayscale = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// All pixels in the image were grayscale!
|
|
_properties.force_grayscale();
|
|
_forced_grayscale = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::consider_alpha
|
|
// Access: Private
|
|
// Description: Examines the actual contents of the image to
|
|
// determine what alpha properties it has.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
consider_alpha() {
|
|
// As above, we don't bother doing this if we've already done this
|
|
// in a previous session.
|
|
|
|
// _alpha_bits == -1 indicates we have read an older textures.boo
|
|
// file that didn't define these bits.
|
|
if (_read_source_image || !_ever_read_image || _alpha_bits == -1) {
|
|
_alpha_bits = 0;
|
|
int num_mid_pixels = 0;
|
|
|
|
const PNMImage &source = read_source_image();
|
|
if (source.is_valid() && source.has_alpha()) {
|
|
xelval maxval = source.get_maxval();
|
|
for (int y = 0; y < source.get_y_size(); y++) {
|
|
for (int x = 0; x < source.get_x_size(); x++) {
|
|
xelval alpha_val = source.get_alpha_val(x, y);
|
|
if (alpha_val == 0) {
|
|
_alpha_bits |= AB_zero;
|
|
} else if (alpha_val == maxval) {
|
|
_alpha_bits |= AB_one;
|
|
} else {
|
|
_alpha_bits |= AB_mid;
|
|
++num_mid_pixels;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_mid_pixel_ratio = (double)num_mid_pixels / (double)(source.get_x_size() * source.get_y_size());
|
|
}
|
|
|
|
_is_cutout = false;
|
|
|
|
if (_alpha_bits != 0) {
|
|
if (_alpha_bits == AB_one) {
|
|
// All alpha pixels are white; drop the alpha channel.
|
|
_properties.force_nonalpha();
|
|
|
|
} else if (_alpha_bits == AB_zero) {
|
|
// All alpha pixels are invisible; this is probably a mistake.
|
|
// Drop the alpha channel and complain.
|
|
_properties.force_nonalpha();
|
|
if (_read_source_image) {
|
|
nout << *this << " has an all-zero alpha channel; dropping alpha.\n";
|
|
}
|
|
|
|
} else if (_alpha_mode == EggRenderMode::AM_unspecified) {
|
|
// Consider fiddling with the alpha mode, if the user hasn't
|
|
// specified a particular alpha mode in the txa file.
|
|
if ((_alpha_bits & AB_mid) == 0) {
|
|
// No middle range bits: a binary alpha image.
|
|
_alpha_mode = EggRenderMode::AM_binary;
|
|
|
|
} else if ((_alpha_bits & AB_one) != 0 && _mid_pixel_ratio < pal->_cutout_ratio) {
|
|
// At least some opaque bits, and relatively few middle range
|
|
// bits: a cutout image.
|
|
_alpha_mode = pal->_cutout_mode;
|
|
_is_cutout = true;
|
|
|
|
} else {
|
|
// No opaque bits; just use regular alpha blending.
|
|
_alpha_mode = EggRenderMode::AM_blend;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::remove_old_dests
|
|
// Access: Private
|
|
// Description: Removes all of the filenames named in b that are not
|
|
// also named in a.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
remove_old_dests(const TextureImage::Dests &a, const TextureImage::Dests &b) {
|
|
Dests::const_iterator ai = a.begin();
|
|
Dests::const_iterator bi = b.begin();
|
|
|
|
while (ai != a.end() && bi != b.end()) {
|
|
const string &astr = (*ai).first;
|
|
const string &bstr = (*bi).first;
|
|
|
|
if (astr < bstr) {
|
|
// Here's a filename in a, not in b.
|
|
++ai;
|
|
|
|
} else if (bstr < astr) {
|
|
// Here's a filename in b, not in a.
|
|
(*bi).second->unlink();
|
|
++bi;
|
|
|
|
} else { // bstr == astr
|
|
// Here's a filename in both a and b.
|
|
++ai;
|
|
++bi;
|
|
}
|
|
}
|
|
|
|
while (bi != b.end()) {
|
|
// Here's a filename in b, not in a.
|
|
(*bi).second->unlink();
|
|
++bi;
|
|
}
|
|
|
|
while (ai != a.end()) {
|
|
++ai;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::copy_new_dests
|
|
// Access: Private
|
|
// Description: Copies a resized texture into each filename named in
|
|
// a that is not also listed in b, or whose
|
|
// corresponding listing in b is out of date.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
copy_new_dests(const TextureImage::Dests &a, const TextureImage::Dests &b) {
|
|
Dests::const_iterator ai = a.begin();
|
|
Dests::const_iterator bi = b.begin();
|
|
|
|
while (ai != a.end() && bi != b.end()) {
|
|
const string &astr = (*ai).first;
|
|
const string &bstr = (*bi).first;
|
|
|
|
if (astr < bstr) {
|
|
// Here's a filename in a, not in b.
|
|
(*ai).second->copy(this);
|
|
++ai;
|
|
|
|
} else if (bstr < astr) {
|
|
// Here's a filename in b, not in a.
|
|
++bi;
|
|
|
|
} else { // bstr == astr
|
|
// Here's a filename in both a and b.
|
|
(*ai).second->copy_if_stale((*bi).second, this);
|
|
++ai;
|
|
++bi;
|
|
}
|
|
}
|
|
|
|
while (ai != a.end()) {
|
|
// Here's a filename in a, not in b.
|
|
(*ai).second->copy(this);
|
|
++ai;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::get_source_key
|
|
// Access: Private
|
|
// Description: Returns the key that a SourceTextureImage should be
|
|
// stored in, given its one or two filenames.
|
|
////////////////////////////////////////////////////////////////////
|
|
string TextureImage::
|
|
get_source_key(const Filename &filename, const Filename &alpha_filename,
|
|
int alpha_file_channel) {
|
|
Filename f = FilenameUnifier::make_bam_filename(filename);
|
|
Filename a = FilenameUnifier::make_bam_filename(alpha_filename);
|
|
|
|
return f.get_fullpath() + ":" + a.get_fullpath() + ":" +
|
|
format_string(alpha_file_channel);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::register_with_read_factory
|
|
// Access: Public, Static
|
|
// Description: Registers the current object as something that can be
|
|
// read from a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
register_with_read_factory() {
|
|
BamReader::get_factory()->
|
|
register_factory(get_class_type(), make_TextureImage);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::write_datagram
|
|
// Access: Public, Virtual
|
|
// Description: Fills the indicated datagram up with a binary
|
|
// representation of the current object, in preparation
|
|
// for writing to a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
write_datagram(BamWriter *writer, Datagram &datagram) {
|
|
ImageFile::write_datagram(writer, datagram);
|
|
datagram.add_string(get_name());
|
|
|
|
// We don't write out _request; this is re-read from the .txa file
|
|
// each time.
|
|
|
|
// We don't write out _pre_txa_properties; this is transitional.
|
|
|
|
// We don't write out _preferred_source; this is redetermined each
|
|
// session.
|
|
|
|
datagram.add_bool(_is_surprise);
|
|
datagram.add_bool(_ever_read_image);
|
|
datagram.add_bool(_forced_grayscale);
|
|
datagram.add_uint8(_alpha_bits);
|
|
datagram.add_int16((int)_alpha_mode);
|
|
datagram.add_float64(_mid_pixel_ratio);
|
|
datagram.add_bool(_is_cutout);
|
|
datagram.add_uint8((int)_txa_wrap_u);
|
|
datagram.add_uint8((int)_txa_wrap_v);
|
|
|
|
// We don't write out _explicitly_assigned_groups; this is re-read
|
|
// from the .txa file each time.
|
|
|
|
_actual_assigned_groups.write_datagram(writer, datagram);
|
|
|
|
// We don't write out _egg_files; this is redetermined each session.
|
|
|
|
datagram.add_uint32(_placement.size());
|
|
Placement::const_iterator pi;
|
|
for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
|
|
writer->write_pointer(datagram, (*pi).first);
|
|
writer->write_pointer(datagram, (*pi).second);
|
|
}
|
|
|
|
datagram.add_uint32(_sources.size());
|
|
Sources::const_iterator si;
|
|
for (si = _sources.begin(); si != _sources.end(); ++si) {
|
|
writer->write_pointer(datagram, (*si).second);
|
|
}
|
|
|
|
datagram.add_uint32(_dests.size());
|
|
Dests::const_iterator di;
|
|
for (di = _dests.begin(); di != _dests.end(); ++di) {
|
|
writer->write_pointer(datagram, (*di).second);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::complete_pointers
|
|
// Access: Public, Virtual
|
|
// Description: Called after the object is otherwise completely read
|
|
// from a Bam file, this function's job is to store the
|
|
// pointers that were retrieved from the Bam file for
|
|
// each pointer object written. The return value is the
|
|
// number of pointers processed from the list.
|
|
////////////////////////////////////////////////////////////////////
|
|
int TextureImage::
|
|
complete_pointers(TypedWritable **p_list, BamReader *manager) {
|
|
int pi = ImageFile::complete_pointers(p_list, manager);
|
|
|
|
pi += _actual_assigned_groups.complete_pointers(p_list + pi, manager);
|
|
|
|
int i;
|
|
for (i = 0; i < _num_placement; i++) {
|
|
PaletteGroup *group;
|
|
TexturePlacement *placement;
|
|
DCAST_INTO_R(group, p_list[pi++], pi);
|
|
DCAST_INTO_R(placement, p_list[pi++], pi);
|
|
_placement.insert(Placement::value_type(group, placement));
|
|
}
|
|
|
|
for (i = 0; i < _num_sources; i++) {
|
|
SourceTextureImage *source;
|
|
DCAST_INTO_R(source, p_list[pi++], pi);
|
|
string key = get_source_key(source->get_filename(),
|
|
source->get_alpha_filename(),
|
|
source->get_alpha_file_channel());
|
|
|
|
bool inserted = _sources.insert(Sources::value_type(key, source)).second;
|
|
if (!inserted) {
|
|
nout << "Warning: texture key " << key
|
|
<< " is nonunique; texture lost.\n";
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < _num_dests; i++) {
|
|
DestTextureImage *dest;
|
|
DCAST_INTO_R(dest, p_list[pi++], pi);
|
|
bool inserted = _dests.insert(Dests::value_type(dest->get_filename(), dest)).second;
|
|
if (!inserted) {
|
|
nout << "Warning: dest filename " << dest->get_filename()
|
|
<< " is nonunique; texture lost.\n";
|
|
}
|
|
}
|
|
|
|
return pi;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::make_TextureImage
|
|
// Access: Protected
|
|
// Description: This method is called by the BamReader when an object
|
|
// of this type is encountered in a Bam file; it should
|
|
// allocate and return a new object with all the data
|
|
// read.
|
|
////////////////////////////////////////////////////////////////////
|
|
TypedWritable *TextureImage::
|
|
make_TextureImage(const FactoryParams ¶ms) {
|
|
TextureImage *me = new TextureImage;
|
|
DatagramIterator scan;
|
|
BamReader *manager;
|
|
|
|
parse_params(params, scan, manager);
|
|
me->fillin(scan, manager);
|
|
return me;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextureImage::fillin
|
|
// Access: Protected
|
|
// Description: Reads the binary data from the given datagram
|
|
// iterator, which was written by a previous call to
|
|
// write_datagram().
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextureImage::
|
|
fillin(DatagramIterator &scan, BamReader *manager) {
|
|
ImageFile::fillin(scan, manager);
|
|
set_name(scan.get_string());
|
|
|
|
_is_surprise = scan.get_bool();
|
|
_ever_read_image = scan.get_bool();
|
|
_forced_grayscale = scan.get_bool();
|
|
_alpha_bits = scan.get_uint8();
|
|
_alpha_mode = (EggRenderMode::AlphaMode)scan.get_int16();
|
|
if (pal->_read_pi_version >= 16) {
|
|
_mid_pixel_ratio = scan.get_float64();
|
|
_is_cutout = scan.get_bool();
|
|
} else {
|
|
// Force a re-read of the image if we are upgrading to pi version 16.
|
|
_ever_read_image = false;
|
|
_mid_pixel_ratio = 0.0;
|
|
_is_cutout = false;
|
|
}
|
|
if (pal->_read_pi_version >= 17) {
|
|
_txa_wrap_u = (EggTexture::WrapMode)scan.get_uint8();
|
|
_txa_wrap_v = (EggTexture::WrapMode)scan.get_uint8();
|
|
}
|
|
|
|
_actual_assigned_groups.fillin(scan, manager);
|
|
|
|
_num_placement = scan.get_uint32();
|
|
manager->read_pointers(scan, _num_placement * 2);
|
|
|
|
_num_sources = scan.get_uint32();
|
|
manager->read_pointers(scan, _num_sources);
|
|
_num_dests = scan.get_uint32();
|
|
manager->read_pointers(scan, _num_dests);
|
|
}
|