medfall

A super great game engine
Log | Files | Refs

pp.cc (11624B)


      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 "rng/pcg.h"
     13 
     14 #include "libs/lz4/lz4.h"
     15 #include "libs/lz4/lz4hc.h"
     16 
     17 #include "libs/squish/squish.h"
     18 
     19 #include "libs/stb/stb_image.h"
     20 #include "libs/stb/stb_image_write.h"
     21 
     22 static float tan_theta( v2 a, v2 b ) {
     23 	v2 d = a - b;
     24 	return -d.y / d.x;
     25 }
     26 
     27 static void compute_row_of_horizons(
     28 	MemoryArena * arena,
     29 	const array2d< u16 > heightmap, array2d< float > horizons,
     30 	size_t y
     31 ) {
     32 	MEMARENA_SCOPED_CHECKPOINT( arena );
     33 
     34 	array< v2 > hull = memarena_push_array( arena, v2, heightmap.w );
     35 	hull[ 0 ] = v2( 0, heightmap( 0, y ) / 256.0f );
     36 	horizons( 0, y ) = 0.0f;
     37 
     38 	size_t hull_size = 1;
     39 
     40 	for( size_t x = 1; x < heightmap.w; x++ ) {
     41 		v2 p( checked_cast< float >( x ), heightmap( x, y ) / 256.0f );
     42 
     43 		while( hull_size > 1 && tan_theta( p, hull[ hull_size - 1 ] ) <= tan_theta( hull[ hull_size - 1 ], hull[ hull_size - 2 ] ) ) {
     44 			hull_size--;
     45 		}
     46 
     47 		horizons( x, y ) = max( 0.0f, tan_theta( p, hull[ hull_size - 1 ] ) );
     48 
     49 		hull[ hull_size ] = p;
     50 		hull_size++;
     51 	}
     52 }
     53 
     54 static void compute_horizons(
     55 	MemoryArena * arena,
     56 	const array2d< u16 > heightmap, array2d< float > horizons
     57 ) {
     58 	ASSERT( heightmap.w == horizons.w && heightmap.h == horizons.h );
     59 
     60 	for( size_t y = 0; y < heightmap.h; y++ ) {
     61 		compute_row_of_horizons( arena, heightmap, horizons, y );
     62 	}
     63 }
     64 
     65 static v3 simple_normal( const array2d< u16 > heightmap, size_t x, size_t y ) {
     66 	size_t x_plus_one = x + 1;
     67 	size_t x_minus_one = x - 1;
     68 	size_t y_plus_one = y + 1;
     69 	size_t y_minus_one = y - 1;
     70 
     71 	if( x == 0 ) x_minus_one = x;
     72 	if( x == heightmap.w - 1 ) x_plus_one = x;
     73 	if( y == 0 ) y_minus_one = y;
     74 	if( y == heightmap.h - 1 ) y_plus_one = y;
     75 
     76 	v3 tangent(
     77 		checked_cast< float >( x_plus_one - x_minus_one ),
     78 		0,
     79 		heightmap( x_plus_one, y ) / 256.0f - heightmap( x_minus_one, y ) / 256.0f );
     80 	v3 bitangent(
     81 		0,
     82 		checked_cast< float >( y_plus_one - y_minus_one ),
     83 		heightmap( x, y_plus_one ) / 256.0f - heightmap( x, y_minus_one ) / 256.0f );
     84 
     85 	return normalize( cross( tangent, bitangent ) );
     86 }
     87 
     88 static v3 compute_normal( const array2d< u16 > heightmap, size_t x, size_t y ) {
     89 	if( x == 0 || x == heightmap.w - 1 || y == 0 || y == heightmap.h - 1 ) {
     90 		return simple_normal( heightmap, x, y );
     91 	}
     92 
     93 	float mm = heightmap( x - 1, y - 1 ) / 256.0f;
     94 	float me = heightmap( x - 1, y + 0 ) / 256.0f;
     95 	float mp = heightmap( x - 1, y + 1 ) / 256.0f;
     96 	float em = heightmap( x + 0, y - 1 ) / 256.0f;
     97 	// float ee = heightmap( x + 0, y + 0 ) / 256.0f;
     98 	float ep = heightmap( x + 0, y + 1 ) / 256.0f;
     99 	float pm = heightmap( x + 1, y - 1 ) / 256.0f;
    100 	float pe = heightmap( x + 1, y + 0 ) / 256.0f;
    101 	float pp = heightmap( x + 1, y + 1 ) / 256.0f;
    102 
    103 	float grad_y = -( mm + 2.0f * em + pm - ( mp + 2.0f * ep + pp ) ) / 8.0f;
    104 	float grad_x = -( mm + 2.0f * me + mp - ( pm + 2.0f * pe + pp ) ) / 8.0f;
    105 
    106 	v3 tangent = v3( 1, 0, grad_x );
    107 	v3 bitangent = v3( 0, 1, grad_y );
    108 
    109 	return normalize( cross( tangent, bitangent ) );
    110 }
    111 
    112 static void compute_normals( const array2d< u16 > heightmap, array2d< v3 > normals ) {
    113 	for( size_t y = 0; y < heightmap.h; y++ ) {
    114 		for( size_t x = 0; x < heightmap.w; x++ ) {
    115 			normals( x, y ) = compute_normal( heightmap, x, y );
    116 		}
    117 	}
    118 }
    119 
    120 static size_t quadtree_max_nodes( size_t w, size_t h ) {
    121 	// with a power of 2 + 1 sized quadtree with a 2x2 bottom level it
    122 	// should be exactly 1/3 (1/4 + 1/16 + ...)
    123 	return ( ( w - 1 ) * ( h - 1 ) ) / 3;
    124 }
    125 
    126 static size_t child_idx( size_t node_idx, size_t quadrant ) {
    127 	return node_idx * 4 + quadrant + 1;
    128 }
    129 
    130 static void build_quadtree_node( const array2d< u16 > heightmap, array< QuadTreeNode > & nodes, size_t node_idx, MinMaxu32 aabb ) {
    131 	nodes[ node_idx ].min_z = U16_MAX;
    132 	nodes[ node_idx ].max_z = 0;
    133 
    134 	if( aabb.maxs.x - aabb.mins.x == 2 || aabb.maxs.y - aabb.mins.y == 2 ) {
    135 		for( size_t y = aabb.mins.y; y <= aabb.maxs.y; y++ ) {
    136 			for( size_t x = aabb.mins.x; x <= aabb.maxs.x; x++ ) {
    137 				u16 h = heightmap( x, y );
    138 				nodes[ node_idx ].min_z = min( h, nodes[ node_idx ].min_z );
    139 				nodes[ node_idx ].max_z = max( h, nodes[ node_idx ].max_z );
    140 			}
    141 		}
    142 
    143 		return;
    144 	}
    145 
    146 	u16 lo = U16_MAX;
    147 	u16 hi = 0;
    148 	for( int i = 0; i < 4; i++ ) {
    149 		build_quadtree_node( heightmap, nodes, child_idx( node_idx, i ), aabb.quadrant( i ) );
    150 		lo = min( lo, nodes[ child_idx( node_idx, i ) ].min_z );
    151 		hi = max( hi, nodes[ child_idx( node_idx, i ) ].max_z );
    152 	}
    153 
    154 	nodes[ node_idx ].min_z = lo;
    155 	nodes[ node_idx ].max_z = hi;
    156 }
    157 
    158 static void build_quadtree( const array2d< u16 > heightmap, array< QuadTreeNode > & nodes ) {
    159 	ASSERT( nodes.n >= quadtree_max_nodes( heightmap.w, heightmap.h ) );
    160 	ASSERT( heightmap.w == heightmap.h );
    161 
    162 	size_t dim = heightmap.w - 1;
    163 	MinMaxu32 aabb( v3u32( 0, 0, 0 ), v3u32( dim, dim, U16_MAX ) );
    164 	build_quadtree_node( heightmap, nodes, 0, aabb );
    165 }
    166 
    167 static void write_compressed_file( MemoryArena * arena, const DynamicString & path, const void * data, size_t len ) {
    168 	MEMARENA_SCOPED_CHECKPOINT( arena );
    169 
    170 	const int COMPRESSION_LEVEL = LZ4HC_DEFAULT_CLEVEL;
    171 	int lz4_bound = LZ4_compressBound( len );
    172 	char * lz4 = memarena_push_many( arena, char, lz4_bound );
    173 	int lz4_size = LZ4_compress_HC( ( const char * ) data, lz4, len, lz4_bound, COMPRESSION_LEVEL );
    174 
    175 	FILE * file = fopen( path.c_str(), "wb" );
    176 	ASSERT( file != NULL );
    177 	fwrite( lz4, 1, lz4_size, file );
    178 	ASSERT( ferror( file ) == 0 );
    179 	fclose( file );
    180 }
    181 
    182 struct RGBA { u8 r, g, b, a; };
    183 
    184 static u8 * write_compressed_texture( MemoryArena * arena, const DynamicString & path, const array2d< RGBA > data, int squish_flags ) {
    185 	int w = checked_cast< int >( data.w );
    186 	int h = checked_cast< int >( data.h );
    187 
    188 	int dxt_size = squish::GetStorageRequirements( w, h, squish_flags );
    189 	u8 * dxt = memarena_push_many( arena, u8, dxt_size );
    190 	squish::CompressImage( ( const u8 * ) data.ptr(), w, h, dxt, squish_flags );
    191 
    192 	write_compressed_file( arena, path, dxt, dxt_size );
    193 
    194 	// DynamicString png_path( "{}.png", path );
    195 	// stbi_write_png( png_path.c_str(), data.w, data.h, 4, data.ptr(), 0 );
    196 
    197 	return dxt;
    198 }
    199 
    200 const size_t MAX_TREES = 262144 / 4;
    201 
    202 static bool ok_tree_position( PCG * rng, const array2d< u16 > heightmap, const array2d< v3 > normalmap, v2 pos, v3 * tree ) {
    203 	u16 height = bilerp01( heightmap, pos.x, pos.y );
    204 	double p = 1;
    205 	if( height <= 5 * 256 ) return false;
    206 	if( height > 235 * 256 ) return false;
    207 	if( height >= 200 * 256 ) {
    208 		p *= double( 235 * 256 - height ) / double( 235 * 256 - 200 * 256 );
    209 	}
    210 
    211 	v3 normal = normalize( bilerp01( normalmap, pos.x, pos.y ) );
    212 	if( normal.z < 0.3 ) return false;
    213 
    214 	p *= ( normal.z - 0.3 ) / 0.7;
    215 
    216 	if( !rng_p( rng, p ) ) return false;
    217 
    218 	*tree = v3( pos.x * heightmap.w, pos.y * heightmap.h, height / 256.0f );
    219 	return true;
    220 }
    221 
    222 static void hammersley( size_t n, array< v2 > points ) {
    223 	for( size_t i = 0; i < n; i++ ) {
    224 		float u = 0;
    225 		float v = ( i + 0.5f ) / n;
    226 		float p = 0.5;
    227 
    228 		size_t x = i;
    229 		while( x > 0 ) {
    230 			if( x % 2 == 1 ) u += p;
    231 			p /= 2;
    232 			x /= 2;
    233 		}
    234 
    235 		points[ i ] = v2( u, v );
    236 	}
    237 }
    238 
    239 static size_t place_trees( MemoryArena * arena, const array2d< u16 > heightmap, const array2d< v3 > normalmap, array< v3 > trees ) {
    240 	MEMARENA_SCOPED_CHECKPOINT( arena );
    241 
    242 	array< v2 > points = memarena_push_array( arena, v2, trees.n );
    243 	hammersley( MAX_TREES, points );
    244 
    245 	PCG rng = new_pcg();
    246 
    247 	size_t placed = 0;
    248 	for( v2 point : points ) {
    249 		v3 pos;
    250 		if( ok_tree_position( &rng, heightmap, normalmap, point, &pos ) ) {
    251 			trees[ placed ] = pos;
    252 			placed++;
    253 		}
    254 	}
    255 
    256 	return placed;
    257 }
    258 
    259 int main( int argc, char ** argv ) {
    260 	install_debug_signal_handlers();
    261 
    262 	static u8 arena_memory[ megabytes( 512 ) ];
    263 	MemoryArena arena;
    264 	memarena_init( &arena, arena_memory, ARRAY_COUNT( arena_memory ) );
    265 
    266 	DynamicString input_path( "terrains/gta16.png" );
    267 	DynamicString output_path( "{}.parts", input_path );
    268 
    269 	printf( "decoding\n" );
    270 	int w, h;
    271 	u16 * png = ( u16 * ) stbi_load_16( input_path.c_str(), &w, &h, NULL, 1 );
    272 	ASSERT( png != NULL );
    273 
    274 	array2d< u16 > heightmap( png, w, h );
    275 	array2d< u16 > bc5_heightmap = alloc_array2d< u16 >( &arena, heightmap.w, heightmap.h );
    276 
    277 	mkdir( "terrains", 0755 );
    278 	mkdir( output_path.c_str(), 0755 );
    279 
    280 	{
    281 		MEMARENA_SCOPED_CHECKPOINT( &arena );
    282 		printf( "computing heightmaps\n" );
    283 
    284 		array2d< RGBA > heightmap_split = alloc_array2d< RGBA >( &arena, w, h );
    285 
    286 		for( int y = 0; y < h; y++ ) {
    287 			for( int x = 0; x < w; x++ ) {
    288 				heightmap_split( x, y ).r = heightmap( x, y ) / 256;
    289 				heightmap_split( x, y ).g = heightmap( x, y ) % 256;
    290 				heightmap_split( x, y ).b = 0;
    291 				heightmap_split( x, y ).a = 255;
    292 			}
    293 		}
    294 
    295 		printf( "compressing\n" );
    296 		DynamicString heightmap_path( "{}/heightmap.bc5.lz4", output_path );
    297 		u8 * bc5 = write_compressed_texture( &arena, heightmap_path, heightmap_split, squish::kBc5 );
    298 
    299 		bc5_to_heightmap( &arena, bc5_heightmap, bc5 );
    300 	}
    301 
    302 	{
    303 		MEMARENA_SCOPED_CHECKPOINT( &arena );
    304 		printf( "computing normalmap\n" );
    305 
    306 		array2d< v3 > normalmap_v3 = alloc_array2d< v3 >( &arena, w, h );
    307 		array2d< RGBA > normalmap = alloc_array2d< RGBA >( &arena, w, h );
    308 
    309 		compute_normals( heightmap, normalmap_v3 );
    310 		for( int y = 0; y < h; y++ ) {
    311 			for( int x = 0; x < w; x++ ) {
    312 				normalmap( x, y ).r = quantize01( ( normalmap_v3( x, y ).x + 1.0f ) / 2.0f, 8 );
    313 				normalmap( x, y ).g = quantize01( ( normalmap_v3( x, y ).y + 1.0f ) / 2.0f, 8 );
    314 				normalmap( x, y ).b = 0;
    315 				normalmap( x, y ).a = 255;
    316 			}
    317 		}
    318 
    319 		printf( "compressing\n" );
    320 		DynamicString normalmap_path( "{}/normalmap.bc5.lz4", output_path );
    321 		write_compressed_texture( &arena, normalmap_path, normalmap, squish::kBc5 );
    322 
    323 		{
    324 			printf( "planting trees\n" );
    325 
    326 			array< v3 > trees = alloc_array< v3 >( &arena, MAX_TREES );
    327 			size_t num_trees = place_trees( &arena, bc5_heightmap, normalmap_v3, trees );
    328 
    329 			printf( "compressing\n" );
    330 			DynamicString trees_path( "{}/trees.lz4", output_path );
    331 			write_compressed_file( &arena, trees_path, trees.ptr(), num_trees * sizeof( v3 ) );
    332 		}
    333 	}
    334 
    335 	{
    336 		MEMARENA_SCOPED_CHECKPOINT( &arena );
    337 		printf( "computing horizonmap\n" );
    338 
    339 		array2d< float > horizonmap_float = alloc_array2d< float >( &arena, w, h );
    340 		array2d< RGBA > horizonmap = alloc_array2d< RGBA >( &arena, w, h );
    341 
    342 		compute_horizons( &arena, heightmap, horizonmap_float );
    343 		for( int y = 0; y < h; y++ ) {
    344 			for( int x = 0; x < w; x++ ) {
    345 				float theta01 = atanf( horizonmap_float( x, y ) ) * 2.0f / PI;
    346 				horizonmap( x, y ).r = quantize01( theta01, 8 );
    347 				horizonmap( x, y ).g = 0;
    348 				horizonmap( x, y ).b = 0;
    349 				horizonmap( x, y ).a = 255;
    350 			}
    351 		}
    352 
    353 		printf( "compressing\n" );
    354 		DynamicString horizonmap_path( "{}/horizonmap.bc4.lz4", output_path );
    355 		write_compressed_texture( &arena, horizonmap_path, horizonmap, squish::kBc4 );
    356 	}
    357 
    358 	{
    359 		MEMARENA_SCOPED_CHECKPOINT( &arena );
    360 		printf( "computing quadtree\n" );
    361 
    362 		// quadtree needs POT+1 heightmap, so pad with zeroes
    363 		array2d< u16 > heightmap1 = alloc_array2d< u16 >( &arena, heightmap.w + 1, heightmap.h + 1 );
    364 		for( size_t y = 0; y < heightmap1.h; y++ ) {
    365 			for( size_t x = 0; x < heightmap1.w; x++ ) {
    366 				heightmap1( x, y ) = bc5_heightmap.try_get( x, y, 0 );
    367 			}
    368 		}
    369 
    370 		size_t num_nodes = quadtree_max_nodes( heightmap1.w, heightmap1.h );
    371 		array< QuadTreeNode > nodes = alloc_array< QuadTreeNode >( &arena, num_nodes );
    372 
    373 		build_quadtree( heightmap1, nodes );
    374 
    375 		printf( "compressing\n" );
    376 		DynamicString quadtree_path( "{}/quadtree.lz4", output_path );
    377 		write_compressed_file( &arena, quadtree_path, nodes.ptr(), nodes.num_bytes() );
    378 	}
    379 
    380 	stbi_image_free( png );
    381 
    382 	return 0;
    383 }