medfall

A super great game engine
Log | Files | Refs

pp2.cc (9868B)


      1 #include "intrinsics.h"
      2 #include "array.h"
      3 #include "memory_arena.h"
      4 #include "linear_algebra.h"
      5 #include "aabb.h"
      6 #include "dynstr.h"
      7 #include "int_conversions.h"
      8 #include "heightmap.h"
      9 #include "decompress_bc.h"
     10 #include "autogdb.h"
     11 
     12 #include "libs/lz4/lz4.h"
     13 #include "libs/lz4/lz4hc.h"
     14 
     15 #include "libs/squish/squish.h"
     16 
     17 #include "libs/stb/stb_image.h"
     18 #include "libs/stb/stb_image_write.h"
     19 
     20 static float tan_theta( v2 a, v2 b ) {
     21 	v2 d = a - b;
     22 	return -d.y / d.x;
     23 }
     24 
     25 static void compute_row_of_horizons(
     26 	MemoryArena * arena,
     27 	const array2d< u16 > heightmap, array2d< float > horizons,
     28 	size_t y
     29 ) {
     30 	MEMARENA_SCOPED_CHECKPOINT( arena );
     31 
     32 	array< v2 > hull = memarena_push_array( arena, v2, heightmap.w );
     33 	hull[ 0 ] = v2( 0, heightmap( 0, y ) / 256.0f );
     34 	horizons( 0, y ) = 0.0f;
     35 
     36 	size_t hull_size = 1;
     37 
     38 	for( size_t x = 1; x < heightmap.w; x++ ) {
     39 		v2 p( checked_cast< float >( x ), heightmap( x, y ) / 256.0f );
     40 
     41 		while( hull_size > 1 && tan_theta( p, hull[ hull_size - 1 ] ) <= tan_theta( hull[ hull_size - 1 ], hull[ hull_size - 2 ] ) ) {
     42 			hull_size--;
     43 		}
     44 
     45 		horizons( x, y ) = max( 0.0f, tan_theta( p, hull[ hull_size - 1 ] ) );
     46 
     47 		hull[ hull_size ] = p;
     48 		hull_size++;
     49 	}
     50 }
     51 
     52 static void compute_horizons(
     53 	MemoryArena * arena,
     54 	const array2d< u16 > heightmap, array2d< float > horizons
     55 ) {
     56 	ASSERT( heightmap.w == horizons.w && heightmap.h == horizons.h );
     57 
     58 	for( size_t y = 0; y < heightmap.h; y++ ) {
     59 		compute_row_of_horizons( arena, heightmap, horizons, y );
     60 	}
     61 }
     62 
     63 static v3 simple_normal( const array2d< u16 > heightmap, size_t x, size_t y ) {
     64 	size_t x_plus_one = x + 1;
     65 	size_t x_minus_one = x - 1;
     66 	size_t y_plus_one = y + 1;
     67 	size_t y_minus_one = y - 1;
     68 
     69 	if( x == 0 ) x_minus_one = x;
     70 	if( x == heightmap.w - 1 ) x_plus_one = x;
     71 	if( y == 0 ) y_minus_one = y;
     72 	if( y == heightmap.h - 1 ) y_plus_one = y;
     73 
     74 	v3 tangent(
     75 		checked_cast< float >( x_plus_one - x_minus_one ),
     76 		0,
     77 		heightmap( x_plus_one, y ) / 256.0f - heightmap( x_minus_one, y ) / 256.0f );
     78 	v3 bitangent(
     79 		0,
     80 		checked_cast< float >( y_plus_one - y_minus_one ),
     81 		heightmap( x, y_plus_one ) / 256.0f - heightmap( x, y_minus_one ) / 256.0f );
     82 
     83 	return normalize( cross( tangent, bitangent ) );
     84 }
     85 
     86 static v3 compute_normal( const array2d< u16 > heightmap, size_t x, size_t y ) {
     87 	if( x == 0 || x == heightmap.w - 1 || y == 0 || y == heightmap.h - 1 ) {
     88 		return simple_normal( heightmap, x, y );
     89 	}
     90 
     91 	float mm = heightmap( x - 1, y - 1 ) / 256.0f;
     92 	float me = heightmap( x - 1, y + 0 ) / 256.0f;
     93 	float mp = heightmap( x - 1, y + 1 ) / 256.0f;
     94 	float em = heightmap( x + 0, y - 1 ) / 256.0f;
     95 	// float ee = heightmap( x + 0, y + 0 ) / 256.0f;
     96 	float ep = heightmap( x + 0, y + 1 ) / 256.0f;
     97 	float pm = heightmap( x + 1, y - 1 ) / 256.0f;
     98 	float pe = heightmap( x + 1, y + 0 ) / 256.0f;
     99 	float pp = heightmap( x + 1, y + 1 ) / 256.0f;
    100 
    101 	float grad_y = -( mm + 2.0f * em + pm - ( mp + 2.0f * ep + pp ) ) / 8.0f;
    102 	float grad_x = -( mm + 2.0f * me + mp - ( pm + 2.0f * pe + pp ) ) / 8.0f;
    103 
    104 	v3 tangent = v3( 1, 0, grad_x );
    105 	v3 bitangent = v3( 0, 1, grad_y );
    106 
    107 	return normalize( cross( tangent, bitangent ) );
    108 }
    109 
    110 static void compute_normals( const array2d< u16 > heightmap, array2d< v3 > normals ) {
    111 	for( size_t y = 0; y < heightmap.h; y++ ) {
    112 		for( size_t x = 0; x < heightmap.w; x++ ) {
    113 			normals( x, y ) = compute_normal( heightmap, x, y );
    114 		}
    115 	}
    116 }
    117 
    118 static size_t quadtree_max_nodes( size_t w, size_t h ) {
    119 	// with a power of 2 + 1 sized quadtree with a 2x2 bottom level it
    120 	// should be exactly 1/3 (1/4 + 1/16 + ...)
    121 	return ( ( w - 1 ) * ( h - 1 ) ) / 3;
    122 }
    123 
    124 static size_t child_idx( size_t node_idx, size_t quadrant ) {
    125 	return node_idx * 4 + quadrant + 1;
    126 }
    127 
    128 static void build_quadtree_node( const array2d< u16 > heightmap, array< QuadTreeNode > & nodes, size_t node_idx, MinMaxu32 aabb ) {
    129 	nodes[ node_idx ].min_z = U16_MAX;
    130 	nodes[ node_idx ].max_z = 0;
    131 
    132 	if( aabb.maxs.x - aabb.mins.x == 2 || aabb.maxs.y - aabb.mins.y == 2 ) {
    133 		for( size_t y = aabb.mins.y; y <= aabb.maxs.y; y++ ) {
    134 			for( size_t x = aabb.mins.x; x <= aabb.maxs.x; x++ ) {
    135 				u16 h = heightmap( x, y );
    136 				nodes[ node_idx ].min_z = min( h, nodes[ node_idx ].min_z );
    137 				nodes[ node_idx ].max_z = max( h, nodes[ node_idx ].max_z );
    138 			}
    139 		}
    140 
    141 		return;
    142 	}
    143 
    144 	u16 lo = U16_MAX;
    145 	u16 hi = 0;
    146 	for( int i = 0; i < 4; i++ ) {
    147 		build_quadtree_node( heightmap, nodes, child_idx( node_idx, i ), aabb.quadrant( i ) );
    148 		lo = min( lo, nodes[ child_idx( node_idx, i ) ].min_z );
    149 		hi = max( hi, nodes[ child_idx( node_idx, i ) ].max_z );
    150 	}
    151 
    152 	nodes[ node_idx ].min_z = lo;
    153 	nodes[ node_idx ].max_z = hi;
    154 }
    155 
    156 static void build_quadtree( const array2d< u16 > heightmap, array< QuadTreeNode > & nodes ) {
    157 	ASSERT( nodes.n >= quadtree_max_nodes( heightmap.w, heightmap.h ) );
    158 	ASSERT( heightmap.w == heightmap.h );
    159 
    160 	size_t dim = heightmap.w - 1;
    161 	MinMaxu32 aabb( v3u32( 0, 0, 0 ), v3u32( dim, dim, U16_MAX ) );
    162 	build_quadtree_node( heightmap, nodes, 0, aabb );
    163 }
    164 
    165 static void write_compressed_file( MemoryArena * arena, const DynamicString & path, const void * data, size_t len ) {
    166 	MEMARENA_SCOPED_CHECKPOINT( arena );
    167 
    168 	const int COMPRESSION_LEVEL = 6;
    169 	int lz4_bound = LZ4_compressBound( len );
    170 	char * lz4 = memarena_push_many( arena, char, lz4_bound );
    171 	int lz4_size = LZ4_compress_HC( ( const char * ) data, lz4, len, lz4_bound, COMPRESSION_LEVEL );
    172 
    173 	FILE * file = fopen( path.c_str(), "wb" );
    174 	ASSERT( file != NULL );
    175 	fwrite( lz4, 1, lz4_size, file );
    176 	ASSERT( ferror( file ) == 0 );
    177 	fclose( file );
    178 }
    179 
    180 struct RGBA { u8 r, g, b, a; };
    181 
    182 static u8 * write_compressed_texture( MemoryArena * arena, const DynamicString & path, const array2d< RGBA > data, int squish_flags ) {
    183 	int w = checked_cast< int >( data.w );
    184 	int h = checked_cast< int >( data.h );
    185 
    186 	int dxt_size = squish::GetStorageRequirements( w, h, squish_flags );
    187 	u8 * dxt = memarena_push_many( arena, u8, dxt_size );
    188 	squish::CompressImage( ( const u8 * ) data.ptr(), w, h, dxt, squish_flags );
    189 
    190 	write_compressed_file( arena, path, dxt, dxt_size );
    191 
    192 	// DynamicString png_path( "{}.png", path );
    193 	// stbi_write_png( png_path.c_str(), data.w, data.h, 4, data.ptr(), 0 );
    194 
    195 	return dxt;
    196 }
    197 
    198 int main( int argc, char ** argv ) {
    199 	install_debug_signal_handlers( true );
    200 
    201 	static u8 arena_memory[ megabytes( 512 ) ];
    202 	MemoryArena arena;
    203 	memarena_init( &arena, arena_memory, ARRAY_COUNT( arena_memory ) );
    204 
    205 	DynamicString input_path( "terrains/gta16.png" );
    206 	DynamicString output_path( "{}.parts", input_path );
    207 
    208 	printf( "decoding\n" );
    209 	int w, h;
    210 	u16 * png = ( u16 * ) stbi_load_16( input_path.c_str(), &w, &h, NULL, 1 );
    211 	ASSERT( png != NULL );
    212 
    213 	array2d< u16 > heightmap( png, w, h );
    214 	u8 * bc5_heightmap;
    215 
    216 	{
    217 		printf( "computing heightmaps\n" );
    218 
    219 		array2d< RGBA > heightmap_split = alloc_array2d< RGBA >( &arena, w, h );
    220 
    221 		for( int y = 0; y < h; y++ ) {
    222 			for( int x = 0; x < w; x++ ) {
    223 				heightmap_split( x, y ).r = heightmap( x, y ) / 256;
    224 				heightmap_split( x, y ).g = heightmap( x, y ) % 256;
    225 				heightmap_split( x, y ).b = 0;
    226 				heightmap_split( x, y ).a = 255;
    227 			}
    228 		}
    229 
    230 		printf( "compressing\n" );
    231 		DynamicString heightmap_path( "{}/heightmap.bc5.lz4", output_path );
    232 		bc5_heightmap = write_compressed_texture( &arena, heightmap_path, heightmap_split, squish::kBc5 );
    233 	}
    234 
    235 	{
    236 		MEMARENA_SCOPED_CHECKPOINT( &arena );
    237 		printf( "computing normalmap\n" );
    238 
    239 		array2d< v3 > normalmap_v3 = alloc_array2d< v3 >( &arena, w, h );
    240 		array2d< RGBA > normalmap = alloc_array2d< RGBA >( &arena, w, h );
    241 
    242 		compute_normals( heightmap, normalmap_v3 );
    243 		for( int y = 0; y < h; y++ ) {
    244 			for( int x = 0; x < w; x++ ) {
    245 				normalmap( x, y ).r = quantize01( ( normalmap_v3( x, y ).x + 1.0f ) / 2.0f, 8 );
    246 				normalmap( x, y ).g = quantize01( ( normalmap_v3( x, y ).y + 1.0f ) / 2.0f, 8 );
    247 				normalmap( x, y ).b = 0;
    248 				normalmap( x, y ).a = 255;
    249 			}
    250 		}
    251 
    252 		printf( "compressing\n" );
    253 		DynamicString normalmap_path( "{}/normalmap.bc5.lz4", output_path );
    254 		write_compressed_texture( &arena, normalmap_path, normalmap, squish::kBc5 );
    255 	}
    256 
    257 	{
    258 		MEMARENA_SCOPED_CHECKPOINT( &arena );
    259 		printf( "computing horizonmap\n" );
    260 
    261 		array2d< float > horizonmap_float = alloc_array2d< float >( &arena, w, h );
    262 		array2d< RGBA > horizonmap = alloc_array2d< RGBA >( &arena, w, h );
    263 
    264 		compute_horizons( &arena, heightmap, horizonmap_float );
    265 		for( int y = 0; y < h; y++ ) {
    266 			for( int x = 0; x < w; x++ ) {
    267 				float theta01 = atanf( horizonmap_float( x, y ) ) * 2.0f / PI;
    268 				horizonmap( x, y ).r = quantize01( theta01, 8 );
    269 				horizonmap( x, y ).g = 0;
    270 				horizonmap( x, y ).b = 0;
    271 				horizonmap( x, y ).a = 255;
    272 			}
    273 		}
    274 
    275 		printf( "compressing\n" );
    276 		DynamicString horizonmap_path( "{}/horizonmap.bc4.lz4", output_path );
    277 		write_compressed_texture( &arena, horizonmap_path, horizonmap, squish::kBc4 );
    278 	}
    279 
    280 	{
    281 		MEMARENA_SCOPED_CHECKPOINT( &arena );
    282 		printf( "computing quadtree\n" );
    283 
    284 		array2d< u16 > heightmap1 = alloc_array2d< u16 >( &arena, heightmap.w + 1, heightmap.h + 1 );
    285 
    286 		// TODO: this should just use bc5_to_heightmap once the Heightmap stuff is cleaned up
    287 		{
    288 			MEMARENA_SCOPED_CHECKPOINT( &arena );
    289 			array2d< v2 > rg = alloc_array2d< v2 >( &arena, heightmap.w, heightmap.h );
    290 			decompress_bc5( rg, bc5_heightmap );
    291 
    292 			v2 zero( 0, 0 );
    293 			for( u32 y = 0; y < heightmap1.h; y++ ) {
    294 				for( u32 x = 0; x < heightmap1.w; x++ ) {
    295 					float r = rg.try_get( x, y, zero ).x;
    296 					float g = rg.try_get( x, y, zero ).y;
    297 					heightmap1( x, y ) = u16( ( r * 256.0f + g ) * 255.0f );
    298 				}
    299 			}
    300 		}
    301 
    302 		size_t num_nodes = quadtree_max_nodes( heightmap1.w, heightmap1.h );
    303 		array< QuadTreeNode > nodes = alloc_array< QuadTreeNode >( &arena, num_nodes );
    304 
    305 		build_quadtree( heightmap1, nodes );
    306 
    307 		printf( "compressing\n" );
    308 		DynamicString quadtree_path( "{}/quadtree.lz4", output_path );
    309 		write_compressed_file( &arena, quadtree_path, nodes.ptr(), nodes.num_bytes() );
    310 	}
    311 
    312 	stbi_image_free( png );
    313 
    314 	return 0;
    315 }