git repos / blockattack-game

blame: source/code/sago/SagoLogicalResize.hpp

normal view · raw

62b87fe3 Poul Sander 2025-03-04 20:16 1
/*
62b87fe3 Poul Sander 2025-03-04 20:16 2
Copyright (c) 2025 Poul Sander
62b87fe3 Poul Sander 2025-03-04 20:16 3
62b87fe3 Poul Sander 2025-03-04 20:16 4
Permission is hereby granted, free of charge, to any person
62b87fe3 Poul Sander 2025-03-04 20:16 5
obtaining a copy of this software and associated documentation files
62b87fe3 Poul Sander 2025-03-04 20:16 6
(the "Software"), to deal in the Software without restriction,
62b87fe3 Poul Sander 2025-03-04 20:16 7
including without limitation the rights to use, copy, modify, merge,
62b87fe3 Poul Sander 2025-03-04 20:16 8
publish, distribute, sublicense, and/or sell copies of the Software,
62b87fe3 Poul Sander 2025-03-04 20:16 9
and to permit persons to whom the Software is furnished to do so,
62b87fe3 Poul Sander 2025-03-04 20:16 10
subject to the following conditions:
62b87fe3 Poul Sander 2025-03-04 20:16 11
62b87fe3 Poul Sander 2025-03-04 20:16 12
The above copyright notice and this permission notice shall be
62b87fe3 Poul Sander 2025-03-04 20:16 13
included in all copies or substantial portions of the Software.
62b87fe3 Poul Sander 2025-03-04 20:16 14
62b87fe3 Poul Sander 2025-03-04 20:16 15
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
62b87fe3 Poul Sander 2025-03-04 20:16 16
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
62b87fe3 Poul Sander 2025-03-04 20:16 17
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
62b87fe3 Poul Sander 2025-03-04 20:16 18
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
62b87fe3 Poul Sander 2025-03-04 20:16 19
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
62b87fe3 Poul Sander 2025-03-04 20:16 20
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
62b87fe3 Poul Sander 2025-03-04 20:16 21
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
62b87fe3 Poul Sander 2025-03-04 20:16 22
SOFTWARE.
62b87fe3 Poul Sander 2025-03-04 20:16 23
*/
62b87fe3 Poul Sander 2025-03-04 20:16 24
62b87fe3 Poul Sander 2025-03-04 20:16 25
#pragma once
cb02a4a0 Poul Sander 2025-03-03 21:25 26
#include <algorithm>
cb02a4a0 Poul Sander 2025-03-03 21:25 27
#include "SDL.h"
cb02a4a0 Poul Sander 2025-03-03 21:25 28
cb02a4a0 Poul Sander 2025-03-03 21:25 29
cb02a4a0 Poul Sander 2025-03-03 21:25 30
namespace sago {
cb02a4a0 Poul Sander 2025-03-03 21:25 31
1de972a7 Poul Sander 2025-11-11 20:23 32
/**
1de972a7 Poul Sander 2025-11-11 20:23 33
 * @brief Provides coordinate transformation between logical and physical screen coordinates.
1de972a7 Poul Sander 2025-11-11 20:23 34
 *
1de972a7 Poul Sander 2025-11-11 20:23 35
 * This class helps create resolution-independent layouts by defining coordinates in a fixed
1de972a7 Poul Sander 2025-11-11 20:23 36
 * "logical" resolution and automatically converting them to the actual "physical" screen size.
1de972a7 Poul Sander 2025-11-11 20:23 37
 * It maintains aspect ratio by adding letterboxing/pillarboxing margins when needed.
1de972a7 Poul Sander 2025-11-11 20:23 38
 *
1de972a7 Poul Sander 2025-11-11 20:23 39
 * Key concepts:
1de972a7 Poul Sander 2025-11-11 20:23 40
 * - Logical coordinates: Fixed coordinate system you design for (e.g., 1920x1080)
1de972a7 Poul Sander 2025-11-11 20:23 41
 * - Physical coordinates: Actual screen/window size (e.g., 1280x720, 3840x2160, etc.)
1de972a7 Poul Sander 2025-11-11 20:23 42
 * - Scale factor: Calculated to fit logical size into physical size while preserving aspect ratio
1de972a7 Poul Sander 2025-11-11 20:23 43
 * - Margins: Black bars added to maintain aspect ratio (letterboxing/pillarboxing)
1de972a7 Poul Sander 2025-11-11 20:23 44
 * - Tile alignment: As long as tiles are resized with LogicalToPhysical, they will align correctly. No gaps or overlaps.
1de972a7 Poul Sander 2025-11-11 20:23 45
 *
1de972a7 Poul Sander 2025-11-11 20:23 46
 * Example usage:
1de972a7 Poul Sander 2025-11-11 20:23 47
 * @code
1de972a7 Poul Sander 2025-11-11 20:23 48
 * // Design your UI for 1920x1080
1de972a7 Poul Sander 2025-11-11 20:23 49
 * SagoLogicalResize resize(1920, 1080);
1de972a7 Poul Sander 2025-11-11 20:23 50
 * resize.SetPhysicalSize(window_width, window_height);
1de972a7 Poul Sander 2025-11-11 20:23 51
 *
1de972a7 Poul Sander 2025-11-11 20:23 52
 * // Draw at logical position (100, 100)
1de972a7 Poul Sander 2025-11-11 20:23 53
 * int phys_x, phys_y;
1de972a7 Poul Sander 2025-11-11 20:23 54
 * resize.LogicalToPhysical(100, 100, phys_x, phys_y);
1de972a7 Poul Sander 2025-11-11 20:23 55
 * DrawSprite(renderer, phys_x, phys_y);
1de972a7 Poul Sander 2025-11-11 20:23 56
 *
1de972a7 Poul Sander 2025-11-11 20:23 57
 * // Handle mouse input
1de972a7 Poul Sander 2025-11-11 20:23 58
 * int log_x, log_y;
1de972a7 Poul Sander 2025-11-11 20:23 59
 * resize.PhysicalToLogical(mouse_x, mouse_y, log_x, log_y);
1de972a7 Poul Sander 2025-11-11 20:23 60
 * if (log_x > 100 && log_x < 200) { ... }
1de972a7 Poul Sander 2025-11-11 20:23 61
 * @endcode
1de972a7 Poul Sander 2025-11-11 20:23 62
 */
1de972a7 Poul Sander 2025-11-11 20:23 63
class SagoLogicalResize {
cb02a4a0 Poul Sander 2025-03-03 21:25 64
public:
1de972a7 Poul Sander 2025-11-11 20:23 65
	/**
1de972a7 Poul Sander 2025-11-11 20:23 66
	 * @brief Default constructor with minimal 1x1 logical size.
1de972a7 Poul Sander 2025-11-11 20:23 67
	 */
1de972a7 Poul Sander 2025-11-11 20:23 68
	SagoLogicalResize() : logical_width_(1), logical_height_(1), physical_width_(1), physical_height_(1) {
1de972a7 Poul Sander 2025-11-11 20:23 69
		SetScaleFactor();
1de972a7 Poul Sander 2025-11-11 20:23 70
	}
1de972a7 Poul Sander 2025-11-11 20:23 71
1de972a7 Poul Sander 2025-11-11 20:23 72
	/**
1de972a7 Poul Sander 2025-11-11 20:23 73
	 * @brief Constructor with specified logical resolution.
1de972a7 Poul Sander 2025-11-11 20:23 74
	 * @param logical_width The width of the logical coordinate system (minimum 1)
1de972a7 Poul Sander 2025-11-11 20:23 75
	 * @param logical_height The height of the logical coordinate system (minimum 1)
1de972a7 Poul Sander 2025-11-11 20:23 76
	 */
cb02a4a0 Poul Sander 2025-03-03 21:25 77
	SagoLogicalResize(int logical_width, int logical_height)
1de972a7 Poul Sander 2025-11-11 20:23 78
		: logical_width_(std::max(1, logical_width)), logical_height_(std::max(1, logical_height)), physical_width_(1), physical_height_(1) {
1de972a7 Poul Sander 2025-11-11 20:23 79
		SetScaleFactor();
1de972a7 Poul Sander 2025-11-11 20:23 80
	}
cb02a4a0 Poul Sander 2025-03-03 21:25 81
1de972a7 Poul Sander 2025-11-11 20:23 82
	/**
1de972a7 Poul Sander 2025-11-11 20:23 83
	 * @brief Updates the physical (actual) screen size and recalculates scaling.
1de972a7 Poul Sander 2025-11-11 20:23 84
	 *
1de972a7 Poul Sander 2025-11-11 20:23 85
	 * Call this when the window is resized or when initializing the physical display size.
1de972a7 Poul Sander 2025-11-11 20:23 86
	 * This recalculates the scale factor and margins to fit the logical size into the physical size.
1de972a7 Poul Sander 2025-11-11 20:23 87
	 *
1de972a7 Poul Sander 2025-11-11 20:23 88
	 * @param physical_width The actual width of the screen/window (minimum 1)
1de972a7 Poul Sander 2025-11-11 20:23 89
	 * @param physical_height The actual height of the screen/window (minimum 1)
1de972a7 Poul Sander 2025-11-11 20:23 90
	 */
1de972a7 Poul Sander 2025-11-11 20:23 91
	void SetPhysicalSize(int physical_width, int physical_height) {
62b87fe3 Poul Sander 2025-03-04 20:16 92
		//Physical size must be at least 1. Less than 1 is not drawn anyway and it prevents division by zero.
cb02a4a0 Poul Sander 2025-03-03 21:25 93
		physical_width_ = std::max(1, physical_width);
cb02a4a0 Poul Sander 2025-03-03 21:25 94
		physical_height_ = std::max(1, physical_height);
cb02a4a0 Poul Sander 2025-03-03 21:25 95
		SetScaleFactor();
cb02a4a0 Poul Sander 2025-03-03 21:25 96
	}
cb02a4a0 Poul Sander 2025-03-03 21:25 97
1de972a7 Poul Sander 2025-11-11 20:23 98
	/**
1de972a7 Poul Sander 2025-11-11 20:23 99
	 * @brief Converts logical coordinates to physical screen coordinates.
1de972a7 Poul Sander 2025-11-11 20:23 100
	 *
1de972a7 Poul Sander 2025-11-11 20:23 101
	 * This applies the scale factor and adds the appropriate margins.
1de972a7 Poul Sander 2025-11-11 20:23 102
	 * Use this to convert positions where you want to draw elements on screen.
1de972a7 Poul Sander 2025-11-11 20:23 103
	 *
1de972a7 Poul Sander 2025-11-11 20:23 104
	 * @param logical_x The x coordinate in logical space
1de972a7 Poul Sander 2025-11-11 20:23 105
	 * @param logical_y The y coordinate in logical space
1de972a7 Poul Sander 2025-11-11 20:23 106
	 * @param physical_x Output: the x coordinate in physical screen space
1de972a7 Poul Sander 2025-11-11 20:23 107
	 * @param physical_y Output: the y coordinate in physical screen space
1de972a7 Poul Sander 2025-11-11 20:23 108
	 */
1de972a7 Poul Sander 2025-11-11 20:23 109
	void LogicalToPhysical(int logical_x, int logical_y, int& physical_x, int& physical_y) const {
cb02a4a0 Poul Sander 2025-03-03 21:25 110
		physical_x = logical_x;
cb02a4a0 Poul Sander 2025-03-03 21:25 111
		physical_y = logical_y;
cb02a4a0 Poul Sander 2025-03-03 21:25 112
		LogicalToPhysical(&physical_x, &physical_y);
cb02a4a0 Poul Sander 2025-03-03 21:25 113
	}
cb02a4a0 Poul Sander 2025-03-03 21:25 114
1de972a7 Poul Sander 2025-11-11 20:23 115
	/**
1de972a7 Poul Sander 2025-11-11 20:23 116
	 * @brief Converts logical coordinates to physical screen coordinates (pointer version).
1de972a7 Poul Sander 2025-11-11 20:23 117
	 *
1de972a7 Poul Sander 2025-11-11 20:23 118
	 * This applies the scale factor and adds the appropriate margins.
1de972a7 Poul Sander 2025-11-11 20:23 119
	 * Null pointers are safely ignored.
1de972a7 Poul Sander 2025-11-11 20:23 120
	 *
1de972a7 Poul Sander 2025-11-11 20:23 121
	 * @param x Pointer to x coordinate (will be converted in place), can be null
1de972a7 Poul Sander 2025-11-11 20:23 122
	 * @param y Pointer to y coordinate (will be converted in place), can be null
1de972a7 Poul Sander 2025-11-11 20:23 123
	 */
1de972a7 Poul Sander 2025-11-11 20:23 124
	void LogicalToPhysical(int* x, int* y) const {
cb02a4a0 Poul Sander 2025-03-03 21:25 125
		if (x) {
cb02a4a0 Poul Sander 2025-03-03 21:25 126
			*x = *x * scale_factor_ + left_margin_;
cb02a4a0 Poul Sander 2025-03-03 21:25 127
		}
cb02a4a0 Poul Sander 2025-03-03 21:25 128
		if (y) {
cb02a4a0 Poul Sander 2025-03-03 21:25 129
			*y = *y * scale_factor_ + top_margin_;
cb02a4a0 Poul Sander 2025-03-03 21:25 130
		}
cb02a4a0 Poul Sander 2025-03-03 21:25 131
	}
cb02a4a0 Poul Sander 2025-03-03 21:25 132
1de972a7 Poul Sander 2025-11-11 20:23 133
	/**
1de972a7 Poul Sander 2025-11-11 20:23 134
	 * @brief Converts a logical rectangle to physical screen coordinates.
1de972a7 Poul Sander 2025-11-11 20:23 135
	 *
1de972a7 Poul Sander 2025-11-11 20:23 136
	 * This converts both the position AND size of a rectangle. The rectangle
1de972a7 Poul Sander 2025-11-11 20:23 137
	 * is transformed in place. Use this for converting areas/regions rather than
1de972a7 Poul Sander 2025-11-11 20:23 138
	 * just single points.
1de972a7 Poul Sander 2025-11-11 20:23 139
	 *
1de972a7 Poul Sander 2025-11-11 20:23 140
	 * Note: This properly handles the size conversion by converting the bottom-right
1de972a7 Poul Sander 2025-11-11 20:23 141
	 * corner and calculating the new width/height from the difference.
1de972a7 Poul Sander 2025-11-11 20:23 142
	 *
1de972a7 Poul Sander 2025-11-11 20:23 143
	 * @param inout The rectangle in logical coordinates (input), converted to physical coordinates (output)
1de972a7 Poul Sander 2025-11-11 20:23 144
	 */
1de972a7 Poul Sander 2025-11-11 20:23 145
	void LogicalToPhysical(SDL_Rect& inout) const {
cb02a4a0 Poul Sander 2025-03-03 21:25 146
		SDL_Rect input = inout;
cb02a4a0 Poul Sander 2025-03-03 21:25 147
		LogicalToPhysical(&inout.x, &inout.y);
cb02a4a0 Poul Sander 2025-03-03 21:25 148
		LogicalToPhysical(input.x + input.w + 1, input.y + input.h + 1, inout.w, inout.h);
cb02a4a0 Poul Sander 2025-03-03 21:25 149
		inout.w -= inout.x - 1;
cb02a4a0 Poul Sander 2025-03-03 21:25 150
		inout.h -= inout.y - 1;
cb02a4a0 Poul Sander 2025-03-03 21:25 151
	}
cb02a4a0 Poul Sander 2025-03-03 21:25 152
1de972a7 Poul Sander 2025-11-11 20:23 153
	/**
1de972a7 Poul Sander 2025-11-11 20:23 154
	 * @brief Converts physical screen coordinates to logical coordinates.
1de972a7 Poul Sander 2025-11-11 20:23 155
	 *
1de972a7 Poul Sander 2025-11-11 20:23 156
	 * Use this to convert mouse input or other physical positions back to your
1de972a7 Poul Sander 2025-11-11 20:23 157
	 * logical coordinate system for hit detection and input handling.
1de972a7 Poul Sander 2025-11-11 20:23 158
	 * This removes margins and applies inverse scaling.
1de972a7 Poul Sander 2025-11-11 20:23 159
	 *
1de972a7 Poul Sander 2025-11-11 20:23 160
	 * @param physical_x The x coordinate in physical screen space
1de972a7 Poul Sander 2025-11-11 20:23 161
	 * @param physical_y The y coordinate in physical screen space
1de972a7 Poul Sander 2025-11-11 20:23 162
	 * @param logical_x Output: the x coordinate in logical space
1de972a7 Poul Sander 2025-11-11 20:23 163
	 * @param logical_y Output: the y coordinate in logical space
1de972a7 Poul Sander 2025-11-11 20:23 164
	 */
1de972a7 Poul Sander 2025-11-11 20:23 165
	void PhysicalToLogical(int physical_x, int physical_y, int& logical_x, int& logical_y) const {
931a61e9 Poul Sander 2025-03-05 20:35 166
		logical_x = (physical_x - left_margin_) / scale_factor_;
931a61e9 Poul Sander 2025-03-05 20:35 167
		logical_y = (physical_y - top_margin_) / scale_factor_;
cb02a4a0 Poul Sander 2025-03-03 21:25 168
	}
cb02a4a0 Poul Sander 2025-03-03 21:25 169
1de972a7 Poul Sander 2025-11-11 20:23 170
	/**
1de972a7 Poul Sander 2025-11-11 20:23 171
	 * @brief Gets the top margin (letterboxing/pillarboxing offset).
1de972a7 Poul Sander 2025-11-11 20:23 172
	 *
1de972a7 Poul Sander 2025-11-11 20:23 173
	 * This is the black bar size at the top when the aspect ratio requires vertical margins.
1de972a7 Poul Sander 2025-11-11 20:23 174
	 * Useful for debugging or custom rendering that needs to know the drawable area.
1de972a7 Poul Sander 2025-11-11 20:23 175
	 *
1de972a7 Poul Sander 2025-11-11 20:23 176
	 * @return The top margin in physical pixels
1de972a7 Poul Sander 2025-11-11 20:23 177
	 */
1de972a7 Poul Sander 2025-11-11 20:23 178
	int GetTopMargin() const {
cb02a4a0 Poul Sander 2025-03-03 21:25 179
		return top_margin_;
cb02a4a0 Poul Sander 2025-03-03 21:25 180
	}
cb02a4a0 Poul Sander 2025-03-03 21:25 181
1de972a7 Poul Sander 2025-11-11 20:23 182
	/**
1de972a7 Poul Sander 2025-11-11 20:23 183
	 * @brief Gets the left margin (letterboxing/pillarboxing offset).
1de972a7 Poul Sander 2025-11-11 20:23 184
	 *
1de972a7 Poul Sander 2025-11-11 20:23 185
	 * This is the black bar size on the left when the aspect ratio requires horizontal margins.
1de972a7 Poul Sander 2025-11-11 20:23 186
	 * Useful for debugging or custom rendering that needs to know the drawable area.
1de972a7 Poul Sander 2025-11-11 20:23 187
	 *
1de972a7 Poul Sander 2025-11-11 20:23 188
	 * @return The left margin in physical pixels
1de972a7 Poul Sander 2025-11-11 20:23 189
	 */
1de972a7 Poul Sander 2025-11-11 20:23 190
	int GetLeftMargin() const {
cb02a4a0 Poul Sander 2025-03-03 21:25 191
		return left_margin_;
cb02a4a0 Poul Sander 2025-03-03 21:25 192
	}
cb02a4a0 Poul Sander 2025-03-03 21:25 193
cb02a4a0 Poul Sander 2025-03-03 21:25 194
private:
1de972a7 Poul Sander 2025-11-11 20:23 195
	/**
1de972a7 Poul Sander 2025-11-11 20:23 196
	 * @brief Calculates the scale factor and margins based on current logical and physical sizes.
1de972a7 Poul Sander 2025-11-11 20:23 197
	 *
1de972a7 Poul Sander 2025-11-11 20:23 198
	 * The scale factor is chosen to fit the logical size into the physical size while
1de972a7 Poul Sander 2025-11-11 20:23 199
	 * maintaining aspect ratio. The margins center the content on screen.
1de972a7 Poul Sander 2025-11-11 20:23 200
	 *
1de972a7 Poul Sander 2025-11-11 20:23 201
	 * Scale factor = min(physical_width/logical_width, physical_height/logical_height)
1de972a7 Poul Sander 2025-11-11 20:23 202
	 * This ensures the content fits in both dimensions without distortion.
1de972a7 Poul Sander 2025-11-11 20:23 203
	 */
1de972a7 Poul Sander 2025-11-11 20:23 204
	void SetScaleFactor() {
cb02a4a0 Poul Sander 2025-03-03 21:25 205
		scale_factor_ = std::min(physical_width_ / logical_width_, physical_height_ / logical_height_);
cb02a4a0 Poul Sander 2025-03-03 21:25 206
		left_margin_ = (physical_width_ - logical_width_ * scale_factor_) / 2;
cb02a4a0 Poul Sander 2025-03-03 21:25 207
		top_margin_ = (physical_height_ - logical_height_ * scale_factor_) / 2;
cb02a4a0 Poul Sander 2025-03-03 21:25 208
	}
cb02a4a0 Poul Sander 2025-03-03 21:25 209
1de972a7 Poul Sander 2025-11-11 20:23 210
	double scale_factor_;      ///< Multiplier to convert logical size to physical size
1de972a7 Poul Sander 2025-11-11 20:23 211
	int top_margin_;           ///< Vertical offset for centering (letterboxing)
1de972a7 Poul Sander 2025-11-11 20:23 212
	int left_margin_;          ///< Horizontal offset for centering (pillarboxing)
1de972a7 Poul Sander 2025-11-11 20:23 213
	double logical_width_;     ///< Width of the logical coordinate system
1de972a7 Poul Sander 2025-11-11 20:23 214
	double logical_height_;    ///< Height of the logical coordinate system
1de972a7 Poul Sander 2025-11-11 20:23 215
	double physical_width_;    ///< Actual width of the screen/window
1de972a7 Poul Sander 2025-11-11 20:23 216
	double physical_height_;   ///< Actual height of the screen/window
cb02a4a0 Poul Sander 2025-03-03 21:25 217
};
cb02a4a0 Poul Sander 2025-03-03 21:25 218
cb02a4a0 Poul Sander 2025-03-03 21:25 219
}  // namespace sago