terrain_manager.cc (12872B)
1 #include <stdio.h> 2 #include <float.h> 3 4 #include "intrinsics.h" 5 #include "log.h" 6 #include "array.h" 7 #include "heightmap.h" 8 #include "terrain_manager.h" 9 #include "memory_arena.h" 10 #include "work_queue.h" 11 #include "btt.h" 12 #include "gpubtt.h" 13 #include "linear_algebra.h" 14 #include "str.h" 15 #include "mpsc.h" 16 #include "profiler.h" 17 #include "renderer.h" 18 #include "shaders.h" 19 #include "obj.h" 20 21 #include "libs/lz4/lz4.h" 22 23 #include "libs/stb/stb_image.h" 24 25 struct AsyncLoadTile { 26 TerrainManager * tm; 27 s32 tx, ty; 28 }; 29 30 static size_t quadtree_max_nodes( size_t w, size_t h ) { 31 // with a power of 2 + 1 sized quadtree with a 2x2 bottom level it 32 // should be exactly 1/3 (1/4 + 1/16 + ...) 33 return ( ( w - 1 ) * ( h - 1 ) ) / 3; 34 } 35 36 static WORK_QUEUE_CALLBACK( async_load_tile ) { 37 AsyncLoadTile * alt = ( AsyncLoadTile * ) data; 38 TerrainManager * tm = alt->tm; 39 s32 tx = alt->tx; 40 s32 ty = alt->ty; 41 free( alt ); 42 43 const CompressedTile & ct = tm->compressed_tiles[ tx ][ ty ]; 44 DecompressedTile & dt = tm->decompressed_tiles[ tx ][ ty ]; 45 46 size_t num_points = ( TILE_SIZE + 1 ) * ( TILE_SIZE + 1 ); 47 48 // heightmap 49 dt.heightmap = array2d< u16 >( malloc_array< u16 >( num_points ), TILE_SIZE + 1, TILE_SIZE + 1 ); 50 ASSERT( dt.heightmap.ptr() != NULL ); 51 int ok_heightmap = LZ4_decompress_safe( 52 ct.heightmap.ptr(), 53 ( char * ) dt.heightmap.ptr(), 54 ct.heightmap.num_bytes(), dt.heightmap.num_bytes() ); 55 ASSERT( ok_heightmap > 0 ); 56 57 // normalmap 58 size_t decompressed_normalmap_size = num_points * sizeof( v3 ); 59 v3 * decompressed_normalmap = ( v3 * ) malloc( decompressed_normalmap_size ); 60 ASSERT( decompressed_normalmap != NULL ); 61 62 array2d< v3 > normals( decompressed_normalmap, dt.heightmap.w, dt.heightmap.h ); 63 64 // compute centre region 65 for( size_t y = 1; y < normals.h - 1; y++ ) { 66 for( size_t x = 1; x < normals.w - 1; x++ ) { 67 v3 tangent( 68 2.0f, 69 0, 70 heightmap_height( dt.heightmap, x + 1, y ) - heightmap_height( dt.heightmap, x - 1, y ) ); 71 v3 bitangent( 72 0, 73 2.0f, 74 heightmap_height( dt.heightmap, x, y + 1 ) - heightmap_height( dt.heightmap, x, y - 1 ) ); 75 76 normals( x, y ) = normalize( cross( tangent, bitangent ) ); 77 } 78 } 79 80 // load border 81 array< v3 > normals_border = memarena_push_array( arena, v3, ( TILE_SIZE + 1 ) * 4 - 4 ); 82 int ok_normalmap = LZ4_decompress_safe( 83 ct.normalmap.ptr(), 84 ( char * ) normals_border.ptr(), 85 ct.normalmap.num_bytes(), normals_border.num_bytes() ); 86 ASSERT( ok_normalmap > 0 ); 87 88 { 89 size_t n = 0; 90 for( size_t x = 0; x < normals.w; x++ ) { 91 normals( x, 0 ) = normals_border[ n++ ]; 92 } 93 for( size_t x = 0; x < normals.w; x++ ) { 94 normals( x, normals.h - 1 ) = normals_border[ n++ ]; 95 } 96 for( size_t y = 1; y < normals.h - 1; y++ ) { 97 normals( 0, y ) = normals_border[ n++ ]; 98 } 99 for( size_t y = 1; y < normals.h - 1; y++ ) { 100 normals( normals.w - 1, y ) = normals_border[ n++ ]; 101 } 102 } 103 104 dt.normalmap = decompressed_normalmap; 105 106 // horizonmap 107 int w, h; 108 u8 * decompressed_horizonmap = stbi_load_from_memory( ct.horizonmap.ptr(), checked_cast< int >( ct.horizonmap.n ), &w, &h, NULL, 1 ); 109 size_t decompressed_horizonmap_size = align4( TILE_SIZE + 1 ) * ( TILE_SIZE + 1 ) * sizeof( u8 ); 110 ASSERT( decompressed_horizonmap != NULL ); 111 112 // quadtree 113 size_t num_nodes = quadtree_max_nodes( dt.heightmap.w, dt.heightmap.h ); 114 dt.quadtree.nodes = array< QuadTreeNode >( malloc_array< QuadTreeNode >( num_nodes ), num_nodes ); 115 ASSERT( dt.quadtree.nodes.ptr() != NULL ); 116 int ok_quadtree = LZ4_decompress_safe( 117 ct.quadtree.ptr(), 118 ( char * ) dt.quadtree.nodes.ptr(), 119 ct.quadtree.num_bytes(), dt.quadtree.nodes.num_bytes() ); 120 ASSERT( ok_quadtree > 0 ); 121 122 dt.quadtree.heightmap = dt.heightmap; 123 124 // generate btt 125 // TODO: should generate a triangle mesh here instead of in gpubtt_init 126 MemoryArena btt_arena; 127 void * btt_memory = malloc( megabytes( 16 ) ); 128 memarena_init( &btt_arena, ( u8 * ) btt_memory, megabytes( 16 ) ); 129 GPUTileData gpu_tile_data; 130 BTTs btt = btt_from_heightmap( dt.heightmap, &btt_arena ); 131 132 gpu_tile_data.btt = btt; 133 gpu_tile_data.btt_memory = btt_memory; 134 gpu_tile_data.horizonmap = decompressed_horizonmap; 135 gpu_tile_data.horizonmap_size = decompressed_horizonmap_size; 136 tm->gpu_tiles[ tx ][ ty ] = gpu_tile_data; 137 138 // mark as ready to upload 139 ReadyTile ready_tile = { tx, ty }; 140 tm->ready_tiles.enqueue_spin( ready_tile ); 141 } 142 143 static bool terrain_tile_inside_world( const TerrainManager * tm, s32 tx, s32 ty ) { 144 if( tx < 0 || ty < 0 ) return false; 145 if( ( u32 ) tx * TILE_SIZE >= tm->width ) return false; 146 if( ( u32 ) ty * TILE_SIZE >= tm->height ) return false; 147 return true; 148 } 149 150 static void terrain_load_tile( TerrainManager * tm, s32 tx, s32 ty ) { 151 if( !terrain_tile_inside_world( tm, tx, ty ) ) return; 152 153 if( tm->tile_states[ tx ][ ty ] != TILE_EMPTY ) { 154 return; 155 } 156 157 AsyncLoadTile * alt = ( AsyncLoadTile * ) malloc( sizeof( AsyncLoadTile ) ); 158 if( alt == NULL ) return; 159 160 tm->tile_states[ tx ][ ty ] = TILE_LOADING; 161 162 alt->tm = tm; 163 alt->tx = tx; 164 alt->ty = ty; 165 workqueue_enqueue( tm->background_tasks, async_load_tile, alt ); 166 } 167 168 static void terrain_unload_tile( TerrainManager * tm, s32 tx, s32 ty ) { 169 if( !terrain_tile_inside_world( tm, tx, ty ) ) return; 170 171 if( tm->tile_states[ tx ][ ty ] != TILE_LOADED ) { 172 return; 173 } 174 175 free( tm->decompressed_tiles[ tx ][ ty ].heightmap.ptr() ); 176 free( tm->decompressed_tiles[ tx ][ ty ].normalmap ); 177 free( tm->decompressed_tiles[ tx ][ ty ].quadtree.nodes.ptr() ); 178 gpubtt_destroy( &tm->gpubtts[ tx ][ ty ] ); 179 180 tm->tile_states[ tx ][ ty ] = TILE_EMPTY; 181 } 182 183 void terrain_init( 184 TerrainManager * tm, const char * tiles_dir, 185 MemoryArena * arena, WorkQueue * background_tasks 186 ) { 187 tm->arena = arena; 188 tm->background_tasks = background_tasks; 189 190 str< 512 > dims_path( "{}/dims.txt", tiles_dir ); 191 192 FILE * dims = fopen( dims_path.c_str(), "r" ); 193 ASSERT( dims != NULL ); 194 fscanf( dims, "%d %d", &tm->width, &tm->height ); 195 fclose( dims ); 196 197 for( u32 ty = 0; ty < tm->height / TILE_SIZE; ty++ ) { 198 for( u32 tx = 0; tx < tm->width / TILE_SIZE; tx++ ) { 199 str< 512 > heightmap_path( "{}/{}_{}_heightmap.lz4", tiles_dir, tx, ty ); 200 str< 512 > normalmap_path( "{}/{}_{}_normals.lz4", tiles_dir, tx, ty ); 201 str< 512 > horizonmap_path( "{}/{}_{}_horizons.png", tiles_dir, tx, ty ); 202 str< 512 > quadtree_path( "{}/{}_{}_quadtree.lz4", tiles_dir, tx, ty ); 203 204 CompressedTile & ct = tm->compressed_tiles[ tx ][ ty ]; 205 ct.heightmap = file_get_array< char >( heightmap_path.c_str() ); 206 ct.normalmap = file_get_array< char >( normalmap_path.c_str() ); 207 ct.horizonmap = file_get_array< u8 >( horizonmap_path.c_str() ); 208 ct.quadtree = file_get_array< char >( quadtree_path.c_str() ); 209 210 tm->tile_states[ tx ][ ty ] = TILE_EMPTY; 211 } 212 } 213 214 tm->point_light_origins = renderer_new_tb( TEXFMT_RGB_FLOAT ); 215 tm->point_light_colours = renderer_new_tb( TEXFMT_RGB_FLOAT ); 216 217 tm->first_teleport = true; 218 } 219 220 void terrain_teleport( TerrainManager * tm, v3 position ) { 221 ASSERT( tm->first_teleport ); 222 223 tm->first_teleport = false; 224 225 s32 player_tile_x = s32( position.x / TILE_SIZE ); 226 s32 player_tile_y = s32( position.y / TILE_SIZE ); 227 228 tm->tile_x = player_tile_x; 229 tm->tile_y = player_tile_y; 230 231 for( u32 vy = 0; vy < VIEW_SIZE; vy++ ) { 232 for( u32 vx = 0; vx < VIEW_SIZE; vx++ ) { 233 terrain_load_tile( tm, 234 vx + player_tile_x - VIEW_HALF, 235 vy + player_tile_y - VIEW_HALF ); 236 } 237 } 238 } 239 240 void terrain_update( TerrainManager * tm, v3 position ) { 241 s32 player_tile_x = s32( position.x / TILE_SIZE ); 242 s32 player_tile_y = s32( position.y / TILE_SIZE ); 243 244 if( player_tile_x != tm->tile_x ) { 245 if( player_tile_x > tm->tile_x ) { 246 // +x boundary 247 for( u32 vy = 0; vy < VIEW_SIZE; vy++ ) { 248 terrain_unload_tile( tm, 249 tm->tile_x - VIEW_HALF, 250 tm->tile_y + vy - VIEW_HALF ); 251 // TODO: this should enqueue a tile load because this can fail now 252 terrain_load_tile( tm, 253 tm->tile_x + VIEW_HALF + 1, 254 tm->tile_y + vy - VIEW_HALF ); 255 } 256 tm->tile_x++; 257 } 258 else { 259 // -x boundary 260 for( u32 vy = 0; vy < VIEW_SIZE; vy++ ) { 261 terrain_unload_tile( tm, 262 tm->tile_x + VIEW_HALF, 263 tm->tile_y + vy - VIEW_HALF ); 264 terrain_load_tile( tm, 265 tm->tile_x - VIEW_HALF - 1, 266 tm->tile_y + vy - VIEW_HALF ); 267 } 268 tm->tile_x--; 269 270 } 271 } 272 273 if( player_tile_y != tm->tile_y ) { 274 if( player_tile_y > tm->tile_y ) { 275 // +y boundary 276 for( u32 vx = 0; vx < VIEW_SIZE; vx++ ) { 277 terrain_unload_tile( tm, 278 tm->tile_x + vx - VIEW_HALF, 279 tm->tile_y - VIEW_HALF ); 280 terrain_load_tile( tm, 281 tm->tile_x + vx - VIEW_HALF, 282 tm->tile_y + VIEW_HALF + 1 ); 283 } 284 tm->tile_y++; 285 } 286 else { 287 // -y boundary 288 for( u32 vx = 0; vx < VIEW_SIZE; vx++ ) { 289 terrain_unload_tile( tm, 290 tm->tile_x + vx - VIEW_HALF, 291 tm->tile_y + VIEW_HALF ); 292 terrain_load_tile( tm, 293 tm->tile_x + vx - VIEW_HALF, 294 tm->tile_y - VIEW_HALF - 1 ); 295 } 296 tm->tile_y--; 297 } 298 } 299 } 300 301 void terrain_render( TerrainManager * tm, const m4 & V, const m4 & P, float sun_angle, v3 sun_dir, double current_time ) { 302 PROFILE_FUNCTION(); 303 304 ReadyTile ready_tile; 305 if( tm->ready_tiles.dequeue( &ready_tile ) ) { 306 s32 tx = ready_tile.tx; 307 s32 ty = ready_tile.ty; 308 309 const GPUTileData & gpu_tile_data = tm->gpu_tiles[ tx ][ ty ]; 310 const array2d< u16 > hm = tm->decompressed_tiles[ tx ][ ty ].heightmap; 311 v2u32 offset( tx * TILE_SIZE, ty * TILE_SIZE ); 312 gpubtt_init( 313 tm->arena, 314 &tm->gpubtts[ tx ][ ty ], 315 gpu_tile_data.btt, 316 hm, offset, 317 tm->decompressed_tiles[ tx ][ ty ].normalmap, 318 gpu_tile_data.horizonmap 319 ); 320 free( gpu_tile_data.btt_memory ); 321 free( gpu_tile_data.horizonmap ); 322 323 tm->tile_states[ tx ][ ty ] = TILE_LOADED; 324 } 325 326 /* start lighting */ 327 float tbo1[ 15 ] = { 328 500, float( 1500 + 500 * sin( current_time + 3.14 * 1 / 5 ) ), 15, 329 600, float( 1500 + 500 * sin( current_time + 3.14 * 2 / 5 ) ), 15, 330 700, float( 1500 + 500 * sin( current_time + 3.14 * 3 / 5 ) ), 15, 331 800, float( 1500 + 500 * sin( current_time + 3.14 * 4 / 5 ) ), 15, 332 900, float( 1500 + 500 * sin( current_time + 3.14 * 5 / 5 ) ), 15, 333 }; 334 renderer_tb_data( tm->point_light_origins, tbo1, sizeof( tbo1 ) ); 335 336 float tbo2[ 15 ] = { 337 15, 0, 0, 338 15, 15, 0, 339 0, 15, 0, 340 0, 15, 15, 341 0, 0, 15, 342 }; 343 renderer_tb_data( tm->point_light_colours, tbo2, sizeof( tbo2 ) ); 344 /* end lighting */ 345 346 RenderState render_state; 347 render_state.shader = get_shader( SHADER_TERRAIN ); 348 render_state.textures[ 2 ] = renderer_blue_noise(); 349 render_state.uniforms[ UNIFORMS_VIEW ] = renderer_uniforms( V, P ); 350 render_state.uniforms[ UNIFORMS_SUN ] = renderer_uniforms( sun_dir, sun_angle ); 351 render_state.tbs[ 0 ] = tm->point_light_origins; 352 render_state.tbs[ 1 ] = tm->point_light_colours; 353 354 for( u16 vy = 0; vy < VIEW_SIZE; vy++ ) { 355 for( u16 vx = 0; vx < VIEW_SIZE; vx++ ) { 356 s32 tx = vx + tm->tile_x - VIEW_HALF; 357 s32 ty = vy + tm->tile_y - VIEW_HALF; 358 359 if( tx < 0 || ty < 0 ) continue; 360 361 if( tm->tile_states[ tx ][ ty ] == TILE_LOADED ) { 362 gpubtt_render( &tm->gpubtts[ tx ][ ty ], render_state ); 363 } 364 } 365 } 366 } 367 368 v3 terrain_normal( const TerrainManager * tm, v3 position ) { 369 ASSERT( position.x >= 0 ); 370 ASSERT( position.y >= 0 ); 371 ASSERT( position.x < tm->width ); 372 ASSERT( position.y < tm->height ); 373 374 s32 tx = s32( position.x / TILE_SIZE ); 375 s32 ty = s32( position.y / TILE_SIZE ); 376 377 if( tm->tile_states[ tx ][ ty ] != TILE_LOADED ) { 378 return v3( 0.0f, 0.0f, 1.0f ); 379 } 380 381 const array2d< v3 > normalmap( tm->decompressed_tiles[ tx ][ ty ].normalmap, TILE_SIZE + 1, TILE_SIZE + 1 ); 382 383 float local_x = position.x - tx * TILE_SIZE; 384 float local_y = position.y - ty * TILE_SIZE; 385 386 // TODO: the values we read from normalmap are totally hosed 387 return normalize( bilerp( normalmap, local_x, local_y ) ); 388 } 389 390 bool segment_vs_terrain( const TerrainManager * tm, v3 seg_origin, v3 seg_end, float * t, v3 * xnormal ) { 391 PROFILE_FUNCTION(); 392 393 float dont_care_t; 394 v3 dont_care_xnormal = v3( 0, 0, 0 ); 395 if( t == NULL ) t = &dont_care_t; 396 if( xnormal == NULL ) xnormal = &dont_care_xnormal; 397 398 float segment_length = length( seg_end - seg_origin ); 399 Ray3 ray( seg_origin, normalize( seg_end - seg_origin ) ); 400 401 *t = 1.0f; 402 403 // TODO: bresenhams vs top level 404 for( u16 vy = 0; vy < VIEW_SIZE; vy++ ) { 405 for( u16 vx = 0; vx < VIEW_SIZE; vx++ ) { 406 s32 tx = vx + tm->tile_x - VIEW_HALF; 407 s32 ty = vy + tm->tile_y - VIEW_HALF; 408 409 if( tx < 0 || ty < 0 ) continue; 410 411 if( tm->tile_states[ tx ][ ty ] != TILE_LOADED ) { 412 continue; 413 } 414 415 v3 local_seg_origin = seg_origin - v3( checked_cast< float >( tx * TILE_SIZE ), checked_cast< float >( ty * TILE_SIZE ), 0 ); 416 ray.origin = local_seg_origin; 417 418 const QuadTree * qt = &tm->decompressed_tiles[ tx ][ ty ].quadtree; 419 // TODO: convert from tile t to global t 420 float tile_t; 421 bool tile_hit = ray_vs_quadtree( *qt, ray, &tile_t, xnormal ); 422 423 if( tile_hit && tile_t <= segment_length && tile_t < *t ) { 424 *t = tile_t; 425 } 426 } 427 } 428 429 return *t < 1.0f; 430 }