@ -70,7 +70,7 @@ static bool generate_clut(const Color palette[PALETTE_SIZE], tim::PARAM* outTim)
for ( int c = 0 ; c < PALETTE_SIZE - 1 ; + + c ) // Final palette entry is for alpha masking
for ( int c = 0 ; c < PALETTE_SIZE - 1 ; + + c ) // Final palette entry is for alpha masking
{
{
unsigned char color [ 3 ] ;
unsigned char color [ 3 ] ;
desaturate ( & palette [ c ] . rgb . r , color ) ;
desaturate ( palette [ c ] . channel , color ) ;
clut [ c ] . r = color [ 0 ] > > 3 ;
clut [ c ] . r = color [ 0 ] > > 3 ;
clut [ c ] . g = color [ 1 ] > > 3 ;
clut [ c ] . g = color [ 1 ] > > 3 ;
@ -95,6 +95,79 @@ static bool generate_clut(const Color palette[PALETTE_SIZE], tim::PARAM* outTim)
return true ;
return true ;
}
}
static int color_hash ( const Color & color )
{
const int divider = 24 ;
int r = ( int ) roundf ( ( float ) color . rgb . r / divider ) ;
int g = ( int ) roundf ( ( float ) color . rgb . g / divider ) ;
int b = ( int ) roundf ( ( float ) color . rgb . b / divider ) ;
return ( b < < 16 ) | ( g < < 8 ) | r ;
}
struct ColorAccumulate
{
uint32_t r , g , b ;
size_t count ;
ColorAccumulate ( ) : r ( 0 ) , g ( 0 ) , b ( 0 ) , count ( 0 ) { }
ColorAccumulate ( const Color & color ) : r ( color . rgb . r ) , g ( color . rgb . g ) , b ( color . rgb . b ) , count ( 1 )
{
}
Color getAverage ( )
{
Color result ;
result . rgb . r = ( unsigned char ) ( r / count ) ;
result . rgb . g = ( unsigned char ) ( g / count ) ;
result . rgb . b = ( unsigned char ) ( b / count ) ;
return result ;
}
} ;
static void analyze_texture ( unsigned char * texBytes , int numBytes , const Color palette [ PALETTE_SIZE ] , TextureDescriptor & outTexture )
{
uint64_t rSum = 0 , gSum = 0 , bSum = 0 ;
std : : unordered_map < int , ColorAccumulate > colorCount ;
ColorAccumulate dominant ;
for ( int i = 0 ; i < numBytes ; + + i )
{
unsigned char b = texBytes [ i ] ;
Color color = palette [ b ] ;
rSum + = color . rgb . r ;
gSum + = color . rgb . g ;
bSum + = color . rgb . b ;
int key = color_hash ( color ) ;
const auto iter = colorCount . find ( key ) ;
if ( iter ! = colorCount . end ( ) )
{
iter - > second . r + = color . rgb . r ;
iter - > second . g + = color . rgb . g ;
iter - > second . b + = color . rgb . b ;
iter - > second . count + + ;
}
else
{
colorCount [ key ] = ColorAccumulate ( color ) ;
}
if ( colorCount [ key ] . count > dominant . count )
dominant = colorCount [ key ] ;
}
outTexture . averageColor . rgb . r = ( unsigned char ) ( rSum / numBytes ) ;
outTexture . averageColor . rgb . g = ( unsigned char ) ( gSum / numBytes ) ;
outTexture . averageColor . rgb . b = ( unsigned char ) ( bSum / numBytes ) ;
outTexture . dominantColor = dominant . getAverage ( ) ;
desaturate ( outTexture . averageColor . channel , outTexture . averageColor . channel ) ;
desaturate ( outTexture . dominantColor . channel , outTexture . dominantColor . channel ) ;
}
bool process_textures ( const world_t * world , TextureList & outTextures ) // TODO: return TextureDescriptor structs, including average texture color
bool process_textures ( const world_t * world , TextureList & outTextures ) // TODO: return TextureDescriptor structs, including average texture color
{
{
using spaces_type = rectpack2D : : empty_spaces < false > ;
using spaces_type = rectpack2D : : empty_spaces < false > ;
@ -137,6 +210,8 @@ bool process_textures(const world_t* world, TextureList& outTextures) // TODO: r
ps1mip + + ;
ps1mip + + ;
// TODO: make an exception for the QUAKE sign that's displayed on the start map. It needs to be bold and detailed, but it's too wide for the PS1 to address at full resolution, so it'll need to be broken up.
// TODO: make an exception for the QUAKE sign that's displayed on the start map. It needs to be bold and detailed, but it's too wide for the PS1 to address at full resolution, so it'll need to be broken up.
// The brush that displays this texture is actually broken up into two parts: one that's 224 texels wide, and one that's 64 texels wide. We can take advantage of that.
// We just need to turn that second part into a separate texture entry, and patch the texture ID and UVs on the face when we encounter it.
if ( ! strcmp ( miptex - > name , " quake " ) )
if ( ! strcmp ( miptex - > name , " quake " ) )
ps1mip - - ;
ps1mip - - ;
@ -192,6 +267,8 @@ bool process_textures(const world_t* world, TextureList& outTextures) // TODO: r
if ( * outName = = ' * ' | | * outName = = ' + ' )
if ( * outName = = ' * ' | | * outName = = ' + ' )
outName + + ;
outName + + ;
TextureDescriptor tex = { 0 } ;
for ( int mipLevel = 0 ; mipLevel < 4 ; + + mipLevel )
for ( int mipLevel = 0 ; mipLevel < 4 ; + + mipLevel )
{
{
unsigned char * texBytes = world - > textures [ texNum * 4 + mipLevel ] ;
unsigned char * texBytes = world - > textures [ texNum * 4 + mipLevel ] ;
@ -205,7 +282,6 @@ bool process_textures(const world_t* world, TextureList& outTextures) // TODO: r
memcpy_s ( ( unsigned char * ) outTim . imgData + ( ( rectangle . y + y ) * result_size . w + rectangle . x ) , rectangle . w * sizeof ( unsigned char ) , texBytes + ( y * rectangle . w ) , rectangle . w * sizeof ( unsigned char ) ) ;
memcpy_s ( ( unsigned char * ) outTim . imgData + ( ( rectangle . y + y ) * result_size . w + rectangle . x ) , rectangle . w * sizeof ( unsigned char ) , texBytes + ( y * rectangle . w ) , rectangle . w * sizeof ( unsigned char ) ) ;
}
}
TextureDescriptor tex = { 0 } ;
tex . w = ( u_char ) rectangle . w ;
tex . w = ( u_char ) rectangle . w ;
tex . h = ( u_char ) rectangle . h ;
tex . h = ( u_char ) rectangle . h ;
@ -216,10 +292,13 @@ bool process_textures(const world_t* world, TextureList& outTextures) // TODO: r
tex . uoffs = ( u_char ) ( ( x % 64 ) < < ( 2 - outTim . format ) ) ;
tex . uoffs = ( u_char ) ( ( x % 64 ) < < ( 2 - outTim . format ) ) ;
tex . voffs = ( u_char ) ( y & 0xFF ) ;
tex . voffs = ( u_char ) ( y & 0xFF ) ;
// TODO: animated textures
// TODO: animated textures
outTextures . push_back ( tex ) ;
}
}
}
}
// Use the smallest mip level for analysis, should be good enough
analyze_texture ( world - > textures [ texNum * 4 + 3 ] , ( miptex - > width > > 3 ) * ( miptex - > height > > 3 ) , palette , tex ) ;
outTextures . push_back ( tex ) ;
}
}
sprintf_s ( path , _MAX_PATH , " atlas-%s.tim " , world - > name ) ;
sprintf_s ( path , _MAX_PATH , " atlas-%s.tim " , world - > name ) ;