1 #if defined(TILES)
2
3 #include "pixel_minimap.h"
4
5 #include <algorithm>
6 #include <array>
7 #include <bitset>
8 #include <cmath>
9 #include <cstdlib>
10 #include <functional>
11 #include <iterator>
12 #include <memory>
13 #include <utility>
14 #include <vector>
15
16 #include "cata_assert.h"
17 #include "cata_utility.h"
18 #include "cata_tiles.h"
19 #include "character.h"
20 #include "color.h"
21 #include "coordinate_conversions.h"
22 #include "creature.h"
23 #include "debug.h"
24 #include "game.h"
25 #include "game_constants.h"
26 #include "int_id.h"
27 #include "lightmap.h"
28 #include "map.h"
29 #include "mapdata.h"
30 #include "math_defines.h"
31 #include "monster.h"
32 #include "optional.h"
33 #include "pixel_minimap_projectors.h"
34 #include "sdl_utils.h"
35 #include "vehicle.h"
36 #include "vpart_position.h"
37
38 namespace
39 {
40
41 const point total_tiles_count = { ( MAPSIZE - 2 ) *SEEX, ( MAPSIZE - 2 ) *SEEY };
42
get_pixel_size(const point & tile_size,pixel_minimap_mode mode)43 point get_pixel_size( const point &tile_size, pixel_minimap_mode mode )
44 {
45 switch( mode ) {
46 case pixel_minimap_mode::solid:
47 return tile_size;
48
49 case pixel_minimap_mode::squares:
50 return { std::max( tile_size.x - 1, 1 ), std::max( tile_size.y - 1, 1 ) };
51
52 case pixel_minimap_mode::dots:
53 return { point_south_east };
54 }
55
56 return {};
57 }
58
59 /// Returns a number in range [0..1]. The range lasts for @param phase_length_ms (milliseconds).
get_animation_phase(int phase_length_ms)60 float get_animation_phase( int phase_length_ms )
61 {
62 if( phase_length_ms == 0 ) {
63 return 0.0f;
64 }
65
66 return std::fmod<float>( SDL_GetTicks(), phase_length_ms ) / phase_length_ms;
67 }
68
69 //creates the texture that individual minimap updates are drawn to
70 //later, the main texture is drawn to the display buffer
71 //the surface is needed to determine the color format needed by the texture
create_cache_texture(const SDL_Renderer_Ptr & renderer,int tile_width,int tile_height)72 SDL_Texture_Ptr create_cache_texture( const SDL_Renderer_Ptr &renderer, int tile_width,
73 int tile_height )
74 {
75 return CreateTexture( renderer,
76 SDL_PIXELFORMAT_ARGB8888,
77 SDL_TEXTUREACCESS_TARGET,
78 tile_width,
79 tile_height );
80 }
81
get_map_color_at(const tripoint & p)82 SDL_Color get_map_color_at( const tripoint &p )
83 {
84 const map &here = get_map();
85 if( const optional_vpart_position vp = here.veh_at( p ) ) {
86 return curses_color_to_SDL( vp->vehicle().part_color( vp->part_index() ) );
87 }
88
89 if( const auto furn_id = here.furn( p ) ) {
90 return curses_color_to_SDL( furn_id->color() );
91 }
92
93 return curses_color_to_SDL( here.ter( p )->color() );
94 }
95
get_critter_color(Creature * critter,int flicker,int mixture)96 SDL_Color get_critter_color( Creature *critter, int flicker, int mixture )
97 {
98 SDL_Color result = curses_color_to_SDL( critter->symbol_color() );
99
100 if( const monster *m = dynamic_cast<monster *>( critter ) ) {
101 //faction status (attacking or tracking) determines if red highlights get applied to creature
102 const monster_attitude matt = m->attitude( &get_player_character() );
103
104 if( MATT_ATTACK == matt || MATT_FOLLOW == matt ) {
105 const SDL_Color red_pixel = SDL_Color{ 0xFF, 0x0, 0x0, 0xFF };
106 result = adjust_color_brightness( mix_colors( result, red_pixel, mixture ), flicker );
107 }
108 }
109
110 return result;
111 }
112
113 } // namespace
114
115 // a texture pool to avoid recreating textures every time player changes their view
116 // at most 142 out of 144 textures can be in use due to regular player movement
117 // (moving from submap corner to new corner) with MAPSIZE = 11
118 // textures are dumped when the player moves more than one submap in one update
119 // (teleporting, z-level change) to prevent running out of the remaining pool
120 class pixel_minimap::shared_texture_pool
121 {
122 public:
shared_texture_pool(const std::function<SDL_Texture_Ptr ()> & generator)123 explicit shared_texture_pool( const std::function<SDL_Texture_Ptr()> &generator ) {
124 const size_t pool_size = ( MAPSIZE + 1 ) * ( MAPSIZE + 1 );
125
126 texture_pool.reserve( pool_size );
127 inactive_index.reserve( pool_size );
128
129 for( size_t i = 0; i < pool_size; ++i ) {
130 texture_pool.emplace_back( generator() );
131 inactive_index.push_back( i );
132 }
133 }
134
135 //reserves a texture from the inactive group and returns tracking info
request_tex(size_t & index)136 SDL_Texture_Ptr request_tex( size_t &index ) {
137 if( inactive_index.empty() ) {
138 debugmsg( "Ran out of available textures in the pool." );
139 //shouldn't be happening, but minimap will just be default color instead of crashing
140 return nullptr;
141 }
142 index = inactive_index.back();
143 inactive_index.pop_back();
144 return std::move( texture_pool[index] );
145 }
146
147 //releases the provided texture back into the inactive pool to be used again
148 //called automatically in the submap cache destructor
release_tex(size_t index,SDL_Texture_Ptr && ptr)149 void release_tex( size_t index, SDL_Texture_Ptr &&ptr ) {
150 if( ptr ) {
151 inactive_index.push_back( index );
152 texture_pool[index] = std::move( ptr );
153 }
154 }
155
156 private:
157 std::vector<SDL_Texture_Ptr> texture_pool;
158 std::vector<size_t> inactive_index;
159 };
160
161 struct pixel_minimap::submap_cache {
162 //the color stored for each submap tile
163 std::array<SDL_Color, SEEX *SEEY> minimap_colors = {};
164 //checks if the submap has been looked at by the minimap routine
165 bool touched = false;
166 //the texture updates are drawn to
167 SDL_Texture_Ptr chunk_tex;
168 //the submap being handled
169 size_t texture_index = 0;
170 //the list of updates to apply to the texture
171 //reduces render target switching to once per submap
172 std::vector<point> update_list;
173 //flag used to indicate that the texture needs to be cleared before first use
174 bool ready = false;
175 shared_texture_pool &pool;
176
177 //reserve the SEEX * SEEY submap tiles
submap_cachepixel_minimap::submap_cache178 explicit submap_cache( shared_texture_pool &pool ) :
179 pool( pool ) {
180 chunk_tex = pool.request_tex( texture_index );
181 }
182
183 //handle the release of the borrowed texture
~submap_cachepixel_minimap::submap_cache184 ~submap_cache() {
185 pool.release_tex( texture_index, std::move( chunk_tex ) );
186 }
187
188 submap_cache( const submap_cache & ) = delete;
189 submap_cache( submap_cache && ) = default;
190
color_atpixel_minimap::submap_cache191 SDL_Color &color_at( const point &p ) {
192 cata_assert( p.x < SEEX );
193 cata_assert( p.y < SEEY );
194
195 return minimap_colors[p.y * SEEX + p.x];
196 }
197 };
198
pixel_minimap(const SDL_Renderer_Ptr & renderer,const GeometryRenderer_Ptr & geometry)199 pixel_minimap::pixel_minimap( const SDL_Renderer_Ptr &renderer,
200 const GeometryRenderer_Ptr &geometry ) :
201 renderer( renderer ),
202 geometry( geometry ),
203 type( pixel_minimap_type::ortho ),
204 screen_rect{ 0, 0, 0, 0 }
205 {
206 }
207
208 pixel_minimap::~pixel_minimap() = default;
209
set_type(pixel_minimap_type type)210 void pixel_minimap::set_type( pixel_minimap_type type )
211 {
212 this->type = type;
213 reset();
214 }
215
set_settings(const pixel_minimap_settings & settings)216 void pixel_minimap::set_settings( const pixel_minimap_settings &settings )
217 {
218 this->settings = settings;
219 reset();
220 }
221
prepare_cache_for_updates(const tripoint & center)222 void pixel_minimap::prepare_cache_for_updates( const tripoint ¢er )
223 {
224 const tripoint new_center_sm = get_map().get_abs_sub() + ms_to_sm_copy( center );
225 const tripoint center_sm_diff = cached_center_sm - new_center_sm;
226
227 //invalidate the cache if the game shifted more than one submap in the last update, or if z-level changed.
228 if( std::abs( center_sm_diff.x ) > 1 ||
229 std::abs( center_sm_diff.y ) > 1 ||
230 std::abs( center_sm_diff.z ) > 0 ) {
231 cache.clear();
232 } else {
233 for( auto &mcp : cache ) {
234 mcp.second.touched = false;
235 }
236 }
237
238 cached_center_sm = new_center_sm;
239 }
240
241 //deletes the mapping of unused submap caches from the main map
242 //the touched flag prevents deletion
clear_unused_cache()243 void pixel_minimap::clear_unused_cache()
244 {
245 for( auto it = cache.begin(); it != cache.end(); ) {
246 it = it->second.touched ? std::next( it ) : cache.erase( it );
247 }
248 }
249
250 //draws individual updates to the submap cache texture
251 //the render target will be set back to display_buffer after all submaps are updated
flush_cache_updates()252 void pixel_minimap::flush_cache_updates()
253 {
254 for( auto &mcp : cache ) {
255 if( mcp.second.update_list.empty() ) {
256 continue;
257 }
258
259 SetRenderTarget( renderer, mcp.second.chunk_tex );
260
261 if( !mcp.second.ready ) {
262 mcp.second.ready = true;
263
264 SetRenderDrawColor( renderer, 0x00, 0x00, 0x00, 0x00 );
265 RenderClear( renderer );
266
267 for( int y = 0; y < SEEY; ++y ) {
268 for( int x = 0; x < SEEX; ++x ) {
269 const point tile_pos = projector->get_tile_pos( { x, y }, { SEEX, SEEY } );
270 const point tile_size = projector->get_tile_size();
271
272 const SDL_Rect rect = SDL_Rect{ tile_pos.x, tile_pos.y, tile_size.x, tile_size.y };
273
274 geometry->rect( renderer, rect, SDL_Color() );
275 }
276 }
277 }
278
279 for( const point &p : mcp.second.update_list ) {
280 const point tile_pos = projector->get_tile_pos( p, { SEEX, SEEY } );
281 const SDL_Color tile_color = mcp.second.color_at( p );
282
283 if( pixel_size.x == 1 && pixel_size.y == 1 ) {
284 SetRenderDrawColor( renderer, tile_color.r, tile_color.g, tile_color.b, tile_color.a );
285 RenderDrawPoint( renderer, tile_pos );
286 } else {
287 geometry->rect( renderer, tile_pos, pixel_size.x, pixel_size.y, tile_color );
288 }
289 }
290
291 mcp.second.update_list.clear();
292 }
293 }
294
update_cache_at(const tripoint & sm_pos)295 void pixel_minimap::update_cache_at( const tripoint &sm_pos )
296 {
297 const map &here = get_map();
298 const level_cache &access_cache = here.access_cache( sm_pos.z );
299 const bool nv_goggle = get_player_character().get_vision_modes()[NV_GOGGLES];
300
301 submap_cache &cache_item = get_cache_at( here.get_abs_sub() + sm_pos );
302 const tripoint ms_pos = sm_to_ms_copy( sm_pos );
303
304 cache_item.touched = true;
305
306 for( int y = 0; y < SEEY; ++y ) {
307 for( int x = 0; x < SEEX; ++x ) {
308 const tripoint p = ms_pos + tripoint{ x, y, 0 };
309 const lit_level lighting = access_cache.visibility_cache[p.x][p.y];
310
311 SDL_Color color;
312
313 if( lighting == lit_level::BLANK || lighting == lit_level::DARK ) {
314 // TODO: Map memory?
315 color = { 0x00, 0x00, 0x00, 0xFF };
316 } else {
317 color = get_map_color_at( p );
318
319 //color terrain according to lighting conditions
320 if( nv_goggle ) {
321 if( lighting == lit_level::LOW ) {
322 color = color_pixel_nightvision( color );
323 } else if( lighting != lit_level::DARK && lighting != lit_level::BLANK ) {
324 color = color_pixel_overexposed( color );
325 }
326 } else if( lighting == lit_level::LOW ) {
327 color = color_pixel_grayscale( color );
328 }
329
330 color = adjust_color_brightness( color, settings.brightness );
331 }
332
333 SDL_Color ¤t_color = cache_item.color_at( { x, y } );
334
335 if( current_color != color ) {
336 current_color = color;
337 cache_item.update_list.push_back( { x, y } );
338 }
339 }
340 }
341 }
342
get_cache_at(const tripoint & abs_sm_pos)343 pixel_minimap::submap_cache &pixel_minimap::get_cache_at( const tripoint &abs_sm_pos )
344 {
345 auto it = cache.find( abs_sm_pos );
346
347 if( it == cache.end() ) {
348 it = cache.emplace( abs_sm_pos, submap_cache( *tex_pool ) ).first;
349 }
350
351 return it->second;
352 }
353
process_cache(const tripoint & center)354 void pixel_minimap::process_cache( const tripoint ¢er )
355 {
356 prepare_cache_for_updates( center );
357
358 for( int y = 0; y < MAPSIZE; ++y ) {
359 for( int x = 0; x < MAPSIZE; ++x ) {
360 update_cache_at( { x, y, center.z } );
361 }
362 }
363
364 flush_cache_updates();
365 clear_unused_cache();
366 }
367
set_screen_rect(const SDL_Rect & screen_rect)368 void pixel_minimap::set_screen_rect( const SDL_Rect &screen_rect )
369 {
370 if( this->screen_rect == screen_rect && main_tex && tex_pool && projector ) {
371 return;
372 }
373
374 this->screen_rect = screen_rect;
375
376 projector = create_projector( screen_rect );
377 pixel_size = get_pixel_size( projector->get_tile_size(), settings.mode );
378
379 const point size_on_screen = projector->get_tiles_size( total_tiles_count );
380
381 if( settings.scale_to_fit ) {
382 main_tex_clip_rect = SDL_Rect{ 0, 0, size_on_screen.x, size_on_screen.y };
383 screen_clip_rect = fit_rect_inside( main_tex_clip_rect, screen_rect );
384
385 SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" );
386 main_tex = create_cache_texture( renderer, size_on_screen.x, size_on_screen.y );
387 SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "0" );
388
389 } else {
390 const point d( ( size_on_screen.x - screen_rect.w ) / 2, ( size_on_screen.y - screen_rect.h ) / 2 );
391
392 main_tex_clip_rect = SDL_Rect{
393 std::max( d.x, 0 ),
394 std::max( d.y, 0 ),
395 size_on_screen.x - 2 * std::max( d.x, 0 ),
396 size_on_screen.y - 2 * std::max( d.y, 0 )
397 };
398
399 screen_clip_rect = SDL_Rect{
400 screen_rect.x - std::min( d.x, 0 ),
401 screen_rect.y - std::min( d.y, 0 ),
402 main_tex_clip_rect.w,
403 main_tex_clip_rect.h
404 };
405
406 main_tex = create_cache_texture( renderer, size_on_screen.x, size_on_screen.y );
407 }
408
409 cache.clear();
410
411 const point chunk_size = projector->get_tiles_size( { SEEX, SEEY } );
412
413 const auto chunk_texture_generator = [&chunk_size, this]() {
414 SDL_Texture_Ptr result = create_cache_texture( renderer, chunk_size.x, chunk_size.y );
415 SetTextureBlendMode( result, SDL_BLENDMODE_BLEND );
416 return result;
417 };
418
419 tex_pool = std::make_unique<shared_texture_pool>( chunk_texture_generator );
420 }
421
reset()422 void pixel_minimap::reset()
423 {
424 projector.reset();
425 cache.clear();
426 main_tex.reset();
427 tex_pool.reset();
428 }
429
render(const tripoint & center)430 void pixel_minimap::render( const tripoint ¢er )
431 {
432 SetRenderTarget( renderer, main_tex );
433
434 SetRenderDrawColor( renderer, 0x00, 0x00, 0x00, 0x00 );
435 RenderClear( renderer );
436
437 render_cache( center );
438 render_critters( center );
439
440 //set display buffer to main screen
441 set_displaybuffer_rendertarget();
442 //paint intermediate texture to screen
443 RenderCopy( renderer, main_tex, &main_tex_clip_rect, &screen_clip_rect );
444 }
445
render_cache(const tripoint & center)446 void pixel_minimap::render_cache( const tripoint ¢er )
447 {
448 const tripoint sm_center = get_map().get_abs_sub() + ms_to_sm_copy( center );
449 const tripoint sm_offset = tripoint{
450 total_tiles_count.x / SEEX / 2,
451 total_tiles_count.y / SEEY / 2, 0
452 };
453
454 point ms_offset = center.xy();
455 ms_to_sm_remain( ms_offset );
456 ms_offset = point{ SEEX / 2, SEEY / 2 } - ms_offset;
457
458 for( const auto &elem : cache ) {
459 if( !elem.second.touched ) {
460 continue; // What you gonna do with all that junk?
461 }
462
463 const tripoint rel_pos = elem.first - sm_center;
464
465 if( std::abs( rel_pos.x ) > sm_offset.x + 1 ||
466 std::abs( rel_pos.y ) > sm_offset.y + 1 ||
467 rel_pos.z != 0 ) {
468 continue;
469 }
470
471 const tripoint sm_pos = rel_pos + sm_offset;
472 const tripoint ms_pos = sm_to_ms_copy( sm_pos ) + ms_offset;
473
474 const SDL_Rect chunk_rect = projector->get_chunk_rect( ms_pos.xy(), { SEEX, SEEY } );
475
476 RenderCopy( renderer, elem.second.chunk_tex, nullptr, &chunk_rect );
477 }
478 }
479
render_critters(const tripoint & center)480 void pixel_minimap::render_critters( const tripoint ¢er )
481 {
482 //handles the enemy faction red highlights
483 //this value should be divisible by 200
484 const int indicator_length = settings.beacon_blink_interval * 200; //default is 2000 ms, 2 seconds
485
486 int flicker = 100;
487 int mixture = 0;
488
489 if( indicator_length > 0 ) {
490 const float t = get_animation_phase( 2 * indicator_length );
491 const float s = std::sin( 2 * M_PI * t );
492
493 flicker = lerp_clamped( 25, 100, std::abs( s ) );
494 mixture = lerp_clamped( 0, 100, std::max( s, 0.0f ) );
495 }
496
497 const level_cache &access_cache = get_map().access_cache( center.z );
498
499 const int start_x = center.x - total_tiles_count.x / 2;
500 const int start_y = center.y - total_tiles_count.y / 2;
501 const point beacon_size = {
502 std::max<int>( projector->get_tile_size().x *settings.beacon_size / 2, 2 ),
503 std::max<int>( projector->get_tile_size().y *settings.beacon_size / 2, 2 )
504 };
505
506 for( int y = 0; y < total_tiles_count.y; y++ ) {
507 for( int x = 0; x < total_tiles_count.x; x++ ) {
508 const tripoint p = tripoint{ start_x + x, start_y + y, center.z };
509 const lit_level lighting = access_cache.visibility_cache[p.x][p.y];
510
511 if( lighting == lit_level::DARK || lighting == lit_level::BLANK ) {
512 continue;
513 }
514
515 Creature *critter = g->critter_at( p, true );
516
517 if( critter == nullptr || !get_player_view().sees( *critter ) ) {
518 continue;
519 }
520
521 const point critter_pos = projector->get_tile_pos( { x, y }, total_tiles_count );
522 const SDL_Rect critter_rect = SDL_Rect{ critter_pos.x, critter_pos.y, beacon_size.x, beacon_size.y };
523 const SDL_Color critter_color = get_critter_color( critter, flicker, mixture );
524
525 draw_beacon( critter_rect, critter_color );
526 }
527 }
528 }
529
530 //the main call for drawing the pixel minimap to the screen
draw(const SDL_Rect & screen_rect,const tripoint & center)531 void pixel_minimap::draw( const SDL_Rect &screen_rect, const tripoint ¢er )
532 {
533 if( !g ) {
534 return;
535 }
536
537 if( screen_rect.w <= 0 || screen_rect.h <= 0 ) {
538 return;
539 }
540
541 set_screen_rect( screen_rect );
542 process_cache( center );
543 render( center );
544 }
545
draw_beacon(const SDL_Rect & rect,const SDL_Color & color)546 void pixel_minimap::draw_beacon( const SDL_Rect &rect, const SDL_Color &color )
547 {
548 for( int x = -rect.w, x_max = rect.w; x <= x_max; ++x ) {
549 for( int y = -rect.h + std::abs( x ), y_max = rect.h - std::abs( x ); y <= y_max; ++y ) {
550 const int divisor = 2 * ( std::abs( y ) == rect.h - std::abs( x ) ? 1 : 0 ) + 1;
551
552 SetRenderDrawColor( renderer, color.r / divisor, color.g / divisor, color.b / divisor, 0xFF );
553 RenderDrawPoint( renderer, point( rect.x + x, rect.y + y ) );
554 }
555 }
556 }
557
create_projector(const SDL_Rect & max_screen_rect) const558 std::unique_ptr<pixel_minimap_projector> pixel_minimap::create_projector(
559 const SDL_Rect &max_screen_rect )
560 const
561 {
562 switch( type ) {
563 case pixel_minimap_type::ortho:
564 return std::make_unique<pixel_minimap_ortho_projector> ( total_tiles_count, max_screen_rect,
565 settings.square_pixels );
566
567 case pixel_minimap_type::iso:
568 return std::make_unique<pixel_minimap_iso_projector>( total_tiles_count, max_screen_rect,
569 settings.square_pixels );
570 }
571
572 return nullptr;
573 }
574
575 #endif // SDL_TILES
576