From Jason Daly, added support for reading Valve's texture format files

This commit is contained in:
Robert Osfield
2008-12-04 10:27:29 +00:00
parent fe439b3707
commit 25eff6e27c
2 changed files with 713 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
SET(TARGET_SRC
ReaderWriterBSP.cpp
ReaderWriterVTF.cpp
BITSET.cpp
Q3BSPReader.cpp
Q3BSPLoad.cpp

View File

@@ -0,0 +1,712 @@
/**********************************************************************
*
* FILE: ReaderWriterVTF.cpp
*
* DESCRIPTION: Class for reading a Valve Texture Format (VTF) file
* into an osg::Image.
*
* Borrows heavily from Rune Schmidt Jensen's DDS
* plugin for OSG, as well as the Valve Source SDK
*
* CREATED BY: Jason Daly (jdaly@ist.ucf.edu)
*
* HISTORY: Created 27.10.2008
*
**********************************************************************/
#include <osg/Texture>
#include <osg/Notify>
#include <osgDB/Registry>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <iomanip>
#include <stdio.h>
enum VTFFlags
{
VTF_FLAGS_POINTSAMPLE = 0x00000001,
VTF_FLAGS_TRILINEAR = 0x00000002,
VTF_FLAGS_CLAMP_S = 0x00000004,
VTF_FLAGS_CLAMP_T = 0x00000008,
VTF_FLAGS_ANISOTROPIC = 0x00000010,
VTF_FLAGS_HINT_DXT5 = 0x00000020,
VTF_FLAGS_NOCOMPRESS = 0x00000040,
VTF_FLAGS_NORMAL = 0x00000080,
VTF_FLAGS_NOMIP = 0x00000100,
VTF_FLAGS_NOLOD = 0x00000200,
VTF_FLAGS_MINMIP = 0x00000400,
VTF_FLAGS_PROCEDURAL = 0x00000800,
VTF_FLAGS_ONEBITALPHA = 0x00001000,
VTF_FLAGS_EIGHTBITALPHA = 0x00002000,
VTF_FLAGS_ENVMAP = 0x00004000,
VTF_FLAGS_RENDERTARGET = 0x00008000,
VTF_FLAGS_DEPTHRENDERTARGET = 0x00010000,
VTF_FLAGS_NODEBUGOVERRIDE = 0x00020000,
VTF_FLAGS_SINGLECOPY = 0x00040000,
VTF_FLAGS_ONEOVERMIPLEVELINALPHA = 0x00080000,
VTF_FLAGS_PREMULTCOLORBYONEOVERMIPLEVEL = 0x00100000,
VTF_FLAGS_NORMALTODUDV = 0x00200000,
VTF_FLAGS_ALPHATESTMIPGENERATION = 0x00400000,
VTF_FLAGS_NODEPTHBUFFER = 0x00800000,
VTF_FLAGS_NICEFILTERED = 0x01000000,
VTF_FLAGS_CLAMP_U = 0x02000000,
VTF_FLAGS_PRESWIZZLED = 0x04000000,
VTF_FLAGS_CACHEABLE = 0x08000000,
VTF_FLAGS_UNFILTERABLE_OK = 0x10000000,
VTF_FLAGS_LASTFLAG = 0x10000000
};
enum VTFCubeMapFaceIndex
{
VTF_CUBEMAP_FACE_RIGHT = 0,
VTF_CUBEMAP_FACE_LEFT,
VTF_CUBEMAP_FACE_BACK,
VTF_CUBEMAP_FACE_FRONT,
VTF_CUBEMAP_FACE_UP,
VTF_CUBEMAP_FACE_DOWN,
VTF_CUBEMAP_FACE_SPHEREMAP,
VTF_CUBEMAP_FACE_COUNT
};
enum VTFLookDir
{
VTF_LOOK_DOWN_X = 0,
VTF_LOOK_DOWN_NEGX,
VTF_LOOK_DOWN_Y = 0,
VTF_LOOK_DOWN_NEGY,
VTF_LOOK_DOWN_Z = 0,
VTF_LOOK_DOWN_NEGZ
};
enum VTFImageFormat
{
VTF_FORMAT_UNKNOWN = -1,
VTF_FORMAT_RGBA8888 = 0,
VTF_FORMAT_ABGR8888,
VTF_FORMAT_RGB888,
VTF_FORMAT_BGR888,
VTF_FORMAT_RGB565,
VTF_FORMAT_I8,
VTF_FORMAT_IA88,
VTF_FORMAT_P8,
VTF_FORMAT_A8,
VTF_FORMAT_RGB888_BLUESCREEN,
VTF_FORMAT_BGR888_BLUESCREEN,
VTF_FORMAT_ARGB8888,
VTF_FORMAT_BGRA8888,
VTF_FORMAT_DXT1,
VTF_FORMAT_DXT3,
VTF_FORMAT_DXT5,
VTF_FORMAT_BGRX8888,
VTF_FORMAT_BGR565,
VTF_FORMAT_BGRX5551,
VTF_FORMAT_BGRA4444,
VTF_FORMAT_DXT1_ONEBITALPHA,
VTF_FORMAT_BGRA5551,
VTF_FORMAT_UV88,
VTF_FORMAT_UVWQ8888,
VTF_FORMAT_RGBA16161616F,
VTF_FORMAT_RGBA16161616,
VTF_FORMAT_UVLX8888,
VTF_FORMAT_R32F,
VTF_FORMAT_RGB323232F,
VTF_FORMAT_RGBA32323232F,
VTF_NUM_IMAGE_FORMATS
};
#define VTF_FORMAT_DEFAULT ((VTFImageFormat)-2)
struct VTFFileHeader
{
char magic_number[4];
unsigned int file_version[2];
unsigned int header_size;
unsigned short image_width;
unsigned short image_height;
unsigned int image_flags;
unsigned short num_frames;
unsigned short start_frame;
unsigned char padding_0[4];
osg::Vec3f reflectivity_value;
unsigned char padding_1[4];
float bump_scale;
unsigned int image_format;
unsigned char num_mip_levels;
unsigned char low_res_image_format;
unsigned char padding_2[3];
unsigned char low_res_image_width;
unsigned char low_res_image_height;
unsigned short image_depth;
};
//
// Structure of a DXT-1 compressed texture block
// see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/directx9_c/Opaque_and_1_Bit_Alpha_Textures.asp
//
struct DXT1TexelsBlock
{
unsigned short color_0; // colors at their
unsigned short color_1; // extreme
unsigned int texels4x4; // interpolated colors (2 bits per texel)
};
bool ConvertImageFormat(unsigned int vtfFormat, int& internalFormat,
int& pixelFormat, int& dataType)
{
bool supported;
// Assume a supported format to start
supported = true;
// Decode the format
switch (vtfFormat)
{
case VTF_FORMAT_DEFAULT:
supported = false;
break;
case VTF_FORMAT_UNKNOWN:
supported = false;
break;
case VTF_FORMAT_RGBA8888:
internalFormat = GL_RGBA;
pixelFormat = GL_RGBA;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_ABGR8888:
internalFormat = GL_RGBA;
pixelFormat = GL_ABGR_EXT;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_RGB888:
internalFormat = GL_RGB;
pixelFormat = GL_RGB;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_BGR888:
internalFormat = GL_RGB;
pixelFormat = GL_BGR;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_RGB565:
internalFormat = GL_RGB;
pixelFormat = GL_RGB;
dataType = GL_UNSIGNED_SHORT_5_6_5;
break;
case VTF_FORMAT_I8:
internalFormat = GL_LUMINANCE;
pixelFormat = GL_LUMINANCE;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_IA88:
internalFormat = GL_LUMINANCE_ALPHA;
pixelFormat = GL_LUMINANCE_ALPHA;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_P8:
// 8-bit paletted image, not supported
supported = false;
break;
case VTF_FORMAT_A8:
internalFormat = GL_ALPHA;
pixelFormat = GL_ALPHA;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_RGB888_BLUESCREEN:
// Ignore the "bluescreen" specification for now
internalFormat = GL_RGB;
pixelFormat = GL_RGB;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_BGR888_BLUESCREEN:
// Ignore the "bluescreen" specification for now
internalFormat = GL_RGB;
pixelFormat = GL_BGR;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_ARGB8888:
// ARGB not supported
supported = false;
break;
case VTF_FORMAT_BGRA8888:
internalFormat = GL_RGBA;
pixelFormat = GL_BGRA;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_DXT1:
internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
pixelFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_DXT3:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
pixelFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_DXT5:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
pixelFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_BGRX8888:
internalFormat = GL_RGB;
pixelFormat = GL_BGRA;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_BGR565:
internalFormat = GL_RGB;
pixelFormat = GL_BGR;
dataType = GL_UNSIGNED_SHORT_5_6_5_REV;
break;
case VTF_FORMAT_BGRX5551:
internalFormat = GL_RGB;
pixelFormat = GL_BGRA;
dataType = GL_UNSIGNED_SHORT_5_5_5_1;
break;
case VTF_FORMAT_BGRA4444:
internalFormat = GL_RGBA;
pixelFormat = GL_BGRA;
dataType = GL_UNSIGNED_SHORT_4_4_4_4;
break;
case VTF_FORMAT_DXT1_ONEBITALPHA:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
pixelFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
dataType = GL_UNSIGNED_BYTE;
break;
case VTF_FORMAT_BGRA5551:
internalFormat = GL_RGBA;
pixelFormat = GL_BGRA;
dataType = GL_UNSIGNED_SHORT_5_5_5_1;
break;
case VTF_FORMAT_UV88:
supported = false;
break;
case VTF_FORMAT_UVWQ8888:
supported = false;
break;
case VTF_FORMAT_RGBA16161616F:
internalFormat = GL_RGBA;
pixelFormat = GL_RGBA;
dataType = GL_HALF_FLOAT_NV;
break;
case VTF_FORMAT_RGBA16161616:
internalFormat = GL_RGBA;
pixelFormat = GL_RGBA;
dataType = GL_UNSIGNED_SHORT;
break;
case VTF_FORMAT_UVLX8888:
supported = false;
break;
default:
supported = false;
break;
}
// Return whether or not the format is supported
return supported;
}
osg::Image* ReadVTFFile(std::istream& _istream)
{
VTFFileHeader vtf_header;
bool supported;
int internalFormat;
int pixelFormat;
int dataType;
int s, t, r;
unsigned int lrSize;
unsigned char * imageData;
unsigned char * imageDataPtr;
unsigned int base;
unsigned int size;
int mip;
int mipSize;
int mipOffset;
// Validate the file with the 'VTF\0' magic number
_istream.read(&vtf_header.magic_number[0], 4);
if ((vtf_header.magic_number[0] != 'V') ||
(vtf_header.magic_number[1] != 'T') ||
(vtf_header.magic_number[2] != 'F') ||
(vtf_header.magic_number[3] != 0))
{
// Not a VTF file, so bail
osg::notify(osg::WARN) << "VTF file is invalid" << std::endl;
return NULL;
}
// Read next two fields of the header (which includes the header size)
_istream.read((char *)&vtf_header.file_version[0], 12);
// Now, read the rest of the header
_istream.read((char *)&vtf_header.image_width, vtf_header.header_size - 16);
// No depth in textures earlier than version 7.2
if ((vtf_header.file_version[0] < 7) ||
((vtf_header.file_version[0] == 7) &&
(vtf_header.file_version[1] < 2)))
vtf_header.image_depth = 1;
// Environment maps not supported
if (vtf_header.image_flags & VTF_FLAGS_ENVMAP)
{
osg::notify(osg::WARN) << "VTF Environment maps not supported";
osg::notify(osg::WARN) << std::endl;
return NULL;
}
// Before we get to the real image, we need to skip over the "low res"
// image that's often stored along with VTF textures, so get the
// low-res image dimensions
s = vtf_header.low_res_image_width;
t = vtf_header.low_res_image_height;
r = 1;
// See if the low-res image is there
lrSize = 0;
if ((s > 0) && (t > 0))
{
supported = ConvertImageFormat(vtf_header.low_res_image_format,
internalFormat, pixelFormat, dataType);
// If we don't recognize the format, we can't locate the real image
// in the file, so we have to bail
if (!supported)
{
osg::notify(osg::WARN) << "Low-res image format is not supported";
osg::notify(osg::WARN) << " (" << vtf_header.low_res_image_format;
osg::notify(osg::WARN) << ")" << std::endl;
return NULL;
}
// Allocate an osg::Image for the lo-res image metadata
osg::ref_ptr<osg::Image> loResImage = new osg::Image();
// Set the image metadata, and figure out how many bytes to read
loResImage->setImage(s, t, r, internalFormat, pixelFormat, dataType,
0, osg::Image::USE_NEW_DELETE);
lrSize = loResImage->getTotalSizeInBytes();
// Skip over the low-res image data
_istream.ignore(lrSize);
}
// Compute the base position of the high-res image data
base = vtf_header.header_size + lrSize;
// Now, get the internal format, pixel format, and data type from the
// full-size image format, and check whether the format is supported
supported = ConvertImageFormat(vtf_header.image_format, internalFormat,
pixelFormat, dataType);
// Bail if the format isn't supported
if (!supported)
{
osg::notify(osg::WARN) << "Image format is not supported (";
osg::notify(osg::WARN) << vtf_header.image_format << ")";
osg::notify(osg::WARN) << std::endl;
return NULL;
}
// Get the dimensions of the image
s = vtf_header.image_width;
t = vtf_header.image_height;
r = vtf_header.image_depth;
// VTF allows either 0 or 1 for 2D images
if (r == 0)
r = 1;
// NOTE: VTF supports animated textures and cube maps. Currently, we
// only handle a single frame of data, so multiple frames
// are ignored. Same for cube maps (only one face is loaded).
// Create the mipmap offsets vector
osg::Image::MipmapDataType mipmaps;
// Deal with mipmaps, if necessary
if (vtf_header.num_mip_levels > 1)
{
// Set up the offsets vector
float power2_s = logf((float)s)/logf((float)2);
float power2_t = logf((float)t)/logf((float)2);
mipmaps.resize((unsigned int)osg::maximum(power2_s,power2_t),0);
// Calculate the dimensions of each mipmap
if ((vtf_header.image_format == VTF_FORMAT_DXT1) ||
(vtf_header.image_format == VTF_FORMAT_DXT1_ONEBITALPHA) ||
(vtf_header.image_format == VTF_FORMAT_DXT3) ||
(vtf_header.image_format == VTF_FORMAT_DXT5))
{
// Handle S3TC compressed mipmaps
int width = vtf_header.image_width;
int height = vtf_header.image_height;
int blockSize;
if ((vtf_header.image_format == VTF_FORMAT_DXT1) ||
(vtf_header.image_format == VTF_FORMAT_DXT1_ONEBITALPHA))
blockSize = 8;
else
blockSize = 16;
int offset = 0;
for (unsigned int k = 1;
(k < vtf_header.num_mip_levels) && (width || height);
++k)
{
// Clamp dimensions to 1
if (width == 0)
width = 1;
if (height == 0)
height = 1;
// Compute and store the offset into the final image data
offset += (((width+3)/4) * ((height+3)/4) * blockSize);
mipmaps[k-1] = offset;
// Get the next level's dimensions
width >>= 1;
height >>= 1;
}
}
else
{
// Handle uncompressed mipmaps
int offset = 0;
int width = vtf_header.image_width;
int height = vtf_header.image_height;
int depth = vtf_header.image_depth;
for (unsigned int k = 1;
(k < vtf_header.num_mip_levels) && (width || height || depth);
++k)
{
if (width == 0)
width = 1;
if (height == 0)
height = 1;
if (depth == 0)
height = 1;
// Compute and store the offset into the final image data
offset += depth * height *
osg::Image::computeRowWidthInBytes(width, pixelFormat,
dataType, 1 );
mipmaps[k-1] = offset;
// Get the next level's dimensions
width >>= 1;
height >>= 1;
depth >>= 1;
}
}
}
// Allocate the resulting osg::Image
osg::ref_ptr<osg::Image> osgImage = new osg::Image();
// Set the image meta-data, including dimensions, format, data type,
// and mipmap levels. Everything but the image data itself. We'll use
// this to compute the total image size, so we know how much data to read
// from the file
osgImage->setImage(s, t, r, internalFormat, pixelFormat, dataType,
0, osg::Image::USE_NEW_DELETE);
if (mipmaps.size() > 0)
osgImage->setMipmapLevels(mipmaps);
// Compute the total image size
size = osgImage->getTotalSizeInBytesIncludingMipmaps();
if(size <= 0)
{
osg::notify(osg::WARN) << "ReadVTFFile warning: size <= 0" << std::endl;
return NULL;
}
// Prepare to read the image data
imageData = new unsigned char [size];
if(!imageData)
{
osg::notify(osg::WARN) << "ReadVTFFile warning: imageData == NULL";
osg::notify(osg::WARN) << std::endl;
return NULL;
}
// See if we have mipmaps
if (vtf_header.num_mip_levels > 1)
{
// VTF stores the mipmaps in reverse order from what OpenGL expects, so
// we need to read them from the file and store them in order in the
// image data array
for (mip = vtf_header.num_mip_levels - 2; mip >= 0; mip--)
{
// Look up the offset for this mip level
mipOffset = mipmaps[mip];
// Calculate the size of the mipmap
if (mip == vtf_header.num_mip_levels-2)
mipSize = size - mipOffset;
else
mipSize = mipmaps[mip+1] - mipOffset;
// Read the image data
_istream.read((char*)&imageData[mipOffset], mipSize);
}
// We've read all of the mipmaps except the largest (the original,
// image), so do that now
mipSize = mipmaps[1];
_istream.read((char*)imageData, mipSize);
}
else
{
// Just read the image data
_istream.read((char*)imageData, size);
}
// Now, set the actual image data and mipmap levels
osgImage->setImage(s,t,r, internalFormat, pixelFormat, dataType,
imageData, osg::Image::USE_NEW_DELETE);
if (mipmaps.size()>0) osgImage->setMipmapLevels(mipmaps);
// Finally, return the image
return osgImage.release();
}
class ReaderWriterVTF : public osgDB::ReaderWriter
{
public:
virtual const char* className() const
{
return "VTF Image Reader/Writer";
}
virtual bool acceptsExtension(const std::string& extension) const
{
return osgDB::equalCaseInsensitive(extension, "vtf");
}
virtual ReadResult readObject(
const std::string& file,
const osgDB::ReaderWriter::Options* options) const
{
return readImage(file,options);
}
virtual ReadResult readObject(std::istream& fin,
const Options* options) const
{
return readImage(fin,options);
}
virtual ReadResult readImage(
const std::string& file,
const osgDB::ReaderWriter::Options* options) const
{
std::string ext = osgDB::getLowerCaseFileExtension(file);
if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
std::string fileName = osgDB::findDataFile( file, options );
if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
osgDB::ifstream stream(fileName.c_str(),
std::ios::in | std::ios::binary);
if(!stream) return ReadResult::FILE_NOT_HANDLED;
ReadResult rr = readImage(stream, options);
if(rr.validImage()) rr.getImage()->setFileName(file);
return rr;
}
virtual ReadResult readImage(std::istream& fin,
const Options* options) const
{
osg::Image* osgImage = ReadVTFFile(fin);
if (osgImage==NULL) return ReadResult::FILE_NOT_HANDLED;
if (options &&
options->getOptionString().find("vtf_flip")!=std::string::npos)
{
osgImage->flipVertical();
}
return osgImage;
}
virtual WriteResult writeObject(
const osg::Object& object,
const std::string& file,
const osgDB::ReaderWriter::Options* options) const
{
const osg::Image* image = dynamic_cast<const osg::Image*>(&object);
if (!image) return WriteResult::FILE_NOT_HANDLED;
return writeImage(*image,file,options);
}
virtual WriteResult writeObject(const osg::Object& object,
std::ostream& fout,
const Options* options) const
{
return WriteResult::FILE_NOT_HANDLED;
}
virtual WriteResult writeImage(
const osg::Image &image,
const std::string& file,
const osgDB::ReaderWriter::Options* options) const
{
return WriteResult::FILE_NOT_HANDLED;
}
virtual WriteResult writeImage(const osg::Image& image,
std::ostream& fout,const Options*) const
{
return WriteResult::FILE_NOT_HANDLED;
}
};
// now register with Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(vtf, ReaderWriterVTF)