This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/launch/tools/conv_sprite.c

456 lines
13 KiB
C

//=======================================================================
// Copyright XashXT Group 2008 ©
// conv_sprite.c - convert q1\q2\hl\spr32 sprites
//=======================================================================
#include "launch.h"
#include "ripper.h"
#include "byteorder.h"
#include "sprite.h"
// sprite_decompiler.c
#define SPR_VP_PARALLEL_UPRIGHT 0
#define SPR_FACING_UPRIGHT 1
#define SPR_VP_PARALLEL 2
#define SPR_ORIENTED 3 // all axis are valid
#define SPR_VP_PARALLEL_ORIENTED 4
#define SPR_NORMAL 0 // solid sprite
#define SPR_ADDITIVE 1
#define SPR_INDEXALPHA 2
#define SPR_ALPHTEST 3
#define SPR_ADDGLOW 4 // same as additive, but without depthtest
typedef struct frame_s
{
char name[64]; // framename
int width; // lumpwidth
int height; // lumpheight
int origin[2]; // monster origin
} frame_t;
typedef struct group_s
{
frame_t frame[64]; // max groupframes
float interval[64]; // frame intervals
int numframes; // num group frames;
} group_t;
struct qcsprite_s
{
group_t group[8]; // 64 frames for each group
frame_t frame[512]; // or 512 ungroupped frames
int type; // rendering type
int texFormat; // half-life texture
qboolean truecolor; // spr32
byte palette[256][4]; // shared palette
int numgroup; // groups counter
int totalframes; // including group frames
} spr;
/*
========================================================================
.SPR sprite file format
========================================================================
*/
#define IDSPRQ1HEADER (('P'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDSP"
#define SPRITEQ1_VERSION 1
#define SPRITEHL_VERSION 2
#define SPRITE32_VERSION 32
typedef struct
{
int ident;
int version;
int type;
int boundingradius;
int width;
int height;
int numframes;
float beamlength;
synctype_t synctype;
} dspriteq1_t;
typedef struct
{
int ident;
int version;
int type;
int texFormat; // Half-Life stuff only
float boundingradius; // software rendering stuff
int width;
int height;
int numframes;
float beamlength; // software rendering stuff
synctype_t synctype;
} dspritehl_t;
/*
========================================================================
.SP2 sprite file format
========================================================================
*/
#define IDSPRQ2HEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDS2"
#define SPRITEQ2_VERSION 2
typedef struct
{
int width;
int height;
int origin_x;
int origin_y; // raster coordinates inside pic
char name[64]; // name of pcx file
} dsprframeq2_t;
typedef struct
{
int ident;
int version;
int numframes;
dsprframeq2_t frames[1]; // variable sized
} dspriteq2_t;
_inline const char *SPR_RenderMode( void )
{
switch( spr.texFormat )
{
case SPR_ADDGLOW: return "glow";
case SPR_ALPHTEST: return "alphatest";
case SPR_INDEXALPHA: return "indexalpha";
case SPR_ADDITIVE: return "additive";
case SPR_NORMAL: return "solid";
default: return "normal";
}
}
_inline const char *SPR_RenderType( void )
{
switch( spr.type )
{
case SPR_ORIENTED: return "oriented";
case SPR_VP_PARALLEL: return "vp_parallel";
case SPR_FACING_UPRIGHT: return "facing_upright";
case SPR_VP_PARALLEL_UPRIGHT: return "vp_parallel_upright";
case SPR_VP_PARALLEL_ORIENTED: return "vp_parallel_oriented";
default: return "oriented";
}
}
void *SPR_ConvertFrame( const char *name, const char *ext, void *pin, int framenum, int groupframenum )
{
rgbdata_t *pix;
dspriteframe_t *pinframe;
string framename;
byte *fin, *fout;
int i, pixels, width, height;
pinframe = (dspriteframe_t *)pin;
width = LittleLong( pinframe->width );
height = LittleLong( pinframe->height );
fin = (byte *)(pinframe + 1);
if( width <= 0 || height <= 0 )
{
// NOTE: in Q1 existing sprites with blank frames
spr.totalframes--;
return (void *)((byte *)(pinframe + 1));
}
// extract sprite name from path
FS_FileBase( name, framename );
com.strcat( framename, va("_%i", framenum ));
pixels = width * height;
if( spr.truecolor ) pixels *= 4;
// frame exist, go next
if( FS_FileExists( va("%s/sprites/%s.%s", gs_gamedir, framename, ext ), false ))
return (void *)((byte *)(pinframe + 1) + pixels );
if( spr.truecolor )
{
// HACKHACK: manually create rgbdata_t
pix = Mem_Alloc( Sys.basepool, sizeof( *pix ));
fout = Mem_Alloc( Sys.basepool, pixels );
Mem_Copy( fout, fin, pixels );
if( spr.texFormat >= SPR_INDEXALPHA )
pix->flags |= IMAGE_HAS_ALPHA;
pix->type = PF_RGBA_32;
pix->width = width;
pix->height = height;
pix->size = pixels;
pix->depth = 1;
pix->numMips = 1;
pix->buffer = fout;
}
else
{
pix = FS_LoadImage( va( "#%s.spr", framename ), pin, pixels );
Image_Process( &pix, 0, 0, IMAGE_PALTO24 );
}
if( groupframenum )
{
i = groupframenum - 1;
com.strncpy( spr.group[spr.numgroup].frame[i].name, framename, MAX_STRING );
spr.group[spr.numgroup].frame[i].origin[0] = -(float)LittleLong(pinframe->origin[0]);
spr.group[spr.numgroup].frame[i].origin[1] = (float)LittleLong(pinframe->origin[1]);
spr.group[spr.numgroup].frame[i].width = (float)LittleLong(pinframe->width);
spr.group[spr.numgroup].frame[i].height = (float)LittleLong(pinframe->height);
}
else
{
com.strncpy( spr.frame[framenum].name, framename, MAX_STRING );
spr.frame[framenum].origin[0] = -(float)LittleLong(pinframe->origin[0]);
spr.frame[framenum].origin[1] = (float)LittleLong(pinframe->origin[1]);
spr.frame[framenum].width = (float)LittleLong(pinframe->width);
spr.frame[framenum].height = (float)LittleLong(pinframe->height);
}
if( FS_CheckParm( "-force32" ) && spr.texFormat == SPR_INDEXALPHA )
pix->flags &= ~IMAGE_HAS_ALPHA;
FS_SaveImage( va("%s/sprites/%s.%s", gs_gamedir, framename, ext ), pix );
FS_FreeImage( pix ); // free image
// jump to next frame
return (void *)((byte *)(pinframe + 1) + pixels ); // no mipmap stored
}
qboolean SP2_ConvertFrame( const char *name, const char *ext, int framenum )
{
byte *fin;
size_t filesize;
string barename;
// store framename
FS_FileBase( name, spr.frame[framenum].name );
com.strncpy( barename, name, MAX_STRING );
FS_StripExtension( barename );
if( FS_FileExists( va("%s/%s.%s", gs_gamedir, barename, ext ), false ))
return true;
fin = FS_LoadFile( name, &filesize );
if( fin )
{
ConvPCX( barename, fin, filesize, ext );
Mem_Free( fin );
return true;
}
return false;
}
void *SPR_ConvertGroup( const char *name, const char *ext, void *pin, int framenum )
{
dspritegroup_t *pingroup;
int i, numframes;
dspriteinterval_t *pin_intervals;
void *ptemp;
pingroup = (dspritegroup_t *)pin;
numframes = LittleLong( pingroup->numframes );
pin_intervals = (dspriteinterval_t *)(pingroup + 1);
for (i = 0; i < numframes; i++)
{
spr.group[spr.numgroup].interval[i] = LittleLong( pin_intervals->interval );
pin_intervals++;
}
// update global numframes
spr.group[spr.numgroup].numframes = numframes - 1;
ptemp = (void *)pin_intervals;
for (i = 0; i < numframes; i++ )
{
ptemp = SPR_ConvertFrame( name, ext, ptemp, framenum + i, i + 1 );
}
spr.numgroup++;
return ptemp;
}
qboolean SPR_WriteScript( const char *name, const char *ext )
{
string shortname;
int i, j;
file_t *f;
FS_FileBase( name ,shortname );
if( FS_FileExists( va("%s/sprites/%s.qc", gs_gamedir, shortname ), false ))
return true;
f = FS_Open( va("%s/sprites/%s.qc", gs_gamedir, shortname ), "w", false );
if( !f )
{
Msg( "Can't create qc-script \"%s.qc\"\n", shortname );
return false;
}
// description
FS_Printf(f,"//=======================================================================\n");
FS_Printf(f,"//\t\t\tCopyright XashXT Group %s ©\n", com.timestamp( TIME_YEAR_ONLY ));
FS_Printf(f,"//\t\t\twritten by Xash Miptex Decompiler\n", name );
FS_Printf(f,"//=======================================================================\n");
// sprite header
FS_Printf(f, "\n$spritename\t%s.spr\n", name );
FS_Printf(f, "$type\t\t%s\n", SPR_RenderType());
FS_Printf(f, "$texture\t\t%s\n\n", SPR_RenderMode());
// frames description
for( i = 0; i < spr.totalframes - spr.numgroup; i++)
{
FS_Printf(f,"$load\t\t%s.%s\n", spr.frame[i].name, ext );
FS_Printf(f,"$frame\t\t0 0 %d %d", spr.frame[i].width, spr.frame[i].height );
if(!spr.frame[i].origin[0] && !spr.frame[i].origin[1]) FS_Print(f, "\n" );
else FS_Printf(f, " %.1f %d %d\n", 0.1f, spr.frame[i].origin[0],spr.frame[i].origin[1]);
}
for( i = 0; i < spr.numgroup; i++ )
{
FS_Print(f, "$group\n{\n" );
for( j = 0; j < spr.group[i].numframes; j++)
{
FS_Printf(f,"\t$load\t\t%s.%s\n", spr.group[i].frame[j].name, ext );
FS_Printf(f,"\t$frame\t\t0 0 %d %d", spr.group[i].frame[j].width, spr.group[i].frame[j].height );
if( spr.group[i].interval[j] ) FS_Printf(f, " %g", spr.group[i].interval[j] );
if(!spr.group[i].frame[j].origin[0] && !spr.group[i].frame[j].origin[1]) FS_Print(f, "\n" );
else FS_Printf(f, " %d %d\n", spr.group[i].frame[j].origin[0],spr.group[i].frame[j].origin[1]);
}
FS_Print(f, "}\n" );
}
FS_Print(f,"\n" );
FS_Close( f );
Msg( "%s.spr\n", name ); // echo to console
return true;
}
/*
============
ConvSPR
============
*/
qboolean ConvSPR( const char *name, byte *buffer, size_t filesize, const char *ext )
{
dframetype_t *pframetype = NULL;
dspriteq2_t *pinq2;
dspritehl_t *pinhl;
dspriteq1_t *pin;
short *numi;
int i;
// defaulting to q1
pin = (dspriteq1_t *)buffer;
Mem_Set( &spr, 0, sizeof( spr ));
switch( LittleLong( pin->ident ))
{
case IDSPRQ1HEADER:
switch( LittleLong( pin->version ))
{
case SPRITEQ1_VERSION:
spr.totalframes = LittleLong( pin->numframes );
spr.texFormat = SPR_ALPHTEST; // constant
spr.type = LittleLong( pin->type );
pframetype = (dframetype_t *)(pin + 1);
spr.truecolor = false;
break;
case SPRITE32_VERSION:
spr.totalframes = LittleLong( pin->numframes );
spr.texFormat = SPR_ADDITIVE; // constant
spr.type = LittleLong( pin->type );
pframetype = (dframetype_t *)(pin + 1);
spr.truecolor = true;
break;
case SPRITEHL_VERSION:
pinhl = (dspritehl_t *)buffer; // reorganize header
spr.totalframes = LittleLong( pinhl->numframes );
spr.texFormat = LittleLong( pinhl->texFormat );
spr.type = LittleLong( pinhl->type );
numi = (short *)(pinhl + 1);
spr.truecolor = false;
if( LittleShort( *numi ) == 256 )
{
byte *src = (byte *)(numi + 1);
rgbdata_t *pal = NULL;
// install palette
switch( spr.texFormat )
{
case SPR_INDEXALPHA:
case SPR_ALPHTEST:
pal = FS_LoadImage( "#transparent.pal", src, 768 );
break;
case SPR_NORMAL:
case SPR_ADDGLOW:
case SPR_ADDITIVE:
default:
pal = FS_LoadImage( "#normal.pal", src, 768 );
break;
}
// get frametype for first frame
pframetype = (dframetype_t *)(src + 768);
//FS_FreeImage( pal ); // palette installed, no reason to keep this data
}
else
{
Msg("\"%s.spr\" have invalid palette size\n", name );
return false;
}
break;
default:
Msg("\"%s.%s\" unknown version %i\n", name, "spr", LittleLong( pin->version ));
return false;
}
break;
case IDSPRQ2HEADER:
switch( LittleLong( pin->version ))
{
case SPRITEQ2_VERSION:
pinq2 = (dspriteq2_t *)buffer;
spr.totalframes = LittleLong( pinq2->numframes );
spr.texFormat = SPR_ALPHTEST; // constants
spr.type = SPR_FWD_PARALLEL;
spr.truecolor = false;
for( i = 0; i < spr.totalframes; i++ )
{
spr.frame[i].width = LittleLong( pinq2->frames[i].width );
spr.frame[i].height = LittleLong( pinq2->frames[i].height );
spr.frame[i].origin[0] = LittleLong( pinq2->frames[i].origin_x );
spr.frame[i].origin[1] = LittleLong( pinq2->frames[i].origin_y );
SP2_ConvertFrame( pinq2->frames[i].name, ext, i );
}
break;
default:
Msg("\"%s.%s\" unknown version %i\n", name, "sp2", LittleLong( pin->version ));
return false;
}
break;
default:
Msg("\"%s\" have invalid header\n", name );
return false;
}
// .SPR save frames as normal images
for( i = 0; pframetype && i < spr.totalframes; i++ )
{
frametype_t frametype = LittleLong( pframetype->type );
if( frametype == FRAME_SINGLE )
pframetype = (dframetype_t *)SPR_ConvertFrame( name, ext, (pframetype + 1), i, 0 );
else pframetype = (dframetype_t *)SPR_ConvertGroup( name, ext, (pframetype + 1), i );
}
return SPR_WriteScript( name, ext ); // write script file and done
}