@ -25,6 +25,14 @@ Keep in mind that the coordinates will be rounded down to the next lowest textur
( ( ( ( y ) / 512 ) & 1 ) < < 11 ) \
)
# define getTexWindow(x, y, w, h) ( \
0xe2000000 | \
( ( ~ ( ( w ) - 1 ) & 0xFF ) ) > > 3 | \
( ( ( ~ ( ( h ) - 1 ) & 0xFF ) > > 3 ) < < 5 ) | \
( ( ( ( x ) & 0xFF ) > > 3 ) < < 10 ) | \
( ( ( ( y ) & 0xFF ) > > 3 ) < < 15 ) \
)
// A straight conversion of the Quake palette colors comes out looking rather over-saturated.
// So we desaturate the palette ahead of time to more closely match the original Quake's look.
// Yoinked from: https://stackoverflow.com/questions/13328029/how-to-desaturate-a-color
@ -170,11 +178,11 @@ static void analyze_texture(unsigned char* texBytes, int numBytes, const Color p
desaturate ( outTexture . dominantColor . channel , outTexture . dominantColor . channel ) ;
}
bool process_textures ( const world_t * world , TextureList & outTextures )
{
using spaces_type = rectpack2D : : empty_spaces < false > ;
using rect_type = rectpack2D : : output_rect_t < spaces_type > ;
using spaces_type = rectpack2D : : empty_spaces < false > ;
using rect_type = rectpack2D : : output_rect_t < spaces_type > ;
static rectpack2D : : rect_wh pack_textures ( const std : : map < int , miptex_t * > & textures , const rectpack2D : : rect_wh & max_bin , std : : vector < rect_type > & outRectangles )
{
auto report_successful = [ ] ( rect_type & ) {
return rectpack2D : : callback_result : : CONTINUE_PACKING ;
} ;
@ -184,21 +192,11 @@ bool process_textures(const world_t* world, TextureList& outTextures)
return rectpack2D : : callback_result : : ABORT_PACKING ;
} ;
const auto max_bin = rectpack2D : : rect_wh ( 1024 , 256 ) ; // 8-bit textures take up half the horizontal space so this is 512x256 in practice, or a quarter of the PS1's VRAM allocation.
const auto discard_step = - 4 ;
printf ( " Finding best texture packing... \n " ) ;
std : : vector < rect_type > rectangles ;
// Try some texture packing and see if we fit inside the PS1's VRAM
for ( int texNum = 0 ; texNum < world - > mipheader . numtex ; + + texNum )
for ( auto texIter = textures . cbegin ( ) ; texIter ! = textures . cend ( ) ; + + texIter )
{
miptex_t * miptex = & world - > miptexes [ texNum ] ;
if ( miptex - > name [ 0 ] = = ' \0 ' ) // Weird edge case on N64START.bsp, corrupt data perhaps?
miptex - > width = miptex - > height = 0 ;
//printf("Texture %d (%dx%d): %.16s\n", texNum, miptex->width, miptex->height, miptex->name);
const miptex_t * miptex = texIter - > second ;
// Shrink the larger textures, but keep smaller ones at their original size
int ps1mip = miptex - > width > 64 | | miptex - > height > 64 ? 1 : 0 ;
@ -220,13 +218,13 @@ bool process_textures(const world_t* world, TextureList& outTextures)
ps1mip - - ;
if ( strcmp ( miptex - > name , " clip " ) & & strcmp ( miptex - > name , " trigger " ) )
r ectangles. emplace_back ( rectpack2D : : rect_xywh ( 0 , 0 , miptex - > width > > ps1mip , miptex - > height > > ps1mip ) ) ;
outR ectangles. emplace_back ( rectpack2D : : rect_xywh ( 0 , 0 , miptex - > width > > ps1mip , miptex - > height > > ps1mip ) ) ;
else
r ectangles. emplace_back ( rectpack2D : : rect_xywh ( 0 , 0 , miptex - > width > > 3 , miptex - > width > > 3 ) ) ; // Add the lowest mip level so that it at least gets included in the final texture list, and we don't mess up the texture IDs
outR ectangles. emplace_back ( rectpack2D : : rect_xywh ( 0 , 0 , miptex - > width > > 3 , miptex - > width > > 3 ) ) ; // Add the lowest mip level so that it at least gets included in the final texture list, and we don't mess up the texture IDs
}
const auto result_size = rectpack2D : : find_best_packing < spaces_type > (
r ectangles,
return rectpack2D : : find_best_packing < spaces_type > (
outR ectangles,
rectpack2D : : make_finder_input (
max_bin ,
discard_step ,
@ -235,9 +233,15 @@ bool process_textures(const world_t* world, TextureList& outTextures)
rectpack2D : : flipping_option : : DISABLED
)
) ;
}
printf ( " %d textures. Packed texture atlas size: %d x %d \n " , world - > mipheader . numtex , result_size . w , result_size . h ) ;
static void build_atlas ( const std : : vector < miptex_t * > & textures , const std : : vector < rect_type > & rectangles , const rectpack2D : : rect_wh & size , unsigned char * outAtlas , TextureList & outTextures )
{
}
bool process_textures ( const world_t * world , TextureList & outTextures )
{
// Construct palette CLUT and start building the output TIM structure
Color palette [ PALETTE_SIZE ] ;
load_palette ( " palette.lmp " , palette ) ;
@ -249,6 +253,29 @@ bool process_textures(const world_t* world, TextureList& outTextures)
outTim . clutYoffs = 0 ;
generate_clut ( palette , & outTim ) ;
const auto max_bin = rectpack2D : : rect_wh ( 1024 , 256 ) ; // 8-bit textures take up half the horizontal space so this is 512x256 in practice, or a quarter of the PS1's VRAM allocation.
printf ( " Finding best texture packing... \n " ) ;
std : : map < int , miptex_t * > textures ;
std : : vector < rect_type > rectangles ;
// Try some texture packing and see if we fit inside the PS1's VRAM
for ( int texNum = 0 ; texNum < world - > mipheader . numtex ; + + texNum )
{
miptex_t * miptex = & world - > miptexes [ texNum ] ;
if ( miptex - > name [ 0 ] = = ' \0 ' ) // Weird edge case on N64START.bsp, corrupt data perhaps?
miptex - > width = miptex - > height = 0 ;
//printf("Texture %d (%dx%d): %.16s\n", texNum, miptex->width, miptex->height, miptex->name);
textures [ texNum ] = miptex ;
}
const auto result_size = pack_textures ( textures , max_bin , rectangles ) ;
printf ( " %d textures. Packed texture atlas size: %d x %d \n " , world - > mipheader . numtex , result_size . w , result_size . h ) ;
outTim . imgWidth = result_size . w ;
outTim . imgHeight = result_size . h ;
outTim . imgData = malloc ( result_size . w * result_size . h * sizeof ( unsigned char ) ) ;
@ -259,8 +286,6 @@ bool process_textures(const world_t* world, TextureList& outTextures)
printf ( " Constructing texture atlas... \n " ) ;
std : : unordered_map < std : : string , std : : unordered_map < int , int > > animationFrames ;
// Try to construct the texture atlas, see what we get
for ( int texNum = 0 ; texNum < world - > mipheader . numtex ; + + texNum )
{
@ -295,7 +320,21 @@ bool process_textures(const world_t* world, TextureList& outTextures)
tex . ps1tex . tpage = getTPage ( outTim . format , 0 , x , y ) ;
tex . uoffs = ( u_char ) ( ( x % 64 ) < < ( 2 - outTim . format ) ) ;
tex . voffs = ( u_char ) ( y & 0xFF ) ;
tex . ps1tex . twin = getTexWindow ( tex . uoffs , tex . voffs , tex . w , tex . h ) ; // TODO: figure out the right offsets that are multiples of w and h
}
}
// 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 ) ;
}
// Identify animated textures
std : : unordered_map < std : : string , std : : unordered_map < int , int > > animationFrames ;
for ( int texNum = 0 ; texNum < world - > mipheader . numtex ; + + texNum )
{
miptex_t * miptex = & world - > miptexes [ texNum ] ;
if ( miptex - > name [ 0 ] = = ' + ' )
{
// Animated texture
@ -316,13 +355,6 @@ bool process_textures(const world_t* world, TextureList& outTextures)
}
}
}
}
// 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 ) ;
}
// Link animated texture frames together
for ( auto animIter = animationFrames . cbegin ( ) ; animIter ! = animationFrames . cend ( ) ; + + animIter )
@ -349,3 +381,17 @@ bool process_textures(const world_t* world, TextureList& outTextures)
free ( outTim . clutData ) ;
return true ;
}
bool texture_isRepeatable ( miptex_t * miptex )
{
// Check if the texture is square
if ( miptex - > width ! = miptex - > height )
return false ;
// Check if width and height are powers of two (and a multiple of 8)
unsigned int pot = 8 ;
while ( pot < miptex - > width )
pot < < = 1 ;
return pot = = miptex - > width ;
}