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 &center )
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 &current_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 &center )
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 &center )
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 &center )
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 &center )
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 &center )
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