mirror of
https://github.com/id-Software/DOOM-iOS.git
synced 2024-11-10 06:31:53 +00:00
1716 lines
42 KiB
C
1716 lines
42 KiB
C
#include <libc.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
|
|
/*
|
|
|
|
store sprites in an atlas, or as discrete textures?
|
|
|
|
store audio as 16 bit for direct mmap access, or 4 bit adpcm with dynamic decompress?
|
|
|
|
render without a depth buffer? NO: it wold defeat the deferred rendering ability
|
|
|
|
optional script file with extra parameters
|
|
|
|
|
|
youtube script
|
|
--------------
|
|
ipod music
|
|
music icon
|
|
landscape orientation
|
|
skill level
|
|
start game on any episode / map combination
|
|
controls
|
|
doors opening
|
|
shooting
|
|
damage indicators
|
|
leave game at any time
|
|
custom controls
|
|
items in the world (ammo, health, treasure, weapons, keys)
|
|
changing weapons
|
|
secret doors
|
|
finishing the level
|
|
awards
|
|
|
|
|
|
|
|
TASKS:
|
|
Web page time check
|
|
Data download
|
|
Hardware mp3 playback, remove tremor
|
|
Independent volume adjustment
|
|
Broadcast packet tests
|
|
Good console
|
|
web media type for game launch and download
|
|
Instrumented play data recording
|
|
|
|
*/
|
|
|
|
#include <dirent.h>
|
|
#include <OpenGL/gl.h> // for repeat and filter enums
|
|
#include <OpenGL/glext.h>
|
|
typedef unsigned char byte;
|
|
|
|
#include "doomtool.h"
|
|
|
|
const char *assetDirectory = "/Volumes/Work/idMobileDepot/Archive/DoomClassicDepot/assets";
|
|
const char *outputFile = "/Volumes/Work/idMobileDepot/Archive/DoomClassicDepot/base.iPack";
|
|
const char *parmFile = "/Volumes/Work/idMobileDepot/Archive/DoomClassicDepot/base.parm";
|
|
|
|
pkHeader_t buildHeader;
|
|
FILE *pakFile;
|
|
#define MAX_IMAGE_TABLE 10000
|
|
pkTextureData_t buildTextureTable[MAX_IMAGE_TABLE];
|
|
#define MAX_WAV_TABLE 10000
|
|
pkWavData_t buildWavTable[MAX_WAV_TABLE];
|
|
#define MAX_RAW_TABLE 10000
|
|
pkRawData_t buildRawTable[MAX_RAW_TABLE];
|
|
|
|
// the doom extractor tool writes this out for alpha texels
|
|
#define DOOM_ALPHA_TEXEL 0xff00ffff
|
|
|
|
// the parm file is parsed for modifiers to specify image formats, etc
|
|
#define MAX_ARGV 16
|
|
typedef struct {
|
|
int argc;
|
|
char *argv[MAX_ARGV]; // argv[0] should be a filename local to the asset base
|
|
} parmLine_t;
|
|
|
|
#define MAX_PARM_LINES 10000
|
|
parmLine_t parmLines[MAX_PARM_LINES];
|
|
int numParmLines;
|
|
|
|
void Error( const char *fmt, ... ) {
|
|
va_list argptr;
|
|
va_start( argptr, fmt );
|
|
|
|
vprintf( fmt, argptr );
|
|
exit( 1 );
|
|
}
|
|
|
|
int FileLength( FILE *f ) {
|
|
fseek( f, 0, SEEK_END );
|
|
int len = ftell( f );
|
|
fseek( f, 0, SEEK_SET );
|
|
return len;
|
|
}
|
|
|
|
//====================================================================
|
|
|
|
const byte *iff_pdata;
|
|
const byte *iff_end;
|
|
const byte *iff_last_chunk;
|
|
const byte *iff_data;
|
|
int iff_chunk_len;
|
|
|
|
|
|
short Wav_GetLittleShort( void )
|
|
{
|
|
short val = 0;
|
|
|
|
val = *iff_pdata;
|
|
val += (*(iff_pdata + 1) << 8);
|
|
|
|
iff_pdata += 2;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
int Wav_GetLittleLong( void )
|
|
{
|
|
int val = 0;
|
|
|
|
val = *iff_pdata;
|
|
val += (*(iff_pdata + 1) << 8);
|
|
val += (*(iff_pdata + 2) << 16);
|
|
val += (*(iff_pdata + 3) << 24);
|
|
|
|
iff_pdata += 4;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
void Wav_FindNextChunk( const char *name )
|
|
{
|
|
while( 1 )
|
|
{
|
|
iff_pdata = iff_last_chunk;
|
|
|
|
if( iff_pdata >= iff_end )
|
|
{
|
|
// Didn't find the chunk
|
|
iff_pdata = NULL;
|
|
return;
|
|
}
|
|
|
|
iff_pdata += 4;
|
|
iff_chunk_len = Wav_GetLittleLong();
|
|
if( iff_chunk_len < 0 )
|
|
{
|
|
iff_pdata = NULL;
|
|
return;
|
|
}
|
|
|
|
iff_pdata -= 8;
|
|
iff_last_chunk = iff_pdata + 8 + ((iff_chunk_len + 1) & ~1);
|
|
if( ! strncasecmp((const char *)iff_pdata, name, 4) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Wav_FindChunk( const char *name )
|
|
{
|
|
iff_last_chunk = iff_data;
|
|
|
|
Wav_FindNextChunk( name );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
AddWAV
|
|
|
|
========================
|
|
*/
|
|
void AddWAV( const char *localName, const byte *data, int wavlength ) {
|
|
assert( buildHeader.wavs.count < MAX_WAV_TABLE );
|
|
pkWavData_t *wav = &buildWavTable[buildHeader.wavs.count++];
|
|
|
|
iff_data = data;
|
|
iff_end = data + wavlength;
|
|
|
|
// look for RIFF signature
|
|
Wav_FindChunk( "RIFF" );
|
|
if( ! (iff_pdata && ! strncasecmp( (const char *)iff_pdata + 8, "WAVE", 4 ) ) ) {
|
|
Error( "[LoadWavInfo]: Missing RIFF/WAVE chunks (%s)\n", localName );
|
|
}
|
|
|
|
// Get "fmt " chunk
|
|
iff_data = iff_pdata + 12;
|
|
|
|
Wav_FindChunk( "fmt " );
|
|
if( ! iff_pdata ) {
|
|
Error( "[LoadWavInfo]: Missing fmt chunk (%s)\n", localName );
|
|
}
|
|
|
|
iff_pdata += 8;
|
|
|
|
if( Wav_GetLittleShort() != 1 ) {
|
|
Error( "[LoadWavInfo]: Microsoft PCM format only (%s)\n", localName );
|
|
}
|
|
|
|
int channels = Wav_GetLittleShort();
|
|
int sample_rate = Wav_GetLittleLong();
|
|
|
|
iff_pdata += 4;
|
|
|
|
// bytes per sample, which includes all channels
|
|
// 16 bit stereo = 4 bytes per sample
|
|
int sample_size = Wav_GetLittleShort();
|
|
int channelBytes = sample_size / channels;
|
|
|
|
if ( channelBytes != 1 && channelBytes != 2 ) {
|
|
Error( "[LoadWavInfo]: only 8 and 16 bit WAV files supported (%s)\n", localName );
|
|
}
|
|
|
|
iff_pdata += 2;
|
|
|
|
// Find data chunk
|
|
Wav_FindChunk( "data" );
|
|
if( ! iff_pdata ) {
|
|
Error( "[LoadWavInfo]: missing 'data' chunk (%s)\n", localName );
|
|
}
|
|
|
|
iff_pdata += 4;
|
|
int numSamples = Wav_GetLittleLong() / sample_size;
|
|
|
|
if( numSamples <= 0 ) {
|
|
Error( "[LoadWavInfo]: file with 0 samples (%s)\n", localName );
|
|
}
|
|
|
|
// as of iphone OS 2.2.1, 8 bit samples cause audible pops at the beginning and end, so
|
|
// convert them to 16 bit here
|
|
const void *samples = data + (iff_pdata - data);
|
|
#if 0
|
|
if ( channelBytes == 1 ) {
|
|
int numChannelSamples = numSamples * channels;
|
|
channelBytes = 2;
|
|
sample_size = channelBytes * channels;
|
|
short *newSamples = alloca( numChannelSamples * sample_size );
|
|
for ( int i = 0; i < numChannelSamples ; i++ ) {
|
|
newSamples[i] = ((short)((const byte *)samples)[i] - 128) * 256;
|
|
}
|
|
samples = newSamples;
|
|
}
|
|
#endif
|
|
// write out the raw data
|
|
strcpy( wav->name.name, localName );
|
|
wav->wavDataOfs = ftell( pakFile );
|
|
fwrite( samples, numSamples, sample_size, pakFile );
|
|
wav->wavChannels = channels;
|
|
wav->wavChannelBytes = channelBytes;
|
|
wav->wavRate = sample_rate;
|
|
wav->wavNumSamples = numSamples;
|
|
}
|
|
|
|
|
|
/*
|
|
================================================================================================
|
|
|
|
Bitmap Loading (.bmp)
|
|
|
|
================================================================================================
|
|
*/
|
|
|
|
typedef struct {
|
|
char id[2];
|
|
unsigned int fileSize;
|
|
unsigned int reserved0;
|
|
unsigned int bitmapDataOffset;
|
|
unsigned int bitmapHeaderSize;
|
|
unsigned int width;
|
|
unsigned int height;
|
|
unsigned short planes;
|
|
unsigned short bitsPerPixel;
|
|
unsigned int compression;
|
|
unsigned int bitmapDataSize;
|
|
unsigned int hRes;
|
|
unsigned int vRes;
|
|
unsigned int colors;
|
|
unsigned int importantColors;
|
|
unsigned char palette[256][4];
|
|
} BMPHeader_t;
|
|
|
|
/*
|
|
========================
|
|
LoadBMP
|
|
========================
|
|
*/
|
|
static void LoadBMP( const char *name, byte **pic, int *width, int *height ) {
|
|
int columns, rows, numPixels;
|
|
byte *pixbuf;
|
|
int row, column;
|
|
byte *buf_p;
|
|
byte *buffer;
|
|
int length;
|
|
BMPHeader_t bmpHeader;
|
|
byte *bmpRGBA;
|
|
|
|
*pic = NULL;
|
|
|
|
//
|
|
// load the file
|
|
//
|
|
FILE *f = fopen( name, "rb" );
|
|
if ( !f ) {
|
|
Error( "Can't open '%s'\n", name );
|
|
}
|
|
length = FileLength( f );
|
|
buffer = malloc( length );
|
|
fread( buffer, 1, length, f );
|
|
fclose( f );
|
|
|
|
buf_p = buffer;
|
|
|
|
bmpHeader.id[0] = *buf_p++;
|
|
bmpHeader.id[1] = *buf_p++;
|
|
bmpHeader.fileSize = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.reserved0 = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataOffset = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.bitmapHeaderSize = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.width = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.height = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.planes = * ( short * ) buf_p;
|
|
buf_p += 2;
|
|
bmpHeader.bitsPerPixel = * ( short * ) buf_p;
|
|
buf_p += 2;
|
|
bmpHeader.compression = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataSize = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.hRes = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.vRes = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.colors = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
bmpHeader.importantColors = * ( int * ) buf_p;
|
|
buf_p += 4;
|
|
|
|
memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) );
|
|
|
|
if ( bmpHeader.bitsPerPixel == 8 ) {
|
|
buf_p += 1024;
|
|
}
|
|
|
|
if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) {
|
|
Error( "LoadBMP: only Windows-style BMP files supported (%s)\n", name );
|
|
}
|
|
if ( bmpHeader.fileSize != length ) {
|
|
Error( "LoadBMP: header size does not match file size (%d vs. %d) (%s)\n", bmpHeader.fileSize, length, name );
|
|
}
|
|
if ( bmpHeader.compression != 0 ) {
|
|
Error( "LoadBMP: only uncompressed BMP files supported (%s)\n", name );
|
|
}
|
|
if ( bmpHeader.bitsPerPixel < 8 ) {
|
|
Error( "LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name );
|
|
}
|
|
|
|
columns = bmpHeader.width;
|
|
rows = bmpHeader.height;
|
|
if ( rows < 0 ) {
|
|
rows = -rows;
|
|
}
|
|
numPixels = columns * rows;
|
|
|
|
if ( width ) {
|
|
*width = columns;
|
|
}
|
|
if ( height ) {
|
|
*height = rows;
|
|
}
|
|
|
|
bmpRGBA = (byte *)malloc( numPixels * 4 );
|
|
*pic = bmpRGBA;
|
|
|
|
byte *rowStart = buf_p;
|
|
for ( row = rows-1; row >= 0; row-- ) {
|
|
pixbuf = bmpRGBA + row*columns*4;
|
|
buf_p = rowStart;
|
|
for ( column = 0; column < columns; column++ ) {
|
|
unsigned char red, green, blue, alpha;
|
|
int palIndex;
|
|
unsigned short shortPixel;
|
|
|
|
switch ( bmpHeader.bitsPerPixel ) {
|
|
case 8:
|
|
palIndex = *buf_p++;
|
|
*pixbuf++ = bmpHeader.palette[palIndex][0];
|
|
*pixbuf++ = bmpHeader.palette[palIndex][1];
|
|
*pixbuf++ = bmpHeader.palette[palIndex][2];
|
|
*pixbuf++ = 0xff;
|
|
break;
|
|
case 16:
|
|
shortPixel = * ( unsigned short * ) pixbuf;
|
|
pixbuf += 2;
|
|
*pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7;
|
|
*pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2;
|
|
*pixbuf++ = ( shortPixel & ( 31 ) ) << 3;
|
|
*pixbuf++ = 0xff;
|
|
break;
|
|
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alpha = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alpha;
|
|
break;
|
|
default:
|
|
Error( "LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel, name );
|
|
break;
|
|
}
|
|
}
|
|
// rows are always 32 bit aligned
|
|
rowStart += ( ( buf_p - rowStart ) + 3 ) &~3;
|
|
}
|
|
|
|
free( buffer );
|
|
}
|
|
|
|
|
|
//=====================================================================================
|
|
|
|
typedef struct TargaHeader_s {
|
|
unsigned char id_length;
|
|
unsigned char colormap_type;
|
|
unsigned char image_type;
|
|
unsigned short colormap_index;
|
|
unsigned short colormap_length;
|
|
unsigned char colormap_size;
|
|
unsigned short x_origin;
|
|
unsigned short y_origin;
|
|
unsigned short width, height;
|
|
unsigned char pixel_size;
|
|
unsigned char attributes;
|
|
} TargaHeaeder_t;
|
|
|
|
static const int TGA_HEADER_SIZE = 18;
|
|
|
|
/*
|
|
========================
|
|
WriteTGA
|
|
|
|
Write a TGA to a buffer.
|
|
========================
|
|
*/
|
|
void WriteTGA( byte **bufferOut, size_t *bufferSizeOut, const byte *data, int width, int height,
|
|
int sourceDepth, int flipVertical, int swapRGB ) {
|
|
size_t i;
|
|
int imgStart = TGA_HEADER_SIZE;
|
|
|
|
assert( sourceDepth == 1 || sourceDepth == 3 || sourceDepth == 4 );
|
|
|
|
size_t bufferSize = width * height * sourceDepth + TGA_HEADER_SIZE;
|
|
*bufferSizeOut = bufferSize;
|
|
|
|
byte *buffer = (byte*)malloc( bufferSize );
|
|
*bufferOut = buffer;
|
|
|
|
memset( buffer, 0, TGA_HEADER_SIZE );
|
|
|
|
static const int TGA_IMAGETYPE_GREYSCALE = 3;
|
|
static const int TGA_IMAGETYPE_RGB = 2;
|
|
|
|
buffer[ 2 ] = sourceDepth == 1 ? TGA_IMAGETYPE_GREYSCALE : TGA_IMAGETYPE_RGB;
|
|
buffer[ 12 ] = width & 255;
|
|
buffer[ 13 ] = width >> 8;
|
|
buffer[ 14 ] = height & 255;
|
|
buffer[ 15 ] = height >> 8;
|
|
buffer[ 16 ] = sourceDepth * 8; // pixel size
|
|
if ( !flipVertical ) {
|
|
buffer[ 17 ] = ( 1 << 5 ); // flip bit, for normal top to bottom raster order
|
|
}
|
|
|
|
if ( sourceDepth == 4 ) {
|
|
if ( swapRGB ) {
|
|
// swap rgb to bgr
|
|
for ( i = imgStart ; i < bufferSize ; i += sourceDepth ) {
|
|
buffer[ i ] = data[ i - imgStart + 2 ]; // blue
|
|
buffer[ i + 1 ] = data[ i - imgStart + 1 ]; // green
|
|
buffer[ i + 2 ] = data[ i - imgStart ]; // red
|
|
buffer[ i + 3 ] = data[ i - imgStart + 3 ]; // alpha
|
|
}
|
|
} else {
|
|
memcpy( buffer + imgStart, data, bufferSize - TGA_HEADER_SIZE );
|
|
}
|
|
} else if ( sourceDepth == 3 ) {
|
|
if ( swapRGB ) {
|
|
for ( i = imgStart ; i < bufferSize ; i += sourceDepth ) {
|
|
buffer[ i ] = data[ i - imgStart + 2 ]; // blue
|
|
buffer[ i + 1 ] = data[ i - imgStart + 1 ]; // green
|
|
buffer[ i + 2 ] = data[ i - imgStart + 0 ]; // red
|
|
}
|
|
} else {
|
|
for ( i = imgStart ; i < bufferSize ; i += sourceDepth ) {
|
|
buffer[ i ] = data[ i - imgStart ]; // blue
|
|
buffer[ i + 1 ] = data[ i - imgStart + 1 ]; // green
|
|
buffer[ i + 2 ] = data[ i - imgStart + 2 ]; // red
|
|
}
|
|
}
|
|
} else if ( sourceDepth == 1 ) {
|
|
memcpy( buffer + imgStart, data, bufferSize - TGA_HEADER_SIZE );
|
|
}
|
|
}
|
|
|
|
void WriteTGAFile( const char *filename, const byte *pic, int w, int h ) {
|
|
byte *buf;
|
|
size_t bufLen;
|
|
WriteTGA( &buf, &bufLen, pic, w, h, 4, 0, 0 );
|
|
FILE * f = fopen( filename, "wb" );
|
|
assert( f );
|
|
fwrite( buf, bufLen, 1, f );
|
|
fclose( f );
|
|
free( buf );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
LoadTGAFromBuffer
|
|
|
|
Load a TGA from a buffer containing a TGA file.
|
|
========================
|
|
*/
|
|
int LoadTGAFromBuffer( const char *name, const unsigned char *buffer, const int bufferSize,
|
|
unsigned char **pic, int *width, int *height ) {
|
|
int columns, rows, numPixels;
|
|
size_t numBytes;
|
|
unsigned char *pixbuf;
|
|
int row, column;
|
|
const unsigned char *buf_p;
|
|
struct TargaHeader_s targa_header;
|
|
unsigned char *targa_rgba;
|
|
|
|
*pic = NULL;
|
|
|
|
buf_p = buffer;
|
|
|
|
targa_header.id_length = *buf_p++;
|
|
targa_header.colormap_type = *buf_p++;
|
|
targa_header.image_type = *buf_p++;
|
|
|
|
targa_header.colormap_index = *(short *)buf_p;
|
|
buf_p += 2;
|
|
targa_header.colormap_length = *(short *)buf_p;
|
|
buf_p += 2;
|
|
targa_header.colormap_size = *buf_p++;
|
|
targa_header.x_origin = *(short *)buf_p;
|
|
buf_p += 2;
|
|
targa_header.y_origin = *(short *)buf_p;
|
|
buf_p += 2;
|
|
targa_header.width = *(short *)buf_p;
|
|
buf_p += 2;
|
|
targa_header.height = *(short *)buf_p;
|
|
buf_p += 2;
|
|
targa_header.pixel_size = *buf_p++;
|
|
targa_header.attributes = *buf_p++;
|
|
|
|
if ( targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3 ) {
|
|
printf( "LoadTGA( %s ): Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported", name );
|
|
return 0;
|
|
}
|
|
|
|
if ( targa_header.colormap_type != 0 ) {
|
|
printf( "LoadTGA( %s ): colormaps not supported", name );
|
|
return 0;
|
|
}
|
|
|
|
if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 ) {
|
|
printf( "LoadTGA( %s ): Only 32 or 24 bit images supported (no colormaps)", name );
|
|
return 0;
|
|
}
|
|
|
|
if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) {
|
|
numBytes = targa_header.width * targa_header.height * ( targa_header.pixel_size >> 3 );
|
|
if ( numBytes > bufferSize - TGA_HEADER_SIZE - targa_header.id_length ) {
|
|
printf( "LoadTGA( %s ): incomplete file", name );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
columns = targa_header.width;
|
|
rows = targa_header.height;
|
|
numPixels = columns * rows;
|
|
|
|
if ( width ) {
|
|
*width = columns;
|
|
}
|
|
if ( height ) {
|
|
*height = rows;
|
|
}
|
|
|
|
targa_rgba = (unsigned char *)malloc( numPixels*4 );
|
|
*pic = targa_rgba;
|
|
|
|
if ( targa_header.id_length != 0 ) {
|
|
buf_p += targa_header.id_length; // skip TARGA image comment
|
|
}
|
|
|
|
if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) {
|
|
unsigned char red,green,blue,alphabyte;
|
|
switch( targa_header.pixel_size ) {
|
|
case 8:
|
|
// Uncompressed gray scale image
|
|
for( row = rows - 1; row >= 0; row-- ) {
|
|
pixbuf = targa_rgba + row*columns*4;
|
|
for( column = 0; column < columns; column++ ) {
|
|
blue = *buf_p++;
|
|
green = blue;
|
|
red = blue;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
}
|
|
}
|
|
break;
|
|
case 24:
|
|
// Uncompressed RGB image
|
|
for( row = rows - 1; row >= 0; row-- ) {
|
|
pixbuf = targa_rgba + row*columns*4;
|
|
for( column = 0; column < columns; column++ ) {
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
}
|
|
}
|
|
break;
|
|
case 32:
|
|
// Uncompressed RGBA image
|
|
for( row = rows - 1; row >= 0; row-- ) {
|
|
pixbuf = targa_rgba + row*columns*4;
|
|
for( column = 0; column < columns; column++ ) {
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alphabyte;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
printf( "LoadTGA( %s ): illegal pixel_size '%d'", name, targa_header.pixel_size );
|
|
free( *pic );
|
|
*pic = NULL;
|
|
return 0;
|
|
}
|
|
}
|
|
else if ( targa_header.image_type == 10 ) { // Runlength encoded RGB images
|
|
unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j;
|
|
|
|
red = 0;
|
|
green = 0;
|
|
blue = 0;
|
|
alphabyte = 0xff;
|
|
|
|
for( row = rows - 1; row >= 0; row-- ) {
|
|
pixbuf = targa_rgba + row*columns*4;
|
|
for( column = 0; column < columns; ) {
|
|
packetHeader= *buf_p++;
|
|
packetSize = 1 + (packetHeader & 0x7f);
|
|
if ( packetHeader & 0x80 ) { // run-length packet
|
|
switch( targa_header.pixel_size ) {
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = 255;
|
|
break;
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = *buf_p++;
|
|
break;
|
|
default:
|
|
printf( "LoadTGA( %s ): illegal pixel_size '%d'", name, targa_header.pixel_size );
|
|
free( *pic );
|
|
*pic = NULL;
|
|
return 0;
|
|
}
|
|
|
|
for( j = 0; j < packetSize; j++ ) {
|
|
*pixbuf++=red;
|
|
*pixbuf++=green;
|
|
*pixbuf++=blue;
|
|
*pixbuf++=alphabyte;
|
|
column++;
|
|
if ( column == columns ) { // run spans across rows
|
|
column = 0;
|
|
if ( row > 0) {
|
|
row--;
|
|
}
|
|
else {
|
|
goto breakOut;
|
|
}
|
|
pixbuf = targa_rgba + row*columns*4;
|
|
}
|
|
}
|
|
} else { // non run-length packet
|
|
for( j = 0; j < packetSize; j++ ) {
|
|
switch( targa_header.pixel_size ) {
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alphabyte;
|
|
break;
|
|
default:
|
|
printf( "LoadTGA( %s ): illegal pixel_size '%d'", name, targa_header.pixel_size );
|
|
free( *pic );
|
|
*pic = NULL;
|
|
return 0;
|
|
}
|
|
column++;
|
|
if ( column == columns ) { // pixel packet run spans across rows
|
|
column = 0;
|
|
if ( row > 0 ) {
|
|
row--;
|
|
}
|
|
else {
|
|
goto breakOut;
|
|
}
|
|
pixbuf = targa_rgba + row*columns*4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
breakOut: ;
|
|
}
|
|
}
|
|
|
|
if ( (targa_header.attributes & (1<<5)) ) { // image flp bit
|
|
byte *temp = malloc( *width * *height * 4 );
|
|
memcpy( temp, *pic, *width * *height * 4 );
|
|
|
|
for ( int y = 0 ; y < *height ; y++ ) {
|
|
memcpy( *pic + y * *width * 4, temp + (*height-1-y) * *width * 4, *width * 4 );
|
|
}
|
|
free( temp );
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
LoadTGA
|
|
|
|
Load TGA directly from a file.
|
|
========================
|
|
*/
|
|
int LoadTGA( const char *name, unsigned char **pic, int *width, int *height ) {
|
|
int len;
|
|
unsigned char *buf;
|
|
int ret;
|
|
|
|
FILE *f = fopen( name, "rb" );
|
|
if ( !f ) {
|
|
return 0;
|
|
}
|
|
len = FileLength( f );
|
|
buf = malloc( len );
|
|
fread( buf, 1, len, f );
|
|
fclose( f );
|
|
|
|
ret = LoadTGAFromBuffer( name, buf, len, pic, width, height );
|
|
|
|
free( buf );
|
|
|
|
return ret;
|
|
}
|
|
|
|
void OutlineImage( unsigned char *rgba, int width, int height ) {
|
|
unsigned char *data_p;
|
|
unsigned char *copy_p;
|
|
unsigned char *copy = (unsigned char *)alloca( width * height * 4 );
|
|
int x, y;
|
|
|
|
memcpy( copy, rgba, width * height * 4 );
|
|
data_p = rgba;
|
|
copy_p = copy;
|
|
|
|
for ( y = 0 ; y < height ; y++ ) {
|
|
for ( x = 0 ; x < width ; x++, data_p+=4, copy_p+=4 ) {
|
|
if ( data_p[3] != 0 ) {
|
|
continue;
|
|
}
|
|
if ( x < width-1 && copy_p[7] != 0 ) {
|
|
*(int *)data_p = ((int *)copy_p)[1];
|
|
} else if ( x > 0 && copy_p[-1] != 0 ) {
|
|
*(int *)data_p = ((int *)copy_p)[-1];
|
|
} else if ( y < height-1 && copy_p[width*4+3] != 0 ) {
|
|
*(int *)data_p = ((int *)copy_p)[width];
|
|
} else if ( y > 0 && copy_p[-width*4+3] != 0 ) {
|
|
*(int *)data_p = ((int *)copy_p)[-width];
|
|
}
|
|
data_p[3] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
int RowClear( unsigned char *rgba, int w, int h, int y ) {
|
|
int x;
|
|
for ( x = 0 ; x < w ; x++ ) {
|
|
if ( rgba[(y*w+x)*4+3] != 0 ) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int NextPowerOfTwo( int n ) {
|
|
int p = 1;
|
|
|
|
while ( p < n ) {
|
|
p <<= 1;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
AddTGA
|
|
|
|
========================
|
|
*/
|
|
void AddTGA( const char *localName, const byte *data, int dataLen ) {
|
|
assert( buildHeader.textures.count < MAX_IMAGE_TABLE );
|
|
pkTextureData_t *image = &buildTextureTable[buildHeader.textures.count++];
|
|
strcpy( image->name.name, localName );
|
|
image->picDataOfs = ftell( pakFile );
|
|
|
|
// load it
|
|
unsigned char *pic;
|
|
int width, height;
|
|
|
|
if ( !LoadTGAFromBuffer( localName, data, dataLen, &pic, &width, &height ) ) {
|
|
Error( "failed.\n" );
|
|
}
|
|
|
|
// scan for alpha
|
|
int hasAlpha = 0;
|
|
for ( int i = 0 ; i < width*height ; i++ ) {
|
|
if ( pic[i*4+3] != 255 ) {
|
|
hasAlpha = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// default image format
|
|
image->format = TF_5551;
|
|
|
|
// scan the parmLines for this filename
|
|
for ( int i = 0 ; i < numParmLines ; i++ ) {
|
|
if ( !strcasecmp( parmLines[i].argv[0], localName ) ) {
|
|
for ( int j = 1 ; j < parmLines[i].argc ; j++ ) {
|
|
if ( !strcmp( parmLines[i].argv[j], "5551" ) ) {
|
|
image->format = TF_5551;
|
|
} else if ( !strcmp( parmLines[i].argv[j], "4444" ) ) {
|
|
image->format = TF_4444;
|
|
} else if ( !strcmp( parmLines[i].argv[j], "565" ) ) {
|
|
image->format = TF_565;
|
|
} else if ( !strcmp( parmLines[i].argv[j], "8888" ) ) {
|
|
image->format = TF_8888;
|
|
} else if ( !strcmp( parmLines[i].argv[j], "LA" ) ) {
|
|
image->format = TF_LA;
|
|
} else if ( !strcmp( parmLines[i].argv[j], "PVR4" ) ) {
|
|
if ( hasAlpha ) {
|
|
image->format = TF_PVR4;
|
|
} else {
|
|
image->format = TF_PVR4A;
|
|
}
|
|
} else if ( !strcmp( parmLines[i].argv[j], "PVR2" ) ) {
|
|
if ( hasAlpha ) {
|
|
image->format = TF_PVR2;
|
|
} else {
|
|
image->format = TF_PVR2A;
|
|
}
|
|
} else {
|
|
printf( "bad parm '%s'\n", parmLines[i].argv[j] );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// set this true if we need to write a new tga out for compression
|
|
// because we modified it in some way from the original (make power of 2, sprite outline, etc)
|
|
int imageModified = 0;
|
|
|
|
// make sure it is a power of two
|
|
int potW = NextPowerOfTwo( width );
|
|
int potH = NextPowerOfTwo( height );
|
|
|
|
// the texturetool compressor only supports square textures as of iphone OS 2.2.1
|
|
// Not sure if that is a hardware limit or just software. This throws away
|
|
// some of the space savings, but it is still a speed savings to use.
|
|
if ( image->format == TF_PVR4 || image->format == TF_PVR2 ) {
|
|
if ( potW > potH ) {
|
|
potH = potW;
|
|
}
|
|
if ( potH > potW ) {
|
|
potW = potH;
|
|
}
|
|
}
|
|
|
|
if ( potW > width || potH > height ) {
|
|
printf( "Insetting %i x %i image in %i x %i block\n", width, height, potW, potH );
|
|
unsigned char *newPic = (unsigned char *)malloc( potW * potH * 4 );
|
|
// replicating the last row or column might be better
|
|
if ( hasAlpha ) {
|
|
memset( newPic, 0, potW * potH * 4 );
|
|
} else {
|
|
memset( newPic, 255, potW * potH * 4 );
|
|
}
|
|
for ( int y = 0 ; y < height ; y++ ) {
|
|
memcpy( newPic + y * potW * 4, pic + y * width * 4, width * 4 );
|
|
}
|
|
free( pic );
|
|
pic = newPic;
|
|
imageModified = 1;
|
|
}
|
|
|
|
image->srcWidth = width;
|
|
image->srcHeight = height;
|
|
image->uploadWidth = potW;
|
|
image->uploadHeight = potH;
|
|
|
|
image->wrapS = GL_REPEAT;
|
|
image->wrapT = GL_REPEAT;
|
|
image->minFilter = GL_LINEAR_MIPMAP_NEAREST;
|
|
image->magFilter = GL_LINEAR;
|
|
image->aniso = 1;
|
|
image->numLevels = 0;
|
|
image->maxS = (float)image->srcWidth / image->uploadWidth;
|
|
image->maxT = (float)image->srcHeight / image->uploadHeight;
|
|
|
|
int w = image->uploadWidth;
|
|
int h = image->uploadHeight;
|
|
|
|
// determine the number of mip levels. We can't just count as
|
|
// we create them, because the PVRTC texturetool creates them
|
|
// all in one run
|
|
int max = w > h ? w : h;
|
|
while ( max >= 1 ) {
|
|
image->numLevels++;
|
|
max >>= 1;
|
|
}
|
|
|
|
// checkerboard debug tool for testing texel centers
|
|
int checker = 0;
|
|
if ( checker ) {
|
|
for ( int y = 0 ; y < height ; y++ ) {
|
|
for ( int x = 0 ; x < width ; x++ ) {
|
|
if ( (x^y)&1 ) {
|
|
*((int *)pic+y*potW+x) = -1;
|
|
} else {
|
|
*((int *)pic+y*potW+x) = 0;
|
|
}
|
|
}
|
|
}
|
|
imageModified = 1;
|
|
}
|
|
|
|
// sprite image outlining to avoid bilinear filter halos
|
|
int sprite = 0;
|
|
if ( sprite ) {
|
|
for ( int i = 0 ; i < 8 ; i++ ) {
|
|
OutlineImage( pic, width, height );
|
|
}
|
|
for ( int i = 0 ; i < width*height ; i++ ) {
|
|
if ( pic[i*4+3] == 1 ) {
|
|
pic[i*4+3] = 0;
|
|
}
|
|
}
|
|
imageModified = 1;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// scan for bounding box of opaque texels
|
|
//-----------------------------------------
|
|
if ( !hasAlpha ) {
|
|
image->numBounds = 0;
|
|
} else {
|
|
int x, y;
|
|
|
|
// find the bounding boxes for more efficient drawing
|
|
image->numBounds = 1;
|
|
for ( y = 0 ; y < h ; y++ ) {
|
|
if ( !RowClear( pic, w, h, y ) ) {
|
|
// this row is needed
|
|
image->bounds[0][0][1] = y;
|
|
break;
|
|
}
|
|
}
|
|
for ( y = h-1 ; y >= 0 ; y-- ) {
|
|
if ( !RowClear( pic, w, h, y ) ) {
|
|
// this row is needed
|
|
image->bounds[0][1][1] = y;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if the middle row is clear, make two boxes
|
|
// We could make a better test, but this catches the ones we care about...
|
|
if ( image->bounds[0][0][1] < h/2 && image->bounds[0][1][1] > h / 2
|
|
&& RowClear( pic, w, h, h/2 ) ) {
|
|
image->numBounds = 2;
|
|
image->bounds[1][1][1] = image->bounds[0][1][1];
|
|
|
|
for ( y = h/2-1 ; y >= 0 ; y-- ) {
|
|
if ( !RowClear( pic, w, h, y ) ) {
|
|
image->bounds[0][1][1] = y;
|
|
break;
|
|
}
|
|
}
|
|
for ( y = h/2+1 ; y < h ; y++ ) {
|
|
if ( !RowClear( pic, w, h, y ) ) {
|
|
image->bounds[1][0][1] = y;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( int b = 0 ; b < image->numBounds ; b++ ) {
|
|
for ( x = 0 ; x < w ; x++ ) {
|
|
for ( y = image->bounds[b][0][1] ; y <= image->bounds[b][1][1] ; y++ ) {
|
|
if ( pic[(y*w+x)*4+3] != 0 ) {
|
|
// this column is needed
|
|
image->bounds[b][0][0] = x;
|
|
break;
|
|
}
|
|
}
|
|
if ( y <= image->bounds[b][1][1] ) {
|
|
break;
|
|
}
|
|
}
|
|
for ( x = w-1 ; x >= 0 ; x-- ) {
|
|
for ( y = image->bounds[b][0][1] ; y <= image->bounds[b][1][1] ; y++ ) {
|
|
if ( pic[(y*w+x)*4+3] != 0 ) {
|
|
// this column is needed
|
|
image->bounds[b][1][0] = x;
|
|
break;
|
|
}
|
|
}
|
|
if ( y <= image->bounds[b][1][1] ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// run texturetool to PVR compress and generate all mip levels
|
|
// Arguably, we should do the sprite outlining on each mip level
|
|
// independently, and PVR compress each layer seperately.
|
|
//-----------------------------------------
|
|
if ( image->format == TF_PVR4 || image->format == TF_PVR2
|
|
|| image->format == TF_PVR4A || image->format == TF_PVR2A ) {
|
|
char tempTGAname[L_tmpnam];
|
|
|
|
// write the modified image data out if necessary
|
|
if ( imageModified ) {
|
|
tmpnam( tempTGAname );
|
|
|
|
WriteTGAFile( tempTGAname, pic, w, h );
|
|
} else {
|
|
sprintf( tempTGAname, "%s/%s", assetDirectory, localName );
|
|
}
|
|
|
|
// run the external compression tool
|
|
// FIXME: use an explicit name and timestamp check
|
|
char tempPVRname[L_tmpnam];
|
|
tmpnam( tempPVRname );
|
|
char cmd[1024];
|
|
sprintf( cmd, "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/texturetool -m -e PVRTC %s -f Raw -o %s %s",
|
|
( image->format == TF_PVR2 || image->format == TF_PVR2A ) ? "--bits-per-pixel-2" : "--bits-per-pixel-4",
|
|
tempPVRname, tempTGAname );
|
|
printf( "%s\n", cmd );
|
|
system( cmd );
|
|
|
|
FILE *f = fopen( tempPVRname, "rb" );
|
|
if ( !f ) {
|
|
Error( "Can't open '%s'\n", tempPVRname );
|
|
}
|
|
int len = FileLength( f );
|
|
unsigned char *raw = alloca( len );
|
|
fread( raw, 1, len, f );
|
|
fclose( f );
|
|
|
|
// write to the pak file
|
|
fwrite( raw, 1, len, pakFile );
|
|
|
|
if ( imageModified ) {
|
|
remove( tempTGAname );
|
|
}
|
|
remove( tempPVRname );
|
|
return;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// create mip maps and write out simple image formats
|
|
//-----------------------------------------
|
|
while ( 1 ) {
|
|
byte *rgba_p = pic;
|
|
|
|
// convert to target format
|
|
switch ( image->format ) {
|
|
case TF_8888:
|
|
{
|
|
int * processed = alloca( w * h * 4 );
|
|
int * s_p = processed;
|
|
|
|
for ( int i = 0 ; i < w*h ; i++, rgba_p+=4 ) {
|
|
int r = rgba_p[0];
|
|
int g = rgba_p[1];
|
|
int b = rgba_p[2];
|
|
int a = rgba_p[3];
|
|
|
|
*s_p++ = (b<<24) | (g<<16) | (r<<8) | a;
|
|
}
|
|
// write it out
|
|
fwrite( processed, w * h, 4, pakFile );
|
|
break;
|
|
}
|
|
case TF_LA:
|
|
{
|
|
byte * processed = alloca( w * h * 2 );
|
|
byte * s_p = processed;
|
|
|
|
for ( int i = 0 ; i < w*h ; i++, rgba_p+=4 ) {
|
|
int l = rgba_p[0];
|
|
int a = rgba_p[1]; // this should probably be [3], but Cass's font renderer saved it out as LA01
|
|
|
|
*s_p++ = l;
|
|
*s_p++ = a;
|
|
}
|
|
// write it out
|
|
fwrite( processed, w * h, 2, pakFile );
|
|
break;
|
|
}
|
|
case TF_5551:
|
|
{
|
|
short * processed = alloca( w * h * 2 );
|
|
short * s_p = processed;
|
|
|
|
for ( int i = 0 ; i < w*h ; i++, rgba_p+=4 ) {
|
|
int r = rgba_p[0];
|
|
int g = rgba_p[1];
|
|
int b = rgba_p[2];
|
|
int a = rgba_p[3];
|
|
|
|
*s_p++ = ((r>>3)<<11) | ((g>>3)<<6) | ((b>>3)<<1) | (a>>7);
|
|
}
|
|
// write it out
|
|
fwrite( processed, w * h, 2, pakFile );
|
|
break;
|
|
}
|
|
case TF_565:
|
|
{
|
|
short * processed = alloca( w * h * 2 );
|
|
short * s_p = processed;
|
|
|
|
for ( int i = 0 ; i < w*h ; i++, rgba_p+=4 ) {
|
|
int r = rgba_p[0];
|
|
int g = rgba_p[1];
|
|
int b = rgba_p[2];
|
|
|
|
*s_p++ = ((r>>3)<<11) | ((g>>2)<<5) | (b>>3);
|
|
}
|
|
// write it out
|
|
fwrite( processed, w * h, 2, pakFile );
|
|
break;
|
|
}
|
|
case TF_4444:
|
|
{
|
|
short * processed = alloca( w * h * 2 );
|
|
short * s_p = processed;
|
|
|
|
for ( int i = 0 ; i < w*h ; i++, rgba_p+=4 ) {
|
|
int r = rgba_p[0];
|
|
int g = rgba_p[1];
|
|
int b = rgba_p[2];
|
|
int a = rgba_p[3];
|
|
|
|
*s_p++ = ((r>>4)<<12) | ((g>>4)<<8) | ((b>>4)<<4) | (a>>4);
|
|
}
|
|
// write it out
|
|
fwrite( processed, w * h, 2, pakFile );
|
|
break;
|
|
}
|
|
default:
|
|
Error( "unimplemented format: %i\n", image->format );
|
|
}
|
|
|
|
if ( w == 1 && h == 1 ) {
|
|
break;
|
|
}
|
|
// mip map
|
|
w >>= 1;
|
|
if ( w == 0 ) {
|
|
w = 1;
|
|
}
|
|
h >>= 1;
|
|
if ( h == 0 ) {
|
|
h = 1;
|
|
}
|
|
byte *tempMip = alloca( w * h * 4 );
|
|
// FIXME: doesn't handle 2x1 and 1x2 cases properly...
|
|
for ( int y = 0 ; y < h ; y++ ) {
|
|
for ( int x = 0 ; x < w ; x++ ) {
|
|
for ( int c = 0 ; c < 4 ; c++ ) {
|
|
tempMip[(y*w+x)*4+c] = (
|
|
pic[((y*2+0)*w*2+(x*2+0))*4+c] +
|
|
pic[((y*2+0)*w*2+(x*2+1))*4+c] +
|
|
pic[((y*2+1)*w*2+(x*2+0))*4+c] +
|
|
pic[((y*2+1)*w*2+(x*2+1))*4+c] ) >> 2;
|
|
}
|
|
}
|
|
}
|
|
pic = tempMip;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
========================
|
|
AddRAW
|
|
|
|
========================
|
|
*/
|
|
void AddRAW( const char *localName, const byte *data, int dataLen ) {
|
|
assert( buildHeader.raws.count < MAX_RAW_TABLE );
|
|
pkRawData_t *raw = &buildRawTable[buildHeader.raws.count++];
|
|
strcpy( raw->name.name, localName );
|
|
raw->rawDataOfs = ftell( pakFile );
|
|
raw->rawDataLen = dataLen;
|
|
|
|
fwrite( data, 1, dataLen, pakFile );
|
|
|
|
// always add a 0 after each raw file so text files can be assumed to be
|
|
// c-string terminated
|
|
byte zero = 0;
|
|
fwrite( &zero, 1, 1, pakFile );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
AddDirectoryToPak_r
|
|
|
|
========================
|
|
*/
|
|
void AddDirectoryToPak_r( const char *localDirName ) {
|
|
char fullDirName[MAXPATHLEN];
|
|
|
|
if ( localDirName[0] == '/' ) {
|
|
localDirName++;
|
|
}
|
|
sprintf( fullDirName, "%s/%s", assetDirectory, localDirName );
|
|
printf( "entering %s\n", fullDirName );
|
|
DIR *dir = opendir( fullDirName );
|
|
assert( dir );
|
|
|
|
while( 1 ) {
|
|
// make sure the file pointer is 16 byte aligned, since
|
|
// we will be referencing it with mmap. Alignment greater than
|
|
// 4 might be wasted on iPhone, but it won't be all that much space.
|
|
int ofs = ftell( pakFile );
|
|
if ( ofs & 15 ) {
|
|
byte pad[16];
|
|
memset( pad, 0, sizeof( pad ) );
|
|
fwrite( pad, 16 - ( ofs & 15 ), 1, pakFile );
|
|
}
|
|
|
|
// get the next file in the directory
|
|
struct dirent *file = readdir( dir );
|
|
if ( !file ) {
|
|
return;
|
|
}
|
|
|
|
char localFileName[MAXPATHLEN];
|
|
if ( localDirName[0] ) {
|
|
sprintf( localFileName, "%s/%s", localDirName, file->d_name );
|
|
} else {
|
|
sprintf( localFileName, "%s", file->d_name );
|
|
}
|
|
|
|
if ( file->d_name[0] == '.' ) {
|
|
// ignore . and .. and hidden files
|
|
continue;
|
|
}
|
|
if ( file->d_type == DT_DIR ) {
|
|
// recurse into another directory
|
|
AddDirectoryToPak_r( localFileName );
|
|
continue;
|
|
}
|
|
|
|
// make sure name length fits
|
|
assert( strlen( localFileName ) < MAX_PK_NAME - 1 );
|
|
|
|
// load the file
|
|
char fullFileName[MAXPATHLEN];
|
|
sprintf( fullFileName, "%s/%s", assetDirectory, localFileName );
|
|
FILE *f = fopen( fullFileName, "rb" );
|
|
if ( !f ) {
|
|
Error( "Can't open '%s'\n", localFileName );
|
|
}
|
|
int len = FileLength( f );
|
|
unsigned char *raw = malloc( len );
|
|
fread( raw, 1, len, f );
|
|
fclose( f );
|
|
printf( "%8i %s\n", len, localFileName );
|
|
if ( strstr( localFileName, ".tga" ) ) {
|
|
AddTGA( localFileName, raw, len );
|
|
} else if ( strstr( localFileName, ".wav" ) ) {
|
|
AddWAV( localFileName, raw, len );
|
|
} else {
|
|
AddRAW( localFileName, raw, len );
|
|
}
|
|
free( raw );
|
|
}
|
|
}
|
|
|
|
//======================================================================================
|
|
|
|
#define ATLAS_SIZE 1024
|
|
#define ATLAS_EMPTY_ALPHA 128
|
|
|
|
byte atlas[ATLAS_SIZE*ATLAS_SIZE*4];
|
|
int atlasNum = 0;
|
|
|
|
int FindSpotInAtlas( int w, int h, int *spotX, int *spotY ) {
|
|
int x = 0;
|
|
int y = 0;
|
|
int maxX = ATLAS_SIZE - w;
|
|
int maxY = ATLAS_SIZE - h;
|
|
|
|
while( 1 ) {
|
|
retry:
|
|
for ( int yy = 0 ; yy < h ; yy++ ) {
|
|
for ( int xx = 0 ; xx < w ; xx++ ) {
|
|
if ( atlas[((y+yy)*ATLAS_SIZE+x+xx)*4+3] != ATLAS_EMPTY_ALPHA ) {
|
|
// can't use this spot, skip ahead past this solid mark
|
|
x = x + xx + 1;
|
|
if ( x > maxX ) {
|
|
x = 0;
|
|
y++;
|
|
if ( y > maxY ) {
|
|
return 0;
|
|
}
|
|
}
|
|
goto retry;
|
|
}
|
|
}
|
|
}
|
|
*spotX = x;
|
|
*spotY = y;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void EmptyAtlas() {
|
|
// fill with alpha 128 to signify empty
|
|
memset( atlas, 0, sizeof( atlas ) );
|
|
for ( int i = 0 ; i < ATLAS_SIZE * ATLAS_SIZE ; i++ ) {
|
|
atlas[i*4+3] = ATLAS_EMPTY_ALPHA;
|
|
}
|
|
}
|
|
|
|
void ClearBlock( int x, int y, int w, int h ) {
|
|
// fill with black / alpha 0
|
|
for ( int yy = 0 ; yy < h ; yy++ ) {
|
|
memset( atlas + ((y+yy)*ATLAS_SIZE+x)*4, 0, w*4 );
|
|
}
|
|
}
|
|
|
|
void FinishAtlas() {
|
|
char filename[1024];
|
|
|
|
sprintf( filename, "%s/atlas%i.tga", assetDirectory, atlasNum );
|
|
printf( "Writing %s.\n", filename );
|
|
WriteTGAFile( filename, atlas, ATLAS_SIZE, ATLAS_SIZE );
|
|
// this atlas is complete, write it out
|
|
atlasNum++;
|
|
// clear it and retry the allocation
|
|
EmptyAtlas();
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
AtlasDirectory
|
|
|
|
========================
|
|
*/
|
|
void AtlasDirectory( const char *fullDirName, const char *prefix ) {
|
|
printf( "atlasing %s* from %s\n", prefix, fullDirName );
|
|
DIR *dir = opendir( fullDirName );
|
|
assert( dir );
|
|
|
|
int totalSourceTexels = 0;
|
|
int totalSourceImages = 0;
|
|
int totalBorderedSourceTexels = 0;
|
|
int totalPotTexels = 0;
|
|
|
|
EmptyAtlas();
|
|
|
|
while( 1 ) {
|
|
// get the next file in the directory
|
|
struct dirent *file = readdir( dir );
|
|
if ( !file ) {
|
|
break;
|
|
}
|
|
if ( file->d_name[0] == '.' ) {
|
|
// ignore . and .. and hidden files
|
|
continue;
|
|
}
|
|
#if 0
|
|
if ( file->d_type == DT_DIR ) {
|
|
// recurse into another directory
|
|
AddDirectoryToPak_r( localFileName );
|
|
continue;
|
|
}
|
|
#endif
|
|
if ( !strstr( file->d_name, ".BMP" ) && !strstr( file->d_name, ".bmp" ) ) {
|
|
continue;
|
|
}
|
|
|
|
// only grab the specified images
|
|
if ( strncmp( file->d_name, prefix, strlen( prefix ) ) ) {
|
|
continue;
|
|
}
|
|
|
|
// load the image
|
|
char fullFileName[MAXPATHLEN];
|
|
sprintf( fullFileName, "%s/%s", fullDirName, file->d_name );
|
|
|
|
byte *pic;
|
|
int width, height;
|
|
LoadBMP( fullFileName, &pic, &width, &height );
|
|
|
|
// add a four pixel border around each sprite for mip map outlines
|
|
static const int OUTLINE_WIDTH = 4;
|
|
int widthInAtlas = width + 2*OUTLINE_WIDTH;
|
|
int heightInAtlas = height + 2*OUTLINE_WIDTH;
|
|
|
|
int ax, ay;
|
|
|
|
if ( !FindSpotInAtlas( widthInAtlas, heightInAtlas, &ax, &ay ) ) {
|
|
FinishAtlas();
|
|
if ( !FindSpotInAtlas( widthInAtlas, heightInAtlas, &ax, &ay ) ) {
|
|
Error( "Couldn't allocate %s: %i,%i in empty atlas", fullFileName, width, height );
|
|
}
|
|
}
|
|
|
|
printf( "%4i, %4i at %4i,%4i: %s\n", width, height, ax, ay, fullFileName );
|
|
totalSourceTexels += width * height;
|
|
totalSourceImages++;
|
|
totalBorderedSourceTexels += widthInAtlas * heightInAtlas;
|
|
totalPotTexels += NextPowerOfTwo( width ) * NextPowerOfTwo( height );
|
|
|
|
// clear the extended border area to fully transparent
|
|
ClearBlock( ax, ay, widthInAtlas, heightInAtlas );
|
|
|
|
// copy the actual image into the inset area past the added borders
|
|
// for Doom graphics, the color key alpha value is always the top left corner texel
|
|
ax += OUTLINE_WIDTH;
|
|
ay += OUTLINE_WIDTH;
|
|
for ( int y = 0 ; y < height ; y++ ) {
|
|
for ( int x = 0 ; x < width ; x++ ) {
|
|
int p = ((int *)pic)[y*width+x];
|
|
if ( p == DOOM_ALPHA_TEXEL ) {
|
|
((int *)atlas)[ (ay+y)*ATLAS_SIZE+ax+x ] = 0;
|
|
} else {
|
|
((int *)atlas)[ (ay+y)*ATLAS_SIZE+ax+x ] = p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// process and write out the partially filled atlas
|
|
FinishAtlas();
|
|
|
|
printf ("%i soource images\n", totalSourceImages );
|
|
printf ("%i atlas images\n", atlasNum );
|
|
printf ("%6.1fk source texels\n", totalSourceTexels*0.001f );
|
|
printf ("%6.1fk bordered source texels\n", totalBorderedSourceTexels*0.001f );
|
|
printf ("%6.1fk atlas texels\n", atlasNum*ATLAS_SIZE*ATLAS_SIZE*0.001f );
|
|
printf ("%6.1fk power of two inset texels\n", totalPotTexels*0.001f );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
PK_HashName
|
|
|
|
==================
|
|
*/
|
|
int PK_HashName( const char *name, char canonical[MAX_PK_NAME] ) {
|
|
int o = 0;
|
|
int hash = 0;
|
|
|
|
do {
|
|
int c = name[o];
|
|
if ( c == 0 ) {
|
|
break;
|
|
}
|
|
// backslashes to forward slashes
|
|
if ( c == '\\' ) {
|
|
c = '/';
|
|
}
|
|
// to lowercase
|
|
c = tolower( c );
|
|
canonical[o++] = c;
|
|
hash = (hash << 5) - hash + c;
|
|
} while ( o < MAX_PK_NAME-1 );
|
|
canonical[o] = 0;
|
|
|
|
return hash;
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
WriteType
|
|
|
|
========================
|
|
*/
|
|
void WriteType( FILE *pakFile, pkType_t *type, int structSize, pkName_t *table ) {
|
|
type->structSize = structSize;
|
|
type->tableOfs = ftell( pakFile );
|
|
|
|
// build hash chains for everything
|
|
for ( int i = 0 ; i < PK_HASH_CHAINS ; i++ ) {
|
|
type->hashChains[i] = -1;
|
|
}
|
|
for ( int i = 0 ; i < type->count ; i++ ) {
|
|
pkName_t *name = (pkName_t *)((unsigned char *)table + i * structSize );
|
|
char original[MAX_PK_NAME];
|
|
strcpy( original, name->name );
|
|
// make the name canonical and get the hash
|
|
name->nameHash = PK_HashName( original, name->name );
|
|
|
|
// add it to the hash chain
|
|
int chain = name->nameHash & (PK_HASH_CHAINS-1);
|
|
name->nextOnHashChain = type->hashChains[chain];
|
|
type->hashChains[chain] = i;
|
|
}
|
|
|
|
fwrite( table, type->count, type->structSize, pakFile );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
main
|
|
|
|
========================
|
|
*/
|
|
int main (int argc, const char * argv[]) {
|
|
int arg;
|
|
|
|
for ( arg = 1 ; arg < argc ; arg++ ) {
|
|
if ( argv[arg][0] != '-' ) {
|
|
break;
|
|
}
|
|
if ( !strcmp( argv[arg], "-i" ) ) {
|
|
assetDirectory = argv[arg+1];
|
|
arg++;
|
|
continue;
|
|
}
|
|
if ( !strcmp( argv[arg], "-o" ) ) {
|
|
outputFile = argv[arg+1];
|
|
arg++;
|
|
continue;
|
|
}
|
|
if ( !strcmp( argv[arg], "-p" ) ) {
|
|
parmFile = argv[arg+1];
|
|
arg++;
|
|
continue;
|
|
}
|
|
if ( !strcmp( argv[arg], "-?" ) ) {
|
|
Error( "doomtool [-i inputDirectory] [-o outputFile] [-p parmfile]\n" );
|
|
}
|
|
Error( "unknown option '%s'\n", argv[arg] );
|
|
}
|
|
|
|
//-----------------------------
|
|
// parse the parm file
|
|
//-----------------------------
|
|
FILE *f = fopen( parmFile, "rb" );
|
|
numParmLines = 0;
|
|
if ( f ) {
|
|
char line[1024];
|
|
while( fgets( line, sizeof( line ), f ) ) {
|
|
// remove trailing newline
|
|
if ( line[strlen(line)-1] == '\n' ) {
|
|
line[strlen(line)-1] = 0;
|
|
}
|
|
|
|
parmLine_t *pl = &parmLines[numParmLines];
|
|
// tokenize
|
|
char *inputString = line;
|
|
char *ap;
|
|
while( ap = strsep( &inputString, " \t" ) ) {
|
|
if ( *ap == '\0' ) {
|
|
continue;
|
|
}
|
|
pl->argv[pl->argc] = strdup( ap );
|
|
if ( ++pl->argc == MAX_ARGV ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( pl->argc > 0 ) {
|
|
numParmLines++;
|
|
}
|
|
}
|
|
fclose( f );
|
|
}
|
|
|
|
// AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "" );
|
|
#if 0
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "BOS2" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "BOSS" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "BSPI" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "CPOS" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "CYBR" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "FAT" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "HEAD" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "PAIN" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "PLAY" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "POSS" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "SARG" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "SKEL" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "SKUL" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "SPID" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "SPOS" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "SSWV" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "TROO" );
|
|
AtlasDirectory( "/Users/johnc/DOOM2/SPRITES", "VILE" );
|
|
#endif
|
|
//-----------------------------
|
|
// start writing the outputFile
|
|
//-----------------------------
|
|
|
|
pakFile = fopen( outputFile, "wb" );
|
|
assert( pakFile );
|
|
|
|
// leave space for the header, which will be written at the end
|
|
fwrite( &buildHeader, 1, sizeof( buildHeader ), pakFile );
|
|
|
|
// recursively process everything under the asset directory
|
|
AddDirectoryToPak_r( "" );
|
|
|
|
// write out the tables
|
|
WriteType( pakFile, &buildHeader.textures, sizeof( pkTextureData_t ), &buildTextureTable[0].name );
|
|
WriteType( pakFile, &buildHeader.wavs, sizeof( pkWavData_t ), &buildWavTable[0].name );
|
|
WriteType( pakFile, &buildHeader.raws, sizeof( pkRawData_t ), &buildRawTable[0].name );
|
|
|
|
buildHeader.version = PKFILE_VERSION;
|
|
|
|
printf( "%s : %i bytes\n", outputFile, ftell( pakFile ) );
|
|
|
|
// go back and write the header
|
|
fseek( pakFile, 0, SEEK_SET );
|
|
fwrite( &buildHeader, 1, sizeof( buildHeader ), pakFile );
|
|
|
|
fclose( pakFile );
|
|
|
|
return 0;
|
|
}
|