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 }