medfall

A super great game engine
Log | Files | Refs

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 }