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 }