diff --git a/public/atlas.c b/public/atlas.c new file mode 100644 index 00000000..f4c7e856 --- /dev/null +++ b/public/atlas.c @@ -0,0 +1,62 @@ +/* +atlas.c - generic 2D atlas packer (strip-based) +Copyright (C) 1997 id Software +Copyright (C) Xash3D and Xash3D FWGS developers + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "atlas.h" + +qboolean Atlas_AllocBlock( atlas_t *atlas, int w, int h, int *x, int *y ) +{ + int i, j; + int best, best2; + int size = atlas->size; + + best = size; + + for( i = 0; i <= size - w; ) + { + best2 = 0; + + for( j = 0; j < w; j++ ) + { + if( atlas->allocated[i + j] >= best ) + break; + if( atlas->allocated[i + j] > best2 ) + best2 = atlas->allocated[i + j]; + } + + if( j == w ) + { + *x = i; + *y = best = best2; + if( best == 0 ) + break; + i++; + } + else + { + i += j + 1; + } + } + + if( best + h > size ) + return false; + + for( i = 0; i < w; i++ ) + atlas->allocated[*x + i] = best + h; + + if( best + h > atlas->max_height ) + atlas->max_height = best + h; + + return true; +} diff --git a/public/atlas.h b/public/atlas.h new file mode 100644 index 00000000..f778ff1e --- /dev/null +++ b/public/atlas.h @@ -0,0 +1,41 @@ +/* +atlas.h - generic 2D atlas packer (strip-based) +Copyright (C) 1997 id Software +Copyright (C) Xash3D and Xash3D FWGS developers + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef ATLAS_H +#define ATLAS_H + +#include +#include "xash3d_types.h" + +// maximum atlas dimension +#define ATLAS_MAX_SIZE 1024 + +typedef struct atlas_s +{ + int allocated[ATLAS_MAX_SIZE]; + int size; + int max_height; +} atlas_t; + +static inline void Atlas_Init( atlas_t *atlas, int size ) +{ + memset( atlas->allocated, 0, sizeof( atlas->allocated )); + atlas->size = size; + atlas->max_height = 0; +} + +qboolean Atlas_AllocBlock( atlas_t *atlas, int w, int h, int *x, int *y ); + +#endif // ATLAS_H diff --git a/public/tests/test_atlas.c b/public/tests/test_atlas.c new file mode 100644 index 00000000..342a56ff --- /dev/null +++ b/public/tests/test_atlas.c @@ -0,0 +1,131 @@ +#include "atlas.h" + +static int Test_BasicAlloc( void ) +{ + atlas_t atlas; + int x, y; + + Atlas_Init( &atlas, 128 ); + + if( !Atlas_AllocBlock( &atlas, 16, 16, &x, &y )) + return 1; + + if( x != 0 || y != 0 ) + return 2; + + if( atlas.max_height != 16 ) + return 3; + + if( !Atlas_AllocBlock( &atlas, 16, 16, &x, &y )) + return 4; + + if( x != 16 || y != 0 ) + return 5; + + return 0; +} + +static int Test_Full( void ) +{ + atlas_t atlas; + int x, y; + + Atlas_Init( &atlas, 64 ); + + if( !Atlas_AllocBlock( &atlas, 64, 64, &x, &y )) + return 1; + + if( Atlas_AllocBlock( &atlas, 1, 1, &x, &y )) + return 2; + + return 0; +} + +static int Test_Packing( void ) +{ + atlas_t atlas; + int x, y; + + Atlas_Init( &atlas, 64 ); + + if( !Atlas_AllocBlock( &atlas, 32, 16, &x, &y )) + return 1; + + if( !Atlas_AllocBlock( &atlas, 32, 16, &x, &y )) + return 2; + + if( x != 32 || y != 0 ) + return 3; + + if( !Atlas_AllocBlock( &atlas, 64, 16, &x, &y )) + return 4; + + if( y != 16 ) + return 5; + + return 0; +} + +static int Test_Reset( void ) +{ + atlas_t atlas; + int x, y; + + Atlas_Init( &atlas, 64 ); + + if( !Atlas_AllocBlock( &atlas, 64, 64, &x, &y )) + return 1; + + Atlas_Init( &atlas, 64 ); + + if( !Atlas_AllocBlock( &atlas, 64, 64, &x, &y )) + return 2; + + if( x != 0 || y != 0 ) + return 3; + + return 0; +} + +static int Test_TooLarge( void ) +{ + atlas_t atlas; + int x, y; + + Atlas_Init( &atlas, 64 ); + + if( Atlas_AllocBlock( &atlas, 65, 1, &x, &y )) + return 1; + + if( Atlas_AllocBlock( &atlas, 1, 65, &x, &y )) + return 2; + + return 0; +} + +int main( void ) +{ + int ret; + + ret = Test_BasicAlloc(); + if( ret > 0 ) + return ret; + + ret = Test_Full(); + if( ret > 0 ) + return ret + 16; + + ret = Test_Packing(); + if( ret > 0 ) + return ret + 32; + + ret = Test_Reset(); + if( ret > 0 ) + return ret + 48; + + ret = Test_TooLarge(); + if( ret > 0 ) + return ret + 64; + + return 0; +} diff --git a/public/wscript b/public/wscript index 8228db0f..76495b08 100644 --- a/public/wscript +++ b/public/wscript @@ -157,6 +157,7 @@ def build(bld): 'efp': 'tests/test_efp.c', 'atoi': 'tests/test_atoi.c', 'parsefile': 'tests/test_parsefile.c', + 'atlas': 'tests/test_atlas.c', } for i in tests: diff --git a/ref/gl/gl_rsurf.c b/ref/gl/gl_rsurf.c index d0217d30..38c4a053 100644 --- a/ref/gl/gl_rsurf.c +++ b/ref/gl/gl_rsurf.c @@ -16,6 +16,7 @@ GNU General Public License for more details. #include "gl_local.h" #include "xash3d_mathlib.h" #include "mod_local.h" +#include "atlas.h" #define TURBSCALE ( 256.0f / ( M_PI2 )) @@ -27,8 +28,7 @@ static const float r_turbsin[] = typedef struct { - int allocated[BLOCK_SIZE_MAX]; - int max_height; // maximum height currently in use in the block + atlas_t atlas; int current_lightmap_texture; msurface_t *dynamic_surfaces; msurface_t *lightmap_surfaces[MAX_LIGHTMAPS]; @@ -646,60 +646,17 @@ static void R_AddDynamicLights( const msurface_t *surf ) */ static void LM_InitBlock( void ) { - memset( gl_lms.allocated, 0, sizeof( gl_lms.allocated )); - gl_lms.max_height = 0; + Atlas_Init( &gl_lms.atlas, BLOCK_SIZE ); } -static int LM_AllocBlock( int w, int h, int *x, int *y ) +static qboolean LM_AllocBlock( int w, int h, int *x, int *y ) { - int i, j; - int best, best2; - - best = BLOCK_SIZE; - - for( i = 0; i < BLOCK_SIZE - w; ) - { - best2 = 0; - - for( j = 0; j < w; j++ ) - { - if( gl_lms.allocated[i+j] >= best ) - break; - if( gl_lms.allocated[i+j] > best2 ) - best2 = gl_lms.allocated[i+j]; - } - - if( j == w ) - { - // this is a valid spot - *x = i; - *y = best = best2; - if( best == 0 ) - break; // height 0 is optimal, can't do better - i++; - } - else - { - // allocated[i+j] was too tall — no position in [i, i+j] can work - i += j + 1; - } - } - - if( best + h > BLOCK_SIZE ) - return false; - - for( i = 0; i < w; i++ ) - gl_lms.allocated[*x + i] = best + h; - - if( best + h > gl_lms.max_height ) - gl_lms.max_height = best + h; - - return true; + return Atlas_AllocBlock( &gl_lms.atlas, w, h, x, y ); } static void LM_UploadDynamicBlock( void ) { - pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, gl_lms.max_height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer ); + pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, gl_lms.atlas.max_height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer ); } static void LM_UploadBlock( qboolean dynamic )