Display: use point math for viewport position

This commit is contained in:
Charles Dang 2024-10-22 01:30:28 -04:00
parent d338c90381
commit 5ab73a51fd
7 changed files with 98 additions and 127 deletions

View file

@ -391,13 +391,12 @@ bool controller_base::handle_scroll(int mousex, int mousey, int mouse_flags)
dx += scroll_carry_x_;
dy += scroll_carry_y_;
}
int dx_int = int(dx);
int dy_int = int(dy);
scroll_carry_x_ = dx - double(dx_int);
scroll_carry_y_ = dy - double(dy_int);
point dist{int(dx), int(dy)};
scroll_carry_x_ = dx - double(dist.x);
scroll_carry_y_ = dy - double(dist.y);
// Scroll the display
get_display().scroll(dx_int, dy_int);
get_display().scroll(dist);
// Even if the integer parts are both zero, we are still scrolling.
// The subpixel amounts will add up.

View file

@ -157,8 +157,7 @@ display::display(const display_context* dc,
, exclusive_unit_draw_requests_()
, viewing_team_index_(0)
, dont_show_all_(false)
, xpos_(0)
, ypos_(0)
, viewport_origin_(0, 0)
, view_locked_(false)
, theme_(theme::get_theme_config(theme_id.empty() ? prefs::get().theme() : theme_id), video::game_canvas())
, zoom_index_(0)
@ -549,7 +548,7 @@ map_location display::hex_clicked_on(int xclick, int yclick) const
xclick -= r.x;
yclick -= r.y;
return pixel_position_to_hex(xpos_ + xclick, ypos_ + yclick);
return pixel_position_to_hex(viewport_origin_.x + xclick, viewport_origin_.y + yclick);
}
// This function uses the rect of map_area as reference
@ -627,7 +626,7 @@ display::rect_of_hexes::iterator display::rect_of_hexes::end() const
return iterator(map_location(right+1, top[(right+1) & 1]), *this);
}
const display::rect_of_hexes display::hexes_under_rect(const SDL_Rect& r) const
const display::rect_of_hexes display::hexes_under_rect(const rect& r) const
{
rect_of_hexes res;
@ -642,10 +641,8 @@ const display::rect_of_hexes display::hexes_under_rect(const SDL_Rect& r) const
return res;
}
rect map_rect = map_area();
// translate rect coordinates from screen-based to map_area-based
int x = xpos_ - map_rect.x + r.x;
int y = ypos_ - map_rect.y + r.y;
auto [x, y] = viewport_origin_ - map_area().pos() + r.pos();
// we use the "double" type to avoid important rounding error (size of an hex!)
// we will also need to use std::floor to avoid bad rounding at border (negative values)
double tile_width = hex_width();
@ -686,12 +683,12 @@ bool display::fogged(const map_location& loc) const
int display::get_location_x(const map_location& loc) const
{
return static_cast<int>(map_area().x + (loc.x + theme_.border().size) * hex_width() - xpos_);
return static_cast<int>(map_area().x + (loc.x + theme_.border().size) * hex_width() - viewport_origin_.x);
}
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));
return static_cast<int>(map_area().y + (loc.y + theme_.border().size) * zoom_ - viewport_origin_.y + (is_odd(loc.x) ? zoom_/2 : 0));
}
point display::get_location(const map_location& loc) const
@ -753,10 +750,8 @@ surface display::screenshot(bool map_screenshot)
}
// back up the current map view position and move to top-left
int old_xpos = xpos_;
int old_ypos = ypos_;
xpos_ = 0;
ypos_ = 0;
point old_pos = viewport_origin_;
viewport_origin_ = {0, 0};
// Reroute render output to a separate texture until the end of scope.
SDL_Rect area = max_map_area();
@ -780,8 +775,7 @@ surface display::screenshot(bool map_screenshot)
map_screenshot_ = false;
// Restore map viewport position
xpos_ = old_xpos;
ypos_ = old_ypos;
viewport_origin_ = old_pos;
// Read rendered pixels back as an SDL surface.
LOG_DP << "reading pixels for map screenshot";
@ -1663,8 +1657,8 @@ void display::draw_minimap()
double shift_x = -border * hex_width() - (map_out_rect.w - map_rect.w) / 2;
double shift_y = -(border + 0.25) * hex_size() - (map_out_rect.h - map_rect.h) / 2;
int view_x = static_cast<int>((xpos_ + shift_x) * xscaling);
int view_y = static_cast<int>((ypos_ + shift_y) * yscaling);
int view_x = static_cast<int>((viewport_origin_.x + shift_x) * xscaling);
int view_y = static_cast<int>((viewport_origin_.y + shift_y) * yscaling);
int view_w = static_cast<int>(map_out_rect.w * xscaling);
int view_h = static_cast<int>(map_out_rect.h * yscaling);
@ -1725,32 +1719,27 @@ void display::draw_minimap_units()
}
}
bool display::scroll(int xmove, int ymove, bool force)
bool display::scroll(const point& amount, bool force)
{
if(view_locked_ && !force) {
return false;
}
// No move offset, do nothing.
if(xmove == 0 && ymove == 0) {
if(amount == point{}) {
return false;
}
int new_x = xpos_ + xmove;
int new_y = ypos_ + ymove;
bounds_check_position(new_x, new_y);
point new_pos = viewport_origin_ + amount;
bounds_check_position(new_pos.x, new_pos.y);
// Camera position doesn't change, exit.
if(xpos_ == new_x && ypos_ == new_y) {
if(viewport_origin_ == new_pos) {
return false;
}
const int diff_x = xpos_ - new_x;
const int diff_y = ypos_ - new_y;
xpos_ = new_x;
ypos_ = new_y;
point diff = viewport_origin_ - new_pos;
viewport_origin_ = new_pos;
/* Adjust floating label positions. This only affects labels whose position is anchored
* to the map instead of the screen. In order to do that, we want to adjust their drawing
@ -1762,7 +1751,7 @@ bool display::scroll(int xmove, int ymove, bool force)
*
* const int label_[x,y]_adjust = [x,y]pos_ - new_[x,y];
*/
font::scroll_floating_labels(diff_x, diff_y);
font::scroll_floating_labels(diff.x, diff.y);
labels().recalculate_shroud();
@ -1772,13 +1761,11 @@ bool display::scroll(int xmove, int ymove, bool force)
if(!video::headless()) {
rect dst = map_area();
dst.x += diff_x;
dst.y += diff_y;
dst.shift(diff);
dst.clip(map_area());
rect src = dst;
src.x -= diff_x;
src.y -= diff_y;
src.shift(-diff);
// swap buffers
std::swap(front_, back_);
@ -1796,25 +1783,25 @@ bool display::scroll(int xmove, int ymove, bool force)
draw_manager::invalidate_region(map_area());
}
if(diff_y != 0) {
if(diff.y != 0) {
rect r = map_area();
if(diff_y < 0) {
r.y = r.y + r.h + diff_y;
if(diff.y < 0) {
r.y = r.y + r.h + diff.y;
}
r.h = std::abs(diff_y);
r.h = std::abs(diff.y);
invalidate_locations_in_rect(r);
}
if(diff_x != 0) {
if(diff.x != 0) {
rect r = map_area();
if(diff_x < 0) {
r.x = r.x + r.w + diff_x;
if(diff.x < 0) {
r.x = r.x + r.w + diff.x;
}
r.w = std::abs(diff_x);
r.w = std::abs(diff.x);
invalidate_locations_in_rect(r);
}
@ -1884,13 +1871,12 @@ bool display::set_zoom(unsigned int amount, const bool validate_value_and_set_in
//
// (xpos_ + area.w/2) * new_zoom/zoom_ - area.w/2: Position of the
// leftmost visible map pixel, as it would be under new_zoom.
xpos_ = std::round(((xpos_ + area.w / 2) * zoom_factor) - (area.w / 2));
ypos_ = std::round(((ypos_ + area.h / 2) * zoom_factor) - (area.h / 2));
xpos_ -= (outside_area.w - area.w) / 2;
ypos_ -= (outside_area.h - area.h) / 2;
viewport_origin_.x = std::round(((viewport_origin_.x + area.w / 2) * zoom_factor) - (area.w / 2));
viewport_origin_.y = std::round(((viewport_origin_.y + area.h / 2) * zoom_factor) - (area.h / 2));
viewport_origin_ -= (outside_area.size() - area.size()) / 2;
zoom_ = new_zoom;
bounds_check_position(xpos_, ypos_);
bounds_check_position(viewport_origin_.x, viewport_origin_.y);
if(zoom_ != DefaultZoom) {
last_zoom_ = zoom_;
}
@ -1930,24 +1916,22 @@ bool display::tile_nearly_on_screen(const map_location& loc) const
y + hs >= area.y - hs && y < area.y + area.h + hs;
}
void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_type, bool force)
void display::scroll_to_xy(const point& screen_coordinates, SCROLL_TYPE scroll_type, bool force)
{
if(!force && (view_locked_ || !prefs::get().scroll_to_action())) return;
if(video::headless()) {
return;
}
const rect area = map_area();
const int xmove_expected = screenxpos - (area.x + area.w/2);
const int ymove_expected = screenypos - (area.y + area.h/2);
int xpos = xpos_ + xmove_expected;
int ypos = ypos_ + ymove_expected;
bounds_check_position(xpos, ypos);
int xmove = xpos - xpos_;
int ymove = ypos - ypos_;
point expected_move = screen_coordinates - map_area().center();
point new_pos = viewport_origin_ + expected_move;
bounds_check_position(new_pos.x, new_pos.y);
point move = new_pos - viewport_origin_;
if(scroll_type == WARP || scroll_type == ONSCREEN_WARP || turbo_speed() > 2.0 || prefs::get().scroll_speed() > 99) {
scroll(xmove,ymove,true);
scroll(move, true);
redraw_minimap();
events::draw();
return;
@ -1955,10 +1939,8 @@ void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_ty
// Doing an animated scroll, with acceleration etc.
int x_old = 0;
int y_old = 0;
const double dist_total = std::hypot(xmove, ymove);
point prev_pos;
const double dist_total = std::hypot(move.x, move.y);
double dist_moved = 0.0;
int t_prev = SDL_GetTicks();
@ -1997,15 +1979,14 @@ void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_ty
dist_moved += velocity * dt;
if (dist_moved > dist_total) dist_moved = dist_total;
int x_new = std::round(xmove * dist_moved / dist_total);
int y_new = std::round(ymove * dist_moved / dist_total);
point next_pos(
std::round(move.x * dist_moved / dist_total),
std::round(move.y * dist_moved / dist_total)
);
int dx = x_new - x_old;
int dy = y_new - y_old;
scroll(dx,dy,true);
x_old += dx;
y_old += dy;
point diff = next_pos - prev_pos;
scroll(diff, true);
prev_pos += diff;
redraw_minimap();
events::draw();
@ -2090,7 +2071,7 @@ void display::scroll_to_tiles(const std::vector<map_location>& locs,
locs_bbox.h = maxy - miny + hex_size();
// target the center
auto [target_x, target_y] = locs_bbox.center();
point target = locs_bbox.center();
if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
// when doing an ONSCREEN scroll we do not center the target unless needed
@ -2113,8 +2094,8 @@ void display::scroll_to_tiles(const std::vector<map_location>& locs,
if (w < 1) w = 1;
if (h < 1) h = 1;
r.x = target_x - w/2;
r.y = target_y - h/2;
r.x = target.x - w/2;
r.y = target.y - h/2;
r.w = w;
r.h = h;
@ -2123,31 +2104,31 @@ void display::scroll_to_tiles(const std::vector<map_location>& locs,
// which will always be at the border of r
if (map_center_x < r.x) {
target_x = r.x;
target_y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
target.x = r.x;
target.y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
} else if (map_center_x > r.x+r.w-1) {
target_x = r.x + r.w - 1;
target_y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
target.x = r.x + r.w - 1;
target.y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
} else if (map_center_y < r.y) {
target_y = r.y;
target_x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
target.y = r.y;
target.x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
} else if (map_center_y > r.y+r.h-1) {
target_y = r.y + r.h - 1;
target_x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
target.y = r.y + r.h - 1;
target.x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
} else {
ERR_DP << "Bug in the scrolling code? Looks like we would not need to scroll after all...";
// keep the target at the center
}
}
scroll_to_xy(target_x, target_y,scroll_type,force);
scroll_to_xy(target, scroll_type, force);
}
void display::bounds_check_position()
{
zoom_ = std::clamp(zoom_, MinZoom, MaxZoom);
bounds_check_position(xpos_, ypos_);
bounds_check_position(viewport_origin_.x, viewport_origin_.y);
}
void display::bounds_check_position(int& xpos, int& ypos) const
@ -3275,8 +3256,8 @@ void display::update_arrow(arrow & arrow)
map_location display::get_middle_location() const
{
auto [center_x, center_y] = map_area().center();
return pixel_position_to_hex(xpos_ + center_x , ypos_ + center_y);
auto [center_x, center_y] = viewport_origin_ + map_area().center();
return pixel_position_to_hex(center_x, center_y);
}
void display::write(config& cfg) const

View file

@ -361,7 +361,7 @@ public:
};
/** Return the rectangular area of hexes overlapped by r (r is in screen coordinates) */
const rect_of_hexes hexes_under_rect(const SDL_Rect& r) const;
const rect_of_hexes hexes_under_rect(const rect& r) const;
/** Returns the rectangular area of visible hexes */
const rect_of_hexes get_visible_hexes() const {return hexes_under_rect(map_area());}
@ -485,11 +485,11 @@ public:
void bounds_check_position(int& xpos, int& ypos) const;
/**
* Scrolls the display by xmov,ymov pixels.
* Scrolls the display by @a amount pixels.
* Invalidation and redrawing will be scheduled.
* @return true if the map actually moved.
*/
bool scroll(int xmov, int ymov, bool force = false);
bool scroll(const point& amount, bool force = false);
/** Zooms the display in (true) or out (false). */
bool set_zoom(bool increase);
@ -722,7 +722,7 @@ protected:
std::vector<texture> get_fog_shroud_images(const map_location& loc, image::TYPE image_type);
void scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_type,bool force = true);
void scroll_to_xy(const point& screen_coordinates, SCROLL_TYPE scroll_type, bool force = true);
static void fill_images_list(const std::string& prefix, std::vector<std::string>& images);
@ -734,7 +734,7 @@ protected:
* Dependent on zoom_.. For example, ypos_==72 only means we're one
* hex below the top of the map when zoom_ == 72 (the default value).
*/
int xpos_, ypos_;
point viewport_origin_;
bool view_locked_;
theme theme_;
/**

View file

@ -128,13 +128,13 @@ void mouse_handler::touch_motion(int x, int y, const bool browse, bool update, m
// Fire the drag & drop only after minimal drag distance
// While we check the mouse buttons state, we also grab fresh position data.
int mx = drag_from_x_; // some default value to prevent unlikely SDL bug
int my = drag_from_y_;
point pos = drag_from_; // some default value to prevent unlikely SDL bug
if(is_dragging() && !dragging_started_) {
if(dragging_touch_) {
sdl::get_mouse_state(&mx, &my);
const double drag_distance = std::pow(static_cast<double>(drag_from_x_- mx), 2)
+ std::pow(static_cast<double>(drag_from_y_- my), 2);
sdl::get_mouse_state(&pos.x, &pos.y);
const double drag_distance = std::pow(static_cast<double>(drag_from_.x - pos.x), 2)
+ std::pow(static_cast<double>(drag_from_.y- pos.y), 2);
if(drag_distance > drag_threshold()*drag_threshold()) {
dragging_started_ = true;
}
@ -145,15 +145,12 @@ void mouse_handler::touch_motion(int x, int y, const bool browse, bool update, m
const auto found_unit = find_unit(selected_hex_);
bool selected_hex_has_my_unit = found_unit.valid() && found_unit.get_shared_ptr()->side() == side_num_;
if((browse || !found_unit.valid()) && is_dragging() && dragging_started_) {
sdl::get_mouse_state(&mx, &my);
sdl::get_mouse_state(&pos.x, &pos.y);
if(gui().map_area().contains(x, y)) {
int dx = drag_from_x_ - mx;
int dy = drag_from_y_ - my;
gui().scroll(dx, dy);
drag_from_x_ = mx;
drag_from_y_ = my;
point dist = drag_from_ - pos;
gui().scroll(dist);
drag_from_ = pos;
}
return;
}
@ -866,7 +863,7 @@ void mouse_handler::teleport_action()
void mouse_handler::select_or_action(bool browse)
{
if(!pc_.get_map().on_board(last_hex_)) {
tooltips::click(drag_from_x_, drag_from_y_);
tooltips::click(drag_from_.x, drag_from_.y);
return;
}

View file

@ -57,8 +57,7 @@ mouse_handler_base::mouse_handler_base()
, dragging_touch_(false)
, dragging_started_(false)
, dragging_right_(false)
, drag_from_x_(0)
, drag_from_y_(0)
, drag_from_(0, 0)
, drag_from_hex_()
, last_hex_()
, show_menu_(false)
@ -130,11 +129,10 @@ bool mouse_handler_base::mouse_motion_default(int x, int y, bool /*update*/)
// Fire the drag & drop only after minimal drag distance
// While we check the mouse buttons state, we also grab fresh position data.
int mx = drag_from_x_; // some default value to prevent unlikely SDL bug
int my = drag_from_y_;
point pos = drag_from_; // some default value to prevent unlikely SDL bug
if(is_dragging() && !dragging_started_) {
Uint32 mouse_state = dragging_left_ || dragging_right_ ? sdl::get_mouse_state(&mx, &my) : 0;
Uint32 mouse_state = dragging_left_ || dragging_right_ ? sdl::get_mouse_state(&pos.x, &pos.y) : 0;
#ifdef MOUSE_TOUCH_EMULATION
if(dragging_left_ && (mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT))) {
// Monkey-patch touch controls again to make them look like left button.
@ -145,8 +143,8 @@ bool mouse_handler_base::mouse_motion_default(int x, int y, bool /*update*/)
(dragging_right_ && (mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0))
{
const double drag_distance =
std::pow(static_cast<double>(drag_from_x_- mx), 2) +
std::pow(static_cast<double>(drag_from_y_- my), 2);
std::pow(static_cast<double>(drag_from_.x - pos.x), 2) +
std::pow(static_cast<double>(drag_from_.y - pos.y), 2);
if(drag_distance > drag_threshold() * drag_threshold()) {
dragging_started_ = true;
@ -371,9 +369,9 @@ void mouse_handler_base::mouse_wheel(int scrollx, int scrolly, bool browse)
CKey pressed;
// Alt + mousewheel do an 90° rotation on the scroll direction
if(pressed[SDLK_LALT] || pressed[SDLK_RALT]) {
gui().scroll(movey, movex);
gui().scroll(point{movey, movex});
} else {
gui().scroll(movex, movey);
gui().scroll(point{movex, movey});
}
}
@ -407,8 +405,8 @@ void mouse_handler_base::right_mouse_up(int x, int y, const bool browse)
void mouse_handler_base::init_dragging(bool& dragging_flag)
{
dragging_flag = true;
sdl::get_mouse_state(&drag_from_x_, &drag_from_y_);
drag_from_hex_ = gui().hex_clicked_on(drag_from_x_, drag_from_y_);
sdl::get_mouse_state(&drag_from_.x, &drag_from_.y);
drag_from_hex_ = gui().hex_clicked_on(drag_from_.x, drag_from_.y);
}
void mouse_handler_base::cancel_dragging()

View file

@ -17,6 +17,7 @@
#pragma once
#include "map/location.hpp"
#include "sdl/point.hpp"
#include <SDL2/SDL_events.h>
@ -238,11 +239,8 @@ protected:
/** RMB drag init flag */
bool dragging_right_;
/** Drag start position x */
int drag_from_x_;
/** Drag start position y */
int drag_from_y_;
/** Drag start position */
point drag_from_;
/** Drag start or mouse-down map location */
map_location drag_from_hex_;

View file

@ -4858,11 +4858,9 @@ int game_lua_kernel::intf_replace_schedule(lua_State * L)
int game_lua_kernel::intf_scroll(lua_State * L)
{
int x = luaL_checkinteger(L, 1);
int y = luaL_checkinteger(L, 2);
if (game_display_) {
game_display_->scroll(x, y, true);
point scroll_to(luaL_checkinteger(L, 1), luaL_checkinteger(L, 2));
game_display_->scroll(scroll_to, true);
lua_remove(L, 1);
lua_remove(L, 1);