Refactored gamemap drawing

This simplifies the drawing buffer implementation. Instead of storing textures (or lists of textures),
we now store a function which takes the rect of the specified hex. This function is responsible for
actually rendering textures. The upsides of this are several. First, it means the whole drawing buffer
interface is much cleaner, since it no longer has to worry about texture mods or anything of the sort
(alpha, color mod, etc). The drawing functions themselves can handle them as needed. Second, it means
the messy, hacky, surface-based unit HP/XP bar drawing code can be replaced with simple rectangles!

Also includes:
* The whiteboard arrows now use the drawing buffer. The complexities of render_image were not needed.
* display::render_image has been removed and made local to the unit frame drawing code, which it is
  intended for.
* unit_drawer::scaled_to_zoom has been removed
* Added a point overload of display::scaled_to_zoom
* Merged drawing buffer calls which can now be logically handled together
* Removed drawing_buffer_key class in favor of a simple function to generate the key.
* Made display::add_submerge_ipf_mod a public static function
* Added display::get_location which combines get_location_x and get_location_y
* Avoid looping through invalidated hexes twice when rendering
* Refactored how hex debug aids are rendered
This commit is contained in:
Charles Dang 2022-10-04 02:00:49 -04:00
parent 880183774c
commit c6932e8133
10 changed files with 576 additions and 737 deletions

View file

@ -20,6 +20,7 @@
#include "arrow.hpp"
#include "draw.hpp"
#include "game_display.hpp"
#include "log.hpp"
@ -138,11 +139,11 @@ bool arrow::path_contains(const map_location& hex) const
void arrow::draw_hex(const map_location& hex)
{
if(path_contains(hex))
{
if(path_contains(hex)) {
display* disp = display::get_singleton();
disp->render_image(disp->get_location_x(hex), disp->get_location_y(hex), layer_,
hex, symbols_map_[hex]);
disp->drawing_buffer_add(layer_, hex, [disp, tex = image::get_texture(symbols_map_[hex])](const rect& dest) {
draw::blit(tex, disp->scaled_to_zoom({dest.x, dest.y, tex.w(), tex.h()}));
});
}
}

View file

@ -57,6 +57,8 @@
#include "video.hpp"
#include "whiteboard/manager.hpp"
#include <boost/algorithm/string/trim.hpp>
#include <SDL2/SDL_image.h>
#include <algorithm>
@ -747,6 +749,14 @@ int display::get_location_y(const map_location& loc) const
return static_cast<int>(map_area().y + (loc.y + theme_.border().size) * zoom_ - ypos_ + (is_odd(loc.x) ? zoom_/2 : 0));
}
point display::get_location(const map_location& loc) const
{
return {
get_location_x(loc),
get_location_y(loc)
};
}
map_location display::minimap_location_on(int x, int y)
{
// TODO: don't return location for this,
@ -1209,20 +1219,14 @@ void display::get_terrain_images(const map_location& loc, const std::string& tim
}
}
display::blit_helper& display::drawing_buffer_add(const drawing_layer layer,
const map_location& loc, const SDL_Rect& dest, const texture& tex)
namespace
{
drawing_buffer_.emplace_back(layer, loc, dest, tex);
return drawing_buffer_.back();
}
display::blit_helper& display::drawing_buffer_add(const drawing_layer layer,
const map_location& loc, const SDL_Rect& dest,
const std::vector<texture> &tex)
{
drawing_buffer_.emplace_back(layer, loc, dest, tex);
return drawing_buffer_.back();
}
constexpr std::array layer_groups {
display::LAYER_TERRAIN_BG,
display::LAYER_UNIT_FIRST,
display::LAYER_UNIT_MOVE_DEFAULT,
display::LAYER_REACHMAP // Make sure the movement doesn't show above fog and reachmap.
};
enum {
// you may adjust the following when needed:
@ -1244,36 +1248,60 @@ enum {
BITS_FOR_LAYER = 8,
// 9 least significant bits == x / 2 => 512 (really 1024 for x)
BITS_FOR_X_OVER_2 = 9
BITS_FOR_X_OVER_2 = 9,
SHIFT_LAYER = BITS_FOR_X_OVER_2,
SHIFT_X_PARITY = BITS_FOR_LAYER + SHIFT_LAYER,
SHIFT_Y = BITS_FOR_X_PARITY + SHIFT_X_PARITY,
SHIFT_LAYER_GROUP = BITS_FOR_Y + SHIFT_Y
};
inline display::drawing_buffer_key::drawing_buffer_key(const map_location &loc, drawing_layer layer)
: key_(0)
uint32_t generate_hex_key(const display::drawing_layer layer, const map_location& loc)
{
// Start with the index of last group entry...
unsigned int group_i = layer_groups.size() - 1;
uint32_t group_i = layer_groups.size() - 1;
// ...and works backwards until the group containing the specified layer is found.
while(layer < layer_groups[group_i]) {
--group_i;
}
enum {
SHIFT_LAYER = BITS_FOR_X_OVER_2,
SHIFT_X_PARITY = BITS_FOR_LAYER + SHIFT_LAYER,
SHIFT_Y = BITS_FOR_X_PARITY + SHIFT_X_PARITY,
SHIFT_LAYER_GROUP = BITS_FOR_Y + SHIFT_Y
};
static_assert(SHIFT_LAYER_GROUP + BITS_FOR_LAYER_GROUP == sizeof(key_) * 8, "Bit field too small");
// the parity of x must be more significant than the layer but less significant than y.
// Thus basically every row is split in two: First the row containing all the odd x
// then the row containing all the even x. Since thus the least significant bit of x is
// not required for x ordering anymore it can be shifted out to the right.
const unsigned int x_parity = static_cast<unsigned int>(loc.x) & 1;
key_ = (group_i << SHIFT_LAYER_GROUP) | (static_cast<unsigned int>(loc.y + MAX_BORDER) << SHIFT_Y);
key_ |= (x_parity << SHIFT_X_PARITY);
key_ |= (static_cast<unsigned int>(layer) << SHIFT_LAYER) | static_cast<unsigned int>(loc.x + MAX_BORDER) / 2;
const uint32_t x_parity = static_cast<uint32_t>(loc.x) & 1;
uint32_t key = 0;
static_assert(SHIFT_LAYER_GROUP + BITS_FOR_LAYER_GROUP == sizeof(key) * 8, "Bit field too small");
key = (group_i << SHIFT_LAYER_GROUP) | (static_cast<uint32_t>(loc.y + MAX_BORDER) << SHIFT_Y);
key |= (x_parity << SHIFT_X_PARITY);
key |= (static_cast<uint32_t>(layer) << SHIFT_LAYER) | static_cast<uint32_t>(loc.x + MAX_BORDER) / 2;
return key;
}
} // namespace
void display::drawing_buffer_add(const drawing_layer layer, const map_location& loc, decltype(draw_helper::do_draw) draw_func)
{
const rect dest {
get_location_x(loc),
get_location_y(loc),
int(zoom_),
int(zoom_)
};
// C++20 needed for in-place aggregate initilization
#ifdef HAVE_CXX20
drawing_buffer_.emplace_back(generate_hex_key(layer, loc), draw_func, dest);
#else
draw_helper temp{generate_hex_key(layer, loc), draw_func, dest};
drawing_buffer_.push_back(std::move(temp));
#endif // HAVE_CXX20
}
void display::drawing_buffer_commit()
@ -1281,7 +1309,7 @@ void display::drawing_buffer_commit()
// std::list::sort() is a stable sort
drawing_buffer_.sort();
auto clipper = draw::reduce_clip(map_area());
const auto clipper = draw::reduce_clip(map_area());
/*
* Info regarding the rendering algorithm.
@ -1293,37 +1321,12 @@ void display::drawing_buffer_commit()
* avoid decapitation a unit.
*
* This ended in the following priority order:
* layergroup > location > layer > 'blit_helper' > surface
* layergroup > location > layer > 'draw_helper' > surface
*/
for(const blit_helper& blit : drawing_buffer_) {
for(texture tex : blit.tex()) {
if (blit.alpha_mod != SDL_ALPHA_OPAQUE) {
tex.set_alpha_mod(blit.alpha_mod);
}
if (blit.r_mod != 255 || blit.g_mod != 255 || blit.b_mod != 255) {
tex.set_color_mod(blit.r_mod, blit.g_mod, blit.b_mod);
}
draw::flipped(tex, blit.dest(), blit.hflip, blit.vflip);
if (blit.highlight) {
tex.set_blend_mode(SDL_BLENDMODE_ADD);
tex.set_alpha_mod(blit.highlight);
draw::flipped(tex, blit.dest(), blit.hflip, blit.vflip);
tex.set_blend_mode(SDL_BLENDMODE_BLEND);
}
if (blit.r_mod != 255 || blit.g_mod != 255 || blit.b_mod != 255) {
tex.set_color_mod(255, 255, 255);
}
if (blit.alpha_mod != SDL_ALPHA_OPAQUE || blit.highlight) {
tex.set_alpha_mod(SDL_ALPHA_OPAQUE);
}
}
for(const draw_helper& helper : drawing_buffer_) {
std::invoke(helper.do_draw, helper.dest);
}
drawing_buffer_clear();
}
void display::drawing_buffer_clear()
{
drawing_buffer_.clear();
}
@ -1497,23 +1500,17 @@ void display::draw_text_in_hex(const map_location& loc,
renderer.set_foreground_color(color);
renderer.set_add_outline(true);
texture res = renderer.render_and_get_texture();
const rect dst {
get_location_x(loc) - (res.w() / 2) + static_cast<int>(x_in_hex * hex_size()),
get_location_y(loc) - (res.h() / 2) + static_cast<int>(y_in_hex * hex_size()),
res.w(),
res.h()
};
drawing_buffer_add(layer, loc, dst, res);
drawing_buffer_add(layer, loc, [x_in_hex, y_in_hex, res = renderer.render_and_get_texture()](const rect& dest) {
draw::blit(res, {
dest.x - (res.w() / 2) + static_cast<int>(x_in_hex * dest.w),
dest.y - (res.h() / 2) + static_cast<int>(y_in_hex * dest.h),
res.w(),
res.h()
});
});
}
static void add_submerge_ipf_mod(
std::string& image_path,
int image_height,
double submersion_amount,
int shift = 0)
void display::add_submerge_ipf_mod(std::string& image_path, int image_height, double submersion_amount, int shift)
{
// We may also want to shift the position so that the waterline matches.
// Note: This currently has blending problems (see the note on sdl_blit),
@ -1555,75 +1552,6 @@ static void add_submerge_ipf_mod(
}
}
void display::render_image(int x, int y, const display::drawing_layer drawing_layer,
const map_location& loc, const image::locator& i_locator,
bool hreverse, bool greyscale, uint8_t alpha, double highlight,
color_t blendto, double blend_ratio, double submerge, bool vreverse)
{
if (alpha == 0) {
return;
}
const point image_size = image::get_size(i_locator);
if (!image_size.x || !image_size.y) {
return;
}
rect dest = scaled_to_zoom({x, y, image_size.x, image_size.y});
if (!dest.overlaps(map_area())) {
return;
}
// For now, we add to the existing IPF modifications for the image.
std::string new_modifications;
if (greyscale) {
new_modifications += "~GS()";
}
add_submerge_ipf_mod(new_modifications, image_size.y, submerge);
texture tex;
if (!new_modifications.empty()) {
const image::locator modified_locator(
i_locator.get_filename(),
i_locator.get_modifications() + new_modifications
);
tex = image::get_texture(modified_locator);
} else {
tex = image::get_texture(i_locator);
}
// Clamp blend ratio so nothing weird happens
blend_ratio = std::clamp(blend_ratio, 0.0, 1.0);
blit_helper& bh = drawing_buffer_add(drawing_layer, loc, dest, tex);
bh.hflip = hreverse;
bh.vflip = vreverse;
bh.alpha_mod = alpha;
bh.highlight = float_to_color(highlight);
// SDL hax to apply an active washout tint at the correct ratio
if (blend_ratio > 0.0) {
// Get a pure-white version of the texture
const image::locator whiteout_locator(
i_locator.get_filename(),
i_locator.get_modifications()
+ new_modifications
+ "~CHAN(255, 255, 255, alpha)"
);
const texture& wt = image::get_texture(whiteout_locator);
// Blit the pure white version, tinted to the right colour
blit_helper &wh = drawing_buffer_add(drawing_layer, loc, dest, wt);
wh.hflip = hreverse;
wh.vflip = vreverse;
wh.alpha_mod = uint8_t(alpha * blend_ratio);
wh.r_mod = blendto.r;
wh.g_mod = blendto.g;
wh.b_mod = blendto.b;
}
}
void display::select_hex(map_location hex)
{
invalidate(selectedHex_);
@ -1768,7 +1696,7 @@ void display::draw_minimap()
return;
}
auto clipper = draw::reduce_clip(area);
const auto clipper = draw::reduce_clip(area);
// Draw the minimap background.
draw::fill(area, 31, 31, 23);
@ -2731,74 +2659,87 @@ rect display::get_clip_rect() const
return map_area();
}
void display::draw_invalidated() {
// log_scope("display::draw_invalidated");
void display::draw_invalidated()
{
// log_scope("display::draw_invalidated");
SDL_Rect clip_rect = get_clip_rect();
auto clipper = draw::reduce_clip(clip_rect);
DBG_DP << "drawing " << invalidated_.size() << " invalidated hexes"
<< " with clip " << clip_rect;
for (const map_location& loc : invalidated_) {
const auto clipper = draw::reduce_clip(clip_rect);
DBG_DP << "drawing " << invalidated_.size() << " invalidated hexes with clip " << clip_rect;
// The unit drawer can't function without teams
std::optional<unit_drawer> drawer{};
if(!dc_->teams().empty()) {
drawer.emplace(*this);
}
for(const map_location& loc : invalidated_) {
int xpos = get_location_x(loc);
int ypos = get_location_y(loc);
//const bool on_map = get_map().on_board(loc);
rect hex_rect(xpos, ypos, zoom_, zoom_);
if(!hex_rect.overlaps(clip_rect)) {
continue;
}
draw_hex(loc);
drawn_hexes_+=1;
drawn_hexes_ += 1;
if(drawer) {
const auto u_it = dc_->units().find(loc);
const auto request = exclusive_unit_draw_requests_.find(loc);
if(u_it != dc_->units().end() && (request == exclusive_unit_draw_requests_.end() || request->second == u_it->id())) {
drawer->redraw_unit(*u_it);
}
}
draw_manager::invalidate_region(hex_rect.intersect(clip_rect));
}
invalidated_hexes_ += invalidated_.size();
// The unit drawer can't function without teams
if(dc_->teams().empty()) {
return;
}
unit_drawer drawer = unit_drawer(*this);
for(const map_location& loc : invalidated_) {
unit_map::const_iterator u_it = dc_->units().find(loc);
exclusive_unit_draw_requests_t::iterator request = exclusive_unit_draw_requests_.find(loc);
if(u_it != dc_->units().end()
&& (request == exclusive_unit_draw_requests_.end() || request->second == u_it->id())) {
drawer.redraw_unit(*u_it);
}
}
}
void display::draw_hex(const map_location& loc)
{
int xpos = get_location_x(loc);
int ypos = get_location_y(loc);
const bool on_map = get_map().on_board(loc);
const time_of_day& tod = get_time_of_day(loc);
const int zoom = int(zoom_);
SDL_Rect dest{xpos, ypos, zoom, zoom};
int num_images_fg = 0;
int num_images_bg = 0;
if(!shrouded(loc)) {
// unshrouded terrain (the normal case)
const bool is_shrouded = shrouded(loc);
// unshrouded terrain (the normal case)
if(!is_shrouded) {
get_terrain_images(loc, tod.id, BACKGROUND); // updates terrain_image_vector_
drawing_buffer_add(LAYER_TERRAIN_BG, loc, dest, terrain_image_vector_);
num_images_bg = terrain_image_vector_.size();
drawing_buffer_add(LAYER_TERRAIN_BG, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
for(const texture& t : images) {
draw::blit(t, dest);
}
});
get_terrain_images(loc, tod.id, FOREGROUND); // updates terrain_image_vector_
drawing_buffer_add(LAYER_TERRAIN_FG, loc, dest, terrain_image_vector_);
num_images_fg = terrain_image_vector_.size();
drawing_buffer_add(LAYER_TERRAIN_BG, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
for(const texture& t : images) {
draw::blit(t, dest);
}
});
// Draw the grid, if that's been enabled
if(preferences::grid()) {
static const image::locator grid_top(game_config::images::grid_top);
drawing_buffer_add(LAYER_GRID_TOP, loc, dest,
image::get_texture(grid_top, image::TOD_COLORED));
static const image::locator grid_bottom(game_config::images::grid_bottom);
drawing_buffer_add(LAYER_GRID_BOTTOM, loc, dest,
image::get_texture(grid_bottom, image::TOD_COLORED));
static const image::locator grid_top{game_config::images::grid_top};
static const image::locator grid_bottom{game_config::images::grid_bottom};
drawing_buffer_add(LAYER_GRID_TOP, loc,
[tex = image::get_texture(grid_top, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
drawing_buffer_add(LAYER_GRID_BOTTOM, loc,
[tex = image::get_texture(grid_bottom, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
}
}
@ -2806,7 +2747,7 @@ void display::draw_hex(const map_location& loc)
const terrain_type& terrain_info = get_map().get_terrain_info(terrain);
const double submerge = terrain_info.unit_submerge();
if(!shrouded(loc)) {
if(!is_shrouded) {
auto it = get_overlays().find(loc);
if(it != get_overlays().end()) {
std::vector<overlay>& overlays = it->second;
@ -2817,7 +2758,7 @@ void display::draw_hex(const map_location& loc)
for(const overlay& ov : overlays) {
bool item_visible_for_team = true;
if(dont_show_all_ && !ov.team_name.empty()) {
//dont_show_all_ imples that viewing_team()is a valid index to get_teams()
// dont_show_all_ imples that viewing_team() is a valid index to get_teams()
const std::string& current_team_name = get_teams()[viewing_team()].team_name();
const std::vector<std::string>& current_team_names = utils::split(current_team_name);
const std::vector<std::string>& team_names = utils::split(ov.team_name);
@ -2825,10 +2766,11 @@ void display::draw_hex(const map_location& loc)
item_visible_for_team = std::find_first_of(team_names.begin(), team_names.end(),
current_team_names.begin(), current_team_names.end()) != team_names.end();
}
if(item_visible_for_team && !(fogged(loc) && !ov.visible_in_fog))
{
if(item_visible_for_team && !(fogged(loc) && !ov.visible_in_fog)) {
point isize = image::get_size(ov.image, image::HEXED);
std::string ipf = ov.image;
if(ov.submerge) {
// Adjust submerge appropriately
double sub = submerge * ov.submerge;
@ -2837,32 +2779,37 @@ void display::draw_hex(const map_location& loc)
int shift = isize.y * (sub - submerge);
add_submerge_ipf_mod(ipf, isize.y, sub, shift);
}
const texture tex = ov.image.find("~NO_TOD_SHIFT()") == std::string::npos
? image::get_lighted_texture(ipf, lt)
: image::get_texture(ipf, image::HEXED);
drawing_buffer_add(LAYER_TERRAIN_BG, loc, dest, tex);
drawing_buffer_add(LAYER_TERRAIN_BG, loc, [tex](const rect& dest) { draw::blit(tex, dest); });
}
}
}
}
}
if(!shrouded(loc)) {
// village-control flags.
drawing_buffer_add(LAYER_TERRAIN_BG, loc, dest, get_flag(loc));
// village-control flags.
if(!is_shrouded) {
drawing_buffer_add(LAYER_TERRAIN_BG, loc, [tex = get_flag(loc)](const rect& dest) { draw::blit(tex, dest); });
}
// Draw the time-of-day mask on top of the terrain in the hex.
// tod may differ from tod if hex is illuminated.
const std::string& tod_hex_mask = tod.image_mask;
if(tod_hex_mask1 || tod_hex_mask2) {
auto& a = drawing_buffer_add(LAYER_TERRAIN_FG, loc, dest, tod_hex_mask1);
a.alpha_mod = tod_hex_alpha1;
auto& b = drawing_buffer_add(LAYER_TERRAIN_FG, loc, dest, tod_hex_mask2);
b.alpha_mod = tod_hex_alpha2;
drawing_buffer_add(LAYER_TERRAIN_FG, loc, [=](const rect& dest) mutable {
tod_hex_mask1.set_alpha_mod(tod_hex_alpha1);
draw::blit(tod_hex_mask1, dest);
tod_hex_mask2.set_alpha_mod(tod_hex_alpha2);
draw::blit(tod_hex_mask2, dest);
});
} else if(!tod_hex_mask.empty()) {
drawing_buffer_add(LAYER_TERRAIN_FG, loc, dest,
image::get_texture(tod_hex_mask,image::HEXED));
drawing_buffer_add(LAYER_TERRAIN_FG, loc,
[tex = image::get_texture(tod_hex_mask, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
}
// Paint mouseover overlays
@ -2871,10 +2818,10 @@ void display::draw_hex(const map_location& loc)
&& !map_screenshot_
&& bool(mouseover_hex_overlay_))
{
const uint8_t alpha = 196;
blit_helper& bh = drawing_buffer_add(LAYER_MOUSEOVER_OVERLAY,
loc, dest, mouseover_hex_overlay_);
bh.alpha_mod = alpha;
drawing_buffer_add(LAYER_MOUSEOVER_OVERLAY, loc, [this](const rect& dest) {
mouseover_hex_overlay_.set_alpha_mod(196);
draw::blit(mouseover_hex_overlay_, dest);
});
}
// Paint arrows
@ -2887,82 +2834,83 @@ void display::draw_hex(const map_location& loc)
// Apply shroud, fog and linger overlay
if(shrouded(loc)) {
// We apply void also on off-map tiles
// to shroud the half-hexes too
const std::string& shroud_image = get_variant(shroud_images_, loc);
drawing_buffer_add(LAYER_FOG_SHROUD, loc, dest,
image::get_texture(shroud_image, image::TOD_COLORED));
if(is_shrouded) {
// We apply void also on off-map tiles to shroud the half-hexes too
drawing_buffer_add(LAYER_FOG_SHROUD, loc,
[tex = image::get_texture(get_variant(shroud_images_, loc), image::TOD_COLORED)](const rect& dest) {
draw::blit(tex, dest);
});
} else if(fogged(loc)) {
const std::string& fog_image = get_variant(fog_images_, loc);
drawing_buffer_add(LAYER_FOG_SHROUD, loc, dest,
image::get_texture(fog_image, image::TOD_COLORED));
drawing_buffer_add(LAYER_FOG_SHROUD, loc,
[tex = image::get_texture(get_variant(fog_images_, loc), image::TOD_COLORED)](const rect& dest) {
draw::blit(tex, dest);
});
}
if(!shrouded(loc)) {
drawing_buffer_add(LAYER_FOG_SHROUD, loc, dest, get_fog_shroud_images(loc, image::TOD_COLORED));
}
if (on_map) {
texture bg = image::get_texture("misc/single-pixel.png");
color_t bg_col = {0, 0, 0, 0xaa};
if (draw_coordinates_) {
int off_x = xpos + hex_size()/2;
int off_y = ypos + hex_size()/2;
texture text = font::pango_render_text(lexical_cast<std::string>(loc), font::SIZE_SMALL, font::NORMAL_COLOR);
off_x -= text.w() / 2;
off_y -= text.h() / 2;
if (draw_terrain_codes_) {
off_y -= text.h() / 2;
if(!is_shrouded) {
drawing_buffer_add(LAYER_FOG_SHROUD, loc, [images = get_fog_shroud_images(loc, image::TOD_COLORED)](const rect& dest) {
for(const texture& t : images) {
draw::blit(t, dest);
}
if (draw_num_of_bitmaps_) {
off_y -= text.h() / 2;
}
rect tdest {off_x, off_y, text.w(), text.h()};
drawing_buffer_add(LAYER_FOG_SHROUD, loc, tdest, bg)
.set_color_and_alpha(bg_col);
drawing_buffer_add(LAYER_FOG_SHROUD, loc, tdest, text);
}
if (draw_terrain_codes_ && (game_config::debug || !shrouded(loc))) {
int off_x = xpos + hex_size()/2;
int off_y = ypos + hex_size()/2;
texture text = font::pango_render_text(lexical_cast<std::string>(get_map().get_terrain(loc)), font::SIZE_SMALL, font::NORMAL_COLOR);
off_x -= text.w() / 2;
off_y -= text.h() / 2;
if (draw_coordinates_ && !draw_num_of_bitmaps_) {
off_y += text.h() / 2;
} else if (draw_num_of_bitmaps_ && !draw_coordinates_) {
off_y -= text.h() / 2;
}
rect tdest {off_x, off_y, text.w(), text.h()};
drawing_buffer_add(LAYER_FOG_SHROUD, loc, tdest, bg)
.set_color_and_alpha(bg_col);
drawing_buffer_add(LAYER_FOG_SHROUD, loc, tdest, text);
}
if (draw_num_of_bitmaps_) {
int off_x = xpos + hex_size()/2;
int off_y = ypos + hex_size()/2;
texture text = font::pango_render_text(std::to_string(num_images_bg + num_images_fg), font::SIZE_SMALL, font::NORMAL_COLOR);
off_x -= text.w() / 2;
off_y -= text.h() / 2;
if (draw_coordinates_) {
off_y += text.h() / 2;
}
if (draw_terrain_codes_) {
off_y += text.h() / 2;
}
rect tdest {off_x, off_y, text.w(), text.h()};
drawing_buffer_add(LAYER_FOG_SHROUD, loc, tdest, bg)
.set_color_and_alpha(bg_col);
drawing_buffer_add(LAYER_FOG_SHROUD, loc, tdest, text);
}
});
}
if(debug_foreground) {
drawing_buffer_add(LAYER_UNIT_DEFAULT, loc, dest,
image::get_texture("terrain/foreground.png", image::TOD_COLORED));
drawing_buffer_add(
LAYER_UNIT_DEFAULT, loc, [tex = image::get_texture("terrain/foreground.png", image::TOD_COLORED)](const rect& dest) {
draw::blit(tex, dest);
});
}
if(on_map) {
std::ostringstream ss;
if(draw_coordinates_) {
ss << loc << '\n';
}
if(draw_terrain_codes_ && (game_config::debug || !is_shrouded)) {
ss << get_map().get_terrain(loc) << '\n';
}
if(draw_num_of_bitmaps_) {
ss << (num_images_bg + num_images_fg) << '\n';
}
std::string output = ss.str();
boost::trim(output);
if(output.empty()) {
return;
}
auto& renderer = font::get_text_renderer();
renderer.set_text(output, false);
renderer.set_font_size(font::SIZE_TINY);
renderer.set_alignment(PANGO_ALIGN_CENTER);
renderer.set_foreground_color(font::NORMAL_COLOR);
renderer.set_maximum_height(-1, false);
renderer.set_maximum_width(-1);
drawing_buffer_add(LAYER_FOG_SHROUD, loc, [tex = renderer.render_and_get_texture()](const rect& dest) {
const rect text_dest {
(dest.x + dest.w / 2) - (tex.w() / 2),
(dest.y + dest.h / 2) - (tex.h() / 2),
tex.w(),
tex.h()
};
// Add a little horizontal padding to the bg
const rect bg_dest {
text_dest.x - 3,
text_dest.y - 3,
text_dest.w + 6,
text_dest.h + 6
};
draw::fill(bg_dest, {0, 0, 0, 0xaa});
draw::blit(tex, text_dest);
});
}
}
/**

View file

@ -258,10 +258,16 @@ public:
}
/** Scale the width and height of a rect by the current zoom factor */
static SDL_Rect scaled_to_zoom(const SDL_Rect& r)
static rect scaled_to_zoom(const SDL_Rect& r)
{
const double zf = get_zoom_factor();
return {r.x, r.y, int(r.w*zf), int(r.h*zf)};
return {r.x, r.y, int(r.w * zf), int(r.h * zf)};
}
static point scaled_to_zoom(const point& p)
{
const double zf = get_zoom_factor();
return {int(p.x * zf), int(p.y * zf)};
}
/**
@ -297,6 +303,7 @@ public:
/** Functions to get the on-screen positions of hexes. */
int get_location_x(const map_location& loc) const;
int get_location_y(const map_location& loc) const;
point get_location(const map_location& loc) const;
/**
* Rectangular area of hexes, allowing to decide how the top and bottom
@ -860,23 +867,7 @@ public:
LAYER_BORDER, /**< The border of the map. */
};
/**
* Draw an image at a certain location.
* x,y: pixel location on screen to draw the image
* image: the image to draw
* reverse: if the image should be flipped across the x axis
* greyscale: used for instance to give the petrified appearance to a unit image
* alpha: the merging to use with the background
* blendto: blend to this color using blend_ratio
* submerged: the amount of the unit out of 1.0 that is submerged
* (presumably under water) and thus shouldn't be drawn
*/
void render_image(int x, int y, const display::drawing_layer drawing_layer,
const map_location& loc, const image::locator& i_locator,
bool hreverse=false, bool greyscale=false,
uint8_t alpha=SDL_ALPHA_OPAQUE, double highlight=0.0,
color_t blendto={0,0,0}, double blend_ratio=0,
double submerged=0.0, bool vreverse=false);
static void add_submerge_ipf_mod(std::string& image_path, int image_height, double submersion_amount, int shift = 0);
/**
* Draw text on a hex. (0.5, 0.5) is the center.
@ -892,11 +883,13 @@ protected:
std::size_t activeTeam_;
/**
* In order to render a hex properly it needs to be rendered per row. On
* this row several layers need to be drawn at the same time. Mainly the
* unit and the background terrain. This is needed since both can spill
* in the next hex. The foreground terrain needs to be drawn before to
* avoid decapitation a unit.
* Helper for rendering the map by ordering draw operations.
*
* In order to render a hex properly, they need to be rendered per row.
* In this row several layers need to be drawn at the same time, mainly
* the unit and the background terrain. This is needed since both can spill
* into the next hex. The foreground terrain needs to be drawn before to
* avoid decapitating a unit.
*
* In other words:
* for every layer
@ -910,163 +903,41 @@ protected:
* for every layer in the group
* for every hex in the row
* ...
*
* * textures are rendered per level in a map.
* * Per level the items are rendered per location these locations are
* stored in the drawing order required for units.
* * every location has a vector with textures, each with its own screen
* coordinate to render at.
* * every vector element has a vector with textures to render.
*/
class drawing_buffer_key
struct draw_helper
{
private:
unsigned int key_;
/** Controls the ordering of draw calls by layer and location. */
const uint32_t key;
// FIXME: temporary method. Group splitting should be made
// public into the definition of drawing_layer
//
// The drawing is done per layer_group, the range per group is [low, high).
static inline const std::array layer_groups {
LAYER_TERRAIN_BG,
LAYER_UNIT_FIRST,
LAYER_UNIT_MOVE_DEFAULT,
// Make sure the movement doesn't show above fog and reachmap.
LAYER_REACHMAP
};
/** Handles the actual drawing at this location. */
std::function<void(const rect&)> do_draw;
public:
drawing_buffer_key(const map_location &loc, drawing_layer layer);
/** The screen coordinates for the specified hex. This is passed to @ref do_draw */
rect dest;
bool operator<(const drawing_buffer_key &rhs) const { return key_ < rhs.key_; }
bool operator<(const draw_helper& rhs) const
{
return key < rhs.key;
}
};
/** Helper structure for rendering the terrains. */
class blit_helper
{
public:
// We don't want to copy this.
// It's expensive when done frequently due to the texture vector.
blit_helper(const blit_helper&) = delete;
blit_helper(const drawing_layer layer, const map_location& loc,
const SDL_Rect& dest, const texture& tex)
: dest_(dest), tex_(1, tex), key_(loc, layer)
{}
blit_helper(const drawing_layer layer, const map_location& loc,
const SDL_Rect& dest, const std::vector<texture>& tex)
: dest_(dest), tex_(tex), key_(loc, layer)
{}
const SDL_Rect& dest() const { return dest_; }
const std::vector<texture> &tex() const { return tex_; }
bool operator<(const blit_helper &rhs) const { return key_ < rhs.key_; }
public:
// Auxiliary parameters, can be modified directly as required.
/** Whether to mirror horizontally on draw */
bool hflip = false;
/** Whether to mirror vertically on draw */
bool vflip = false;
/** An alpha modifier to apply when drawing. 0-255. */
uint8_t alpha_mod = SDL_ALPHA_OPAQUE;
/** Colour modifiers. Multiply colour. 0 = 0.0, 255 = 1.0. */
uint8_t r_mod = 255;
uint8_t g_mod = 255;
uint8_t b_mod = 255;
/** Strength of highlight effect to apply, if any. */
uint8_t highlight = 0;
// Or they can be set in a chain.
blit_helper& set_color_and_alpha(color_t c)
{
this->r_mod = c.r; this->g_mod = c.g; this->b_mod = c.b;
this->alpha_mod = c.a;
return *this;
}
blit_helper& set_color_and_alpha(
uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
this->r_mod = r; this->g_mod = g; this->b_mod = b;
this->alpha_mod = a;
return *this;
}
blit_helper& set_color(color_t c)
{
this->r_mod = c.r; this->g_mod = c.g; this->b_mod = c.b;
return *this;
}
blit_helper& set_color(uint8_t r, uint8_t g, uint8_t b)
{
this->r_mod = r; this->g_mod = g; this->b_mod = b;
return *this;
}
blit_helper& set_alpha(uint8_t alpha)
{
this->alpha_mod = alpha; return *this;
}
blit_helper& set_hflip(bool hflip)
{
this->hflip = hflip; return *this;
}
blit_helper& set_vflip(bool vflip)
{
this->vflip = vflip; return *this;
}
blit_helper& set_highlight(uint8_t highlight)
{
this->highlight = highlight; return *this;
}
private:
// Core info is set on creation.
/** The location on screen to draw to, in drawing coordinates. */
SDL_Rect dest_;
/** One or more textures to render. */
std::vector<texture> tex_;
// TODO: could also add blend mode and rotation if desirable
/** Allows ordering of draw calls by layer and location. */
drawing_buffer_key key_;
};
typedef std::list<blit_helper> drawing_buffer;
drawing_buffer drawing_buffer_;
std::list<draw_helper> drawing_buffer_;
public:
/**
* Add an item to the drawing buffer.
*
* This returns a blit_helper reference with several extra fields that can
* be modified as necessary. In particular hflip, vflip and alpha_mod
* have been moved to this helper. Fields that can be modified are
* available as public members of blit_helper.
*
* @param layer The layer to draw on.
* @param loc The hex the image belongs to, needed for the
* drawing order.
* @param dest The target destination on screen,
* in drawing coordinates.
* @param tex The texture to use.
* @param loc The hex the image belongs to, needed for the drawing order.
* @param draw_func The draw operation to be run.
*/
blit_helper& drawing_buffer_add(const drawing_layer layer,
const map_location& loc, const SDL_Rect& dest, const texture& tex);
blit_helper& drawing_buffer_add(const drawing_layer layer,
const map_location& loc, const SDL_Rect& dest,
const std::vector<texture> &tex);
void drawing_buffer_add(const drawing_layer layer, const map_location& loc, decltype(draw_helper::do_draw) draw_func);
protected:
/** Draws the drawing_buffer_ and clears it. */
void drawing_buffer_commit();
/** Clears the drawing buffer. */
void drawing_buffer_clear();
/** Redraws all panels intersecting the given region.
* Returns true if something was drawn, false otherwise. */
bool draw_all_panels(const rect& region);

View file

@ -15,6 +15,7 @@
#define GETTEXT_DOMAIN "wesnoth-editor"
#include "draw.hpp"
#include "editor/controller/editor_controller.hpp"
#include "editor/editor_display.hpp"
#include "lexical_cast.hpp"
@ -68,22 +69,20 @@ void editor_display::rebuild_terrain(const map_location &loc) {
void editor_display::draw_hex(const map_location& loc)
{
int xpos = get_location_x(loc);
int ypos = get_location_y(loc);
display::draw_hex(loc);
if (map().on_board_with_border(loc) && !map_screenshot_) {
if (map().in_selection(loc)) {
const texture tex = image::get_texture(
"editor/selection-overlay.png", image::TOD_COLORED);
SDL_Rect dest = scaled_to_zoom({xpos, ypos, tex.w(), tex.h()});
drawing_buffer_add(LAYER_FOG_SHROUD, loc, dest, tex);
if(map().in_selection(loc)) {
drawing_buffer_add(LAYER_FOG_SHROUD, loc,
[tex = image::get_texture("editor/selection-overlay.png", image::TOD_COLORED)](const rect& d) {
draw::blit(tex, scaled_to_zoom({d.x, d.y, tex.w(), tex.h()}));
});
}
if (brush_locations_.find(loc) != brush_locations_.end()) {
if(brush_locations_.find(loc) != brush_locations_.end()) {
static const image::locator brush(game_config::images::editor_brush);
const texture tex = image::get_texture(brush, image::HEXED);
SDL_Rect dest = scaled_to_zoom({xpos, ypos, tex.w(), tex.h()});
drawing_buffer_add(LAYER_SELECTED_HEX, loc, dest, tex);
drawing_buffer_add(LAYER_SELECTED_HEX, loc, [tex = image::get_texture(brush, image::HEXED)](const rect& d) {
draw::blit(tex, scaled_to_zoom({d.x, d.y, tex.w(), tex.h()}));
});
}
}
}

View file

@ -46,6 +46,7 @@
#include "utils/general.hpp"
#include "whiteboard/manager.hpp"
#include "overlay.hpp"
#include "draw.hpp"
static lg::log_domain log_display("display");
#define ERR_DP LOG_STREAM(err, log_display)
@ -218,15 +219,25 @@ void game_display::draw_invalidated()
}
}
namespace
{
const std::string mouseover_normal_top = "misc/hover-hex-top.png~RC(magenta>gold)";
const std::string mouseover_normal_bot = "misc/hover-hex-bottom.png~RC(magenta>gold)";
const std::string mouseover_enemy_top = "misc/hover-hex-enemy-top.png~RC(magenta>red)";
const std::string mouseover_enemy_bot = "misc/hover-hex-enemy-bottom.png~RC(magenta>red)";
const std::string mouseover_self_top = "misc/hover-hex-top.png~RC(magenta>green)";
const std::string mouseover_self_bot = "misc/hover-hex-bottom.png~RC(magenta>green)";
const std::string mouseover_ally_top = "misc/hover-hex-top.png~RC(magenta>lightblue)";
const std::string mouseover_ally_bot = "misc/hover-hex-bottom.png~RC(magenta>lightblue)";
}
void game_display::draw_hex(const map_location& loc)
{
const bool on_map = get_map().on_board(loc);
const bool is_shrouded = shrouded(loc);
// const bool is_fogged = fogged(loc);
const int xpos = get_location_x(loc);
const int ypos = get_location_y(loc);
const int zoom = int(zoom_);
const SDL_Rect dest{xpos, ypos, zoom, zoom};
display::draw_hex(loc);
@ -237,77 +248,83 @@ void game_display::draw_hex(const map_location& loc)
if(on_map && loc == mouseoverHex_ && !map_screenshot_) {
drawing_layer hex_top_layer = LAYER_MOUSEOVER_BOTTOM;
const unit *u = resources::gameboard->get_visible_unit(loc, dc_->teams()[viewing_team()] );
if( u != nullptr ) {
const unit* u = resources::gameboard->get_visible_unit(loc, dc_->teams()[viewing_team()]);
if(u != nullptr) {
hex_top_layer = LAYER_MOUSEOVER_TOP;
}
const std::string* mo_top_path;
const std::string* mo_bot_path;
if(u == nullptr) {
drawing_buffer_add( hex_top_layer, loc, dest,
image::get_texture("misc/hover-hex-top.png~RC(magenta>gold)", image::HEXED));
drawing_buffer_add(LAYER_MOUSEOVER_BOTTOM, loc, dest,
image::get_texture("misc/hover-hex-bottom.png~RC(magenta>gold)", image::HEXED));
mo_top_path = &mouseover_normal_top;
mo_bot_path = &mouseover_normal_bot;
} else if(dc_->teams()[currentTeam_].is_enemy(u->side())) {
drawing_buffer_add( hex_top_layer, loc, dest,
image::get_texture("misc/hover-hex-enemy-top.png~RC(magenta>red)", image::HEXED));
drawing_buffer_add(LAYER_MOUSEOVER_BOTTOM, loc, dest,
image::get_texture("misc/hover-hex-enemy-bottom.png~RC(magenta>red)", image::HEXED));
mo_top_path = &mouseover_enemy_top;
mo_bot_path = &mouseover_enemy_bot;
} else if(dc_->teams()[currentTeam_].side() == u->side()) {
drawing_buffer_add( hex_top_layer, loc, dest,
image::get_texture("misc/hover-hex-top.png~RC(magenta>green)", image::HEXED));
drawing_buffer_add(LAYER_MOUSEOVER_BOTTOM, loc, dest,
image::get_texture("misc/hover-hex-bottom.png~RC(magenta>green)", image::HEXED));
mo_top_path = &mouseover_self_top;
mo_bot_path = &mouseover_self_bot;
} else {
drawing_buffer_add( hex_top_layer, loc, dest,
image::get_texture("misc/hover-hex-top.png~RC(magenta>lightblue)", image::HEXED));
drawing_buffer_add(LAYER_MOUSEOVER_BOTTOM, loc, dest,
image::get_texture("misc/hover-hex-bottom.png~RC(magenta>lightblue)", image::HEXED));
mo_top_path = &mouseover_ally_top;
mo_bot_path = &mouseover_ally_bot;
}
drawing_buffer_add(hex_top_layer, loc,
[tex = image::get_texture(*mo_top_path, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
drawing_buffer_add(LAYER_MOUSEOVER_BOTTOM, loc,
[tex = image::get_texture(*mo_bot_path, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
}
// Draw reach_map information.
// We remove the reachability mask of the unit
// that we want to attack.
if (!is_shrouded && !reach_map_.empty()
&& reach_map_.find(loc) == reach_map_.end() && loc != attack_indicator_dst_) {
// We remove the reachability mask of the unit that we want to attack.
if(!is_shrouded && !reach_map_.empty() && reach_map_.find(loc) == reach_map_.end() && loc != attack_indicator_dst_) {
static const image::locator unreachable(game_config::images::unreachable);
drawing_buffer_add(LAYER_REACHMAP, loc, dest,
image::get_texture(unreachable,image::HEXED));
drawing_buffer_add(LAYER_REACHMAP, loc,
[tex = image::get_texture(unreachable, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
}
if (std::shared_ptr<wb::manager> w = wb_.lock()) {
if(std::shared_ptr<wb::manager> w = wb_.lock()) {
w->draw_hex(loc);
if (!(w->is_active() && w->has_temp_move()))
{
if(!(w->is_active() && w->has_temp_move())) {
std::vector<texture> footstepImages = footsteps_images(loc, route_, dc_);
if (!footstepImages.empty()) {
drawing_buffer_add(LAYER_FOOTSTEPS, loc, dest, footstepImages);
if(!footstepImages.empty()) {
drawing_buffer_add(LAYER_FOOTSTEPS, loc, [images = std::move(footstepImages)](const rect& dest) {
for(const texture& t : images) {
draw::blit(t, dest);
}
});
}
}
}
// Draw the attack direction indicator
if(on_map && loc == attack_indicator_src_) {
drawing_buffer_add(LAYER_ATTACK_INDICATOR, loc, dest,
image::get_texture("misc/attack-indicator-src-" + attack_indicator_direction() + ".png", image::HEXED));
} else if (on_map && loc == attack_indicator_dst_) {
drawing_buffer_add(LAYER_ATTACK_INDICATOR, loc, dest,
image::get_texture("misc/attack-indicator-dst-" + attack_indicator_direction() + ".png", image::HEXED));
drawing_buffer_add(LAYER_ATTACK_INDICATOR, loc,
[tex = image::get_texture("misc/attack-indicator-src-" + attack_indicator_direction() + ".png", image::HEXED)](const rect& dest)
{ draw::blit(tex, dest); }
);
} else if(on_map && loc == attack_indicator_dst_) {
drawing_buffer_add(LAYER_ATTACK_INDICATOR, loc,
[tex = image::get_texture("misc/attack-indicator-dst-" + attack_indicator_direction() + ".png", image::HEXED)](const rect& dest)
{ draw::blit(tex, dest); }
);
}
// Linger overlay unconditionally otherwise it might give glitches
// so it's drawn over the shroud and fog.
if(mode_ != RUNNING) {
static const image::locator linger(game_config::images::linger);
drawing_buffer_add(LAYER_LINGER_OVERLAY, loc, dest,
image::get_texture(linger, image::TOD_COLORED));
drawing_buffer_add(LAYER_LINGER_OVERLAY, loc,
[tex = image::get_texture(linger, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
}
if(on_map && loc == selectedHex_ && !game_config::images::selected.empty()) {
static const image::locator selected(game_config::images::selected);
drawing_buffer_add(LAYER_SELECTED_HEX, loc, dest,
image::get_texture(selected, image::HEXED));
drawing_buffer_add(LAYER_SELECTED_HEX, loc,
[tex = image::get_texture(selected, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
}
// Show def% and turn to reach info
@ -322,7 +339,6 @@ void game_display::draw_hex(const map_location& loc)
draw_text_in_hex(loc, LAYER_MOVE_INFO, txt, 18, font::BAD_COLOR);
}
}
//simulate_delay += 1;
}
const time_of_day& game_display::get_time_of_day(const map_location& loc) const
@ -395,25 +411,20 @@ void game_display::draw_movement_info(const map_location& loc)
int def_font = w->second.turns > 0 ? 18 : 16;
draw_text_in_hex(loc, LAYER_MOVE_INFO, def_text.str(), def_font, color);
const int xpos = get_location_x(loc);
const int ypos = get_location_y(loc);
const int zoom = int(zoom_);
const SDL_Rect dest{xpos, ypos, zoom, zoom};
drawing_buffer_add(LAYER_MOVE_INFO, loc,
[inv = w->second.invisible, zoc = w->second.zoc, cap = w->second.capture](const rect& dest) {
if(inv) {
draw::blit(image::get_texture("misc/hidden.png", image::HEXED), dest);
}
if (w->second.invisible) {
drawing_buffer_add(LAYER_MOVE_INFO, loc, dest,
image::get_texture("misc/hidden.png", image::HEXED));
}
if(zoc) {
draw::blit(image::get_texture("misc/zoc.png", image::HEXED), dest);
}
if (w->second.zoc) {
drawing_buffer_add(LAYER_MOVE_INFO, loc, dest,
image::get_texture("misc/zoc.png", image::HEXED));
}
if (w->second.capture) {
drawing_buffer_add(LAYER_MOVE_INFO, loc, dest,
image::get_texture("misc/capture.png", image::HEXED));
}
if(cap) {
draw::blit(image::get_texture("misc/capture.png", image::HEXED), dest);
}
});
//we display turn info only if different from a simple last "1"
if (w->second.turns > 1 || (w->second.turns == 1 && loc != route_.steps.back())) {

View file

@ -18,6 +18,7 @@
#include "color.hpp"
#include "display.hpp"
#include "display_context.hpp"
#include "draw.hpp"
#include "formatter.hpp"
#include "game_display.hpp"
#include "halo.hpp"
@ -27,7 +28,6 @@
#include "picture.hpp"
#include "preferences/game.hpp"
#include "sdl/surface.hpp"
#include "sdl/utils.hpp" // scale_surface_nn, fill_surface_rect
#include "team.hpp"
#include "units/animation.hpp"
#include "units/animation_component.hpp"
@ -61,25 +61,67 @@ std::unique_ptr<image::locator> get_orb_image(orb_status os)
auto color = orb_status_helper::get_orb_color(os);
return std::make_unique<image::locator>(game_config::images::orb + "~RC(magenta>" + color + ")");
}
void draw_bar(int xpos, int ypos, int bar_height, double filled, const color_t& col)
{
// Magic width number
static constexpr unsigned int bar_width = 4;
static constexpr color_t bar_color_bg{0, 0, 0, 80};
static constexpr color_t bar_color_border{213, 213, 213, 200};
// We used to use an image for the bar instead of drawing it procedurally. Its x,y position
// within the file was 19,13, so we offset the origin by that much to make it line up with
// the crowns as before. Should probably compensate for this better in the future.
const point offset = display::scaled_to_zoom(point{19, 13});
// Full bar dimensions.
const rect bar_rect = display::scaled_to_zoom({
xpos + offset.x,
ypos + offset.y,
bar_width,
bar_height
});
filled = std::clamp<double>(filled, 0.0, 1.0);
const int unfilled = static_cast<std::size_t>(bar_rect.h * (1.0 - filled));
// Filled area dimensions.
const rect fill_rect {
bar_rect.x,
bar_rect.y + unfilled,
bar_rect.w,
bar_rect.h - unfilled
};
// Tinted background.
draw::fill(bar_rect, bar_color_bg);
// Filled area.
draw::fill(fill_rect, col);
// Bar outline.
draw::rect(bar_rect, bar_color_border);
}
}
unit_drawer::unit_drawer(display & thedisp) :
disp(thedisp),
dc(disp.get_disp_context()),
map(dc.map()),
teams(dc.teams()),
halo_man(thedisp.get_halo_manager()),
viewing_team(disp.viewing_team()),
playing_team(disp.playing_team()),
viewing_team_ref(teams[viewing_team]),
playing_team_ref(teams[playing_team]),
is_blindfolded(disp.is_blindfolded()),
show_everything(disp.show_everything()),
sel_hex(disp.selected_hex()),
mouse_hex(disp.mouseover_hex()),
zoom_factor(disp.get_zoom_factor()),
hex_size(disp.hex_size()),
hex_size_by_2(disp.hex_size()/2)
unit_drawer::unit_drawer(display& thedisp)
: disp(thedisp)
, dc(disp.get_disp_context())
, map(dc.map())
, teams(dc.teams())
, halo_man(thedisp.get_halo_manager())
, viewing_team(disp.viewing_team())
, playing_team(disp.playing_team())
, viewing_team_ref(teams[viewing_team])
, playing_team_ref(teams[playing_team])
, is_blindfolded(disp.is_blindfolded())
, show_everything(disp.show_everything())
, sel_hex(disp.selected_hex())
, mouse_hex(disp.mouseover_hex())
, zoom_factor(disp.get_zoom_factor())
, hex_size(disp.hex_size())
, hex_size_by_2(disp.hex_size() / 2)
{
if(const game_display* game_display = dynamic_cast<class game_display*>(&disp)) {
units_that_can_reach_goal = game_display->units_that_can_reach_goal();
@ -91,17 +133,7 @@ unit_drawer::unit_drawer(display & thedisp) :
assert(disp.team_valid());
}
rect unit_drawer::scaled_to_zoom(const rect& r) const
{
return {r.x, r.y, int(r.w*zoom_factor), int(r.h*zoom_factor)};
}
point unit_drawer::scaled_to_zoom(const point& p) const
{
return {int(p.x*zoom_factor), int(p.y*zoom_factor)};
}
void unit_drawer::redraw_unit (const unit & u) const
void unit_drawer::redraw_unit(const unit& u) const
{
unit_animation_component & ac = u.anim_comp();
map_location loc = u.get_location();
@ -130,6 +162,10 @@ void unit_drawer::redraw_unit (const unit & u) const
const bool is_highlighted_enemy = units_that_can_reach_goal.count(loc) > 0;
const bool is_selected_hex = (loc == sel_hex || is_highlighted_enemy);
// Override the filled area's color's alpha.
hp_color.a = (loc == mouse_hex || is_selected_hex) ? 255u : float_to_color(0.8);
xp_color.a = hp_color.a;
if(hidden || is_blindfolded || !u.is_visible_to_team(viewing_team_ref, show_everything)) {
ac.clear_haloes();
if(ac.anim_) {
@ -215,6 +251,7 @@ void unit_drawer::redraw_unit (const unit & u) const
rect unit_rect {xsrc, ysrc +adjusted_params.y, hex_size, hex_size};
draw_bars = unit_rect.overlaps(disp.map_outside_area());
}
texture ellipse_front;
texture ellipse_back;
int ellipse_floating = 0;
@ -246,25 +283,20 @@ void unit_drawer::redraw_unit (const unit & u) const
ellipse_front = image::get_texture(image::locator(ellipse_bot));
}
}
if (ellipse_back != nullptr) {
const rect dest = scaled_to_zoom({
xsrc, ysrc + adjusted_params.y - ellipse_floating,
ellipse_back.w(), ellipse_back.h()
});
//disp.drawing_buffer_add(display::LAYER_UNIT_BG, loc,
disp.drawing_buffer_add(display::LAYER_UNIT_FIRST, loc,
dest, ellipse_back);
}
if (ellipse_front != nullptr) {
const rect dest = scaled_to_zoom({
xsrc, ysrc + adjusted_params.y - ellipse_floating,
ellipse_front.w(), ellipse_front.h()
});
//disp.drawing_buffer_add(display::LAYER_UNIT_FG, loc,
disp.drawing_buffer_add(display::LAYER_UNIT_FIRST, loc,
dest, ellipse_front);
}
disp.drawing_buffer_add(display::LAYER_UNIT_FIRST, loc, [=, adj_y = adjusted_params.y](const rect& d) {
// Both front and back have the same origin
const point origin { d.x, d.y + adj_y - ellipse_floating };
if(ellipse_back) {
draw::blit(ellipse_back, display::scaled_to_zoom({origin.x, origin.y, ellipse_back.w(), ellipse_back.h()}));
}
if(ellipse_front) {
draw::blit(ellipse_front, display::scaled_to_zoom({origin.x, origin.y, ellipse_front.w(), ellipse_front.h()}));
}
});
if(draw_bars) {
const auto& type_cfg = u.type().get_cfg();
const auto& cfg_offset_x = type_cfg["bar_offset_x"];
@ -272,7 +304,7 @@ void unit_drawer::redraw_unit (const unit & u) const
int xoff;
int yoff;
if(cfg_offset_x.empty() && cfg_offset_y.empty()) {
const point s = scaled_to_zoom(
const point s = display::scaled_to_zoom(
image::get_size(u.default_anim_image())
);
xoff = !s.x ? 0 : (hex_size - s.x)/2;
@ -285,6 +317,7 @@ void unit_drawer::redraw_unit (const unit & u) const
using namespace orb_status_helper;
std::unique_ptr<image::locator> orb_img = nullptr;
if(viewing_team_ref.is_enemy(side)) {
if(!u.incapacitated())
orb_img = get_orb_image(orb_status::enemy);
@ -303,58 +336,51 @@ void unit_drawer::redraw_unit (const unit & u) const
orb_img = get_orb_image(os);
}
// All the various overlay textures to draw with the HP/XP bars
std::vector<texture> textures;
if(orb_img != nullptr) {
const texture orb(image::get_texture(*orb_img));
const rect dest = scaled_to_zoom({
xsrc + xoff, ysrc + yoff + adjusted_params.y,
orb.w(), orb.h()
});
disp.drawing_buffer_add(display::LAYER_UNIT_BAR, loc, dest, orb);
textures.push_back(image::get_texture(*orb_img));
}
double unit_energy = 0.0;
if(max_hitpoints > 0) {
unit_energy = static_cast<double>(hitpoints)/static_cast<double>(max_hitpoints);
}
const int bar_shift = static_cast<int>(-5*zoom_factor);
const int hp_bar_height = static_cast<int>(max_hitpoints * u.hp_bar_scaling());
const uint8_t bar_alpha = (loc == mouse_hex || is_selected_hex) ? 255 : float_to_color(0.8);
draw_bar(xsrc + xoff + bar_shift, ysrc + yoff + adjusted_params.y,
loc, hp_bar_height, unit_energy,hp_color, bar_alpha);
if(experience > 0 && can_advance) {
const double filled = static_cast<double>(experience) / static_cast<double>(max_experience);
const int xp_bar_height = static_cast<int>(max_experience * u.xp_bar_scaling() / std::max<int>(u.level(),1));
draw_bar(xsrc + xoff, ysrc + yoff + adjusted_params.y,
loc, xp_bar_height, filled, xp_color, bar_alpha);
}
if (can_recruit) {
const texture crown(image::get_texture(u.leader_crown()));
if(crown) {
const rect dest = scaled_to_zoom({
xsrc + xoff, ysrc + yoff + adjusted_params.y,
crown.w(), crown.h()
});
disp.drawing_buffer_add(display::LAYER_UNIT_BAR,
loc, dest, crown);
if(can_recruit) {
if(texture tex = image::get_texture(u.leader_crown())) {
textures.push_back(std::move(tex));
}
}
for(const std::string& ov : u.overlays()) {
const texture ov_img(image::get_texture(ov));
if(ov_img) {
const rect dest = scaled_to_zoom({
xsrc + xoff, ysrc + yoff + adjusted_params.y,
ov_img.w(), ov_img.h()
});
disp.drawing_buffer_add(display::LAYER_UNIT_BAR,
loc, dest, ov_img);
if(texture tex = image::get_texture(ov)) {
textures.push_back(std::move(tex));
}
}
};
disp.drawing_buffer_add(display::LAYER_UNIT_BAR, loc, [=,
textures = std::move(textures),
adj_y = adjusted_params.y,
//origin = point{xsrc + xoff, ysrc + yoff + adjusted_params.y},
bar_hp_height = static_cast<int>(max_hitpoints * u.hp_bar_scaling()),
bar_xp_height = static_cast<int>(max_experience * u.xp_bar_scaling() / std::max<int>(u.level(), 1))
](const rect& d) {
const point origin { d.x + xoff, d.y + yoff + adj_y };
for(const texture& tex : textures) {
draw::blit(tex, display::scaled_to_zoom({origin.x, origin.y, tex.w(), tex.h()}));
}
if(max_hitpoints > 0) {
// Offset slightly to make room for the XP bar
const int hp_offset = static_cast<int>(-5 * display::get_zoom_factor());
double filled = static_cast<double>(hitpoints) / static_cast<double>(max_hitpoints);
draw_bar(origin.x + hp_offset, origin.y, bar_hp_height, filled, hp_color);
}
if(experience > 0 && can_advance) {
double filled = static_cast<double>(experience) / static_cast<double>(max_experience);
draw_bar(origin.x, origin.y, bar_xp_height, filled, xp_color);
}
});
}
// Smooth unit movements from terrain of different elevation.
@ -405,98 +431,3 @@ void unit_drawer::redraw_unit (const unit & u) const
ac.anim_->redraw(params, halo_man);
ac.refreshing_ = false;
}
void unit_drawer::draw_bar(int xpos, int ypos, const map_location& loc,
int height, double filled, const color_t& col, uint8_t alpha) const
{
const std::string& bar_image = game_config::images::energy;
rect bar_loc = calculate_energy_bar(bar_image);
texture tex = image::get_texture(bar_image, image::HEXED);
filled = std::clamp(filled, 0.0, 1.0);
height = std::clamp(height, 0, bar_loc.h);
rect top{0, 0, tex.w(), bar_loc.y};
rect bot(0, bar_loc.y + bar_loc.h - height, tex.w(), 0);
bot.h = tex.h() - bot.y;
rect dest = scaled_to_zoom({xpos, ypos, top.w, top.h});
tex.set_src(top);
disp.drawing_buffer_add(display::LAYER_UNIT_BAR, loc, dest, tex);
dest = scaled_to_zoom({xpos, ypos + int(top.h * zoom_factor), bot.w, bot.h});
tex.set_src(bot);
disp.drawing_buffer_add(display::LAYER_UNIT_BAR, loc, dest, tex);
tex.clear_src();
int unfilled = height * (1.0 - filled);
if(unfilled < height && alpha >= float_to_color(0.3)) {
// display::blit_helper only supports drawing textures,
// however we can draw a rectangle by blitting a single-pixel image.
texture white = image::get_texture("misc/single-pixel.png");
// This would be much cleaner if we weren't placing things scaled.
dest = {
xpos + int(bar_loc.x * zoom_factor),
ypos + int((bar_loc.y + unfilled) * zoom_factor),
int(bar_loc.w * zoom_factor),
int((height - unfilled) * zoom_factor)
};
disp.drawing_buffer_add(display::LAYER_UNIT_BAR, loc, dest, white)
.set_alpha(alpha)
.set_color(col);
}
}
struct is_energy_color {
bool operator()(uint32_t color) const { return (color&0xFF000000) > 0x10000000 &&
(color&0x00FF0000) < 0x00100000 &&
(color&0x0000FF00) < 0x00001000 &&
(color&0x000000FF) < 0x00000010; }
};
rect unit_drawer::calculate_energy_bar(const std::string& bar_image) const
{
// Cache the results here, per bar image
static std::map<std::string, rect> energy_bar_rects;
auto it = energy_bar_rects.find(bar_image);
if(it != energy_bar_rects.end()) {
return it->second;
}
// Cache miss, read the surface and figure out the bar location.
surface surf(image::get_surface(bar_image));
int first_row = -1, last_row = -1, first_col = -1, last_col = -1;
const_surface_lock image_lock(surf);
const uint32_t* const begin = image_lock.pixels();
for(int y = 0; y != surf->h; ++y) {
const uint32_t* const i1 = begin + surf->w*y;
const uint32_t* const i2 = i1 + surf->w;
const uint32_t* const itor = std::find_if(i1,i2,is_energy_color());
const int count = std::count_if(itor,i2,is_energy_color());
if(itor != i2) {
if(first_row == -1) {
first_row = y;
}
first_col = itor - i1;
last_col = first_col + count;
last_row = y;
}
}
const SDL_Rect res {
first_col
, first_row
, last_col-first_col
, last_row+1-first_row
};
energy_bar_rects.emplace(bar_image, res);
LOG_DP << "calculated energy bar location: " << res;
return res;
}

View file

@ -71,24 +71,4 @@ private:
public:
/** draw a unit. */
void redraw_unit(const unit & u) const;
private:
/** draw a health/xp bar of a unit */
void draw_bar(int xpos, int ypos, const map_location& loc,
int height, double filled, const color_t& col, uint8_t alpha) const;
/**
* Find where to draw the bar on an energy bar image.
*
* Results are cached so this can be called frequently.
*
* This looks for a coloured region with significant (>0x10) alpha
* and blackish colour (<0x10 in RGB channels).
*/
rect calculate_energy_bar(const std::string& bar_image) const;
/** Scale a rect to the current zoom level. */
rect scaled_to_zoom(const rect& r) const;
/** Scale a point to the current zoom level. */
point scaled_to_zoom(const point& p) const;
};

View file

@ -16,6 +16,7 @@
#include "units/frame.hpp"
#include "color.hpp"
#include "draw.hpp"
#include "game_display.hpp"
#include "log.hpp"
#include "sound.hpp"
@ -473,6 +474,98 @@ std::vector<std::string> frame_parsed_parameters::debug_strings() const
return v;
}
namespace
{
void render_unit_image(
int x,
int y,
const display::drawing_layer drawing_layer,
const map_location& loc,
const image::locator& i_locator,
bool hreverse,
bool greyscale,
uint8_t alpha,
double highlight,
color_t blendto,
double blend_ratio,
double submerge,
bool vreverse)
{
const point image_size = image::get_size(i_locator);
if(!image_size.x || !image_size.y) {
return;
}
display* disp = display::get_singleton();
rect dest = disp->scaled_to_zoom({x, y, image_size.x, image_size.y});
if(!dest.overlaps(disp->map_area())) {
return;
}
// For now, we add to the existing IPF modifications for the image.
std::string new_modifications;
if(greyscale) {
new_modifications += "~GS()";
}
display::add_submerge_ipf_mod(new_modifications, image_size.y, submerge);
texture tex;
if(!new_modifications.empty()) {
tex = image::get_texture({i_locator.get_filename(), i_locator.get_modifications() + new_modifications});
} else {
tex = image::get_texture(i_locator);
}
// Clamp blend ratio so nothing weird happens
blend_ratio = std::clamp(blend_ratio, 0.0, 1.0);
disp->drawing_buffer_add(drawing_layer, loc, [=](const rect&) mutable {
tex.set_alpha_mod(alpha);
draw::flipped(tex, dest, hreverse, vreverse);
if(uint8_t hl = float_to_color(highlight); hl > 0) {
tex.set_blend_mode(SDL_BLENDMODE_ADD);
tex.set_alpha_mod(hl);
draw::flipped(tex, dest, hreverse, vreverse);
}
tex.set_blend_mode(SDL_BLENDMODE_BLEND);
tex.set_alpha_mod(SDL_ALPHA_OPAQUE);
});
// SDL hax to apply an active washout tint at the correct ratio
if(blend_ratio > 0.0) {
// Get a pure-white version of the texture
const image::locator whiteout_locator(
i_locator.get_filename(),
i_locator.get_modifications()
+ new_modifications
+ "~CHAN(255, 255, 255, alpha)"
);
disp->drawing_buffer_add(drawing_layer, loc, [=, tex = image::get_texture(whiteout_locator)](const rect&) mutable {
tex.set_alpha_mod(alpha * blend_ratio);
tex.set_color_mod(blendto);
draw::flipped(tex, dest, hreverse, vreverse);
if(uint8_t hl = float_to_color(highlight); hl > 0) {
tex.set_blend_mode(SDL_BLENDMODE_ADD);
tex.set_alpha_mod(hl);
draw::flipped(tex, dest, hreverse, vreverse);
}
tex.set_color_mod(255, 255, 255);
tex.set_blend_mode(SDL_BLENDMODE_BLEND);
tex.set_alpha_mod(SDL_ALPHA_OPAQUE);
});
}
}
} // namespace
void unit_frame::redraw(const int frame_time, bool on_start_time, bool in_scope_of_frame,
const map_location& src, const map_location& dst,
halo::handle& halo_id, halo::manager& halo_man,
@ -564,11 +657,21 @@ void unit_frame::redraw(const int frame_time, bool on_start_time, bool in_scope_
alpha = float_to_color(current_data.highlight_ratio);
}
display::get_singleton()->render_image(my_x, my_y,
static_cast<display::drawing_layer>(display::LAYER_UNIT_FIRST + current_data.drawing_layer),
src, image_loc, facing_west, false, alpha, brighten,
current_data.blend_with ? *current_data.blend_with : color_t(),
current_data.blend_ratio, current_data.submerge, !facing_north);
if(alpha != 0) {
render_unit_image(my_x, my_y,
static_cast<display::drawing_layer>(display::LAYER_UNIT_FIRST + current_data.drawing_layer),
src,
image_loc,
facing_west,
false,
alpha,
brighten,
current_data.blend_with ? *current_data.blend_with : color_t(),
current_data.blend_ratio,
current_data.submerge,
!facing_north
);
}
}
halo_id.reset();

View file

@ -24,6 +24,7 @@
#include "arrow.hpp"
#include "config.hpp"
#include "draw.hpp"
#include "fake_unit_ptr.hpp"
#include "game_board.hpp"
#include "play_controller.hpp"
@ -206,26 +207,20 @@ void attack::draw_hex(const map_location& hex)
if (hex == get_dest_hex()) //add symbol to attacker hex
{
auto disp = display::get_singleton();
int x = disp->get_location_x(get_dest_hex());
int y = disp->get_location_y(get_dest_hex());
const texture t = image::get_texture(
"whiteboard/attack-indicator-src-" + direction_text + ".png",
image::HEXED);
const SDL_Rect dest = disp->scaled_to_zoom({x, y, t.w(), t.h()});
disp->drawing_buffer_add(layer, get_dest_hex(), dest, t);
disp->drawing_buffer_add(layer, get_dest_hex(),
[=, tex = image::get_texture("whiteboard/attack-indicator-src-" + direction_text + ".png", image::HEXED)](const rect& d) {
draw::blit(tex, disp->scaled_to_zoom({d.x, d.y, tex.w(), tex.h()}));
});
}
else if (hex == target_hex_) //add symbol to defender hex
{
auto disp = display::get_singleton();
int x = disp->get_location_x(target_hex_);
int y = disp->get_location_y(target_hex_);
const texture t = image::get_texture(
"whiteboard/attack-indicator-dst-" + direction_text + ".png",
image::HEXED);
const SDL_Rect dest = disp->scaled_to_zoom({x, y, t.w(), t.h()});
disp->drawing_buffer_add(layer, target_hex_, dest, t);
disp->drawing_buffer_add(layer, target_hex_,
[=, tex = image::get_texture("whiteboard/attack-indicator-dst-" + direction_text + ".png", image::HEXED)](const rect& d) {
draw::blit(tex, disp->scaled_to_zoom({d.x, d.y, tex.w(), tex.h()}));
});
}
}

View file

@ -27,6 +27,7 @@
#include "arrow.hpp"
#include "config.hpp"
#include "display.hpp"
#include "draw.hpp"
#include "game_end_exceptions.hpp"
#include "mouse_events.hpp"
#include "play_controller.hpp"
@ -144,12 +145,11 @@ void suppose_dead::draw_hex(const map_location& hex)
const display::drawing_layer layer = display::LAYER_ARROWS;
auto disp = display::get_singleton();
int x = disp->get_location_x(loc_);
int y = disp->get_location_y(loc_);
const texture& tex = image::get_texture(
"whiteboard/suppose_dead.png", image::HEXED);
const SDL_Rect dest = disp->scaled_to_zoom({x, y, tex.w(), tex.h()});
disp->drawing_buffer_add(layer, loc_, dest, tex);
disp->drawing_buffer_add(
layer, loc_, [=, tex = image::get_texture("whiteboard/suppose_dead.png", image::HEXED)](const rect& d) {
draw::blit(tex, disp->scaled_to_zoom({d.x, d.y, tex.w(), tex.h()}));
});
}
void suppose_dead::redraw()