Extra2D/Extra2D/src/graphics/camera/viewport_adapter.cpp

452 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <extra2d/graphics/camera/viewport_adapter.h>
#include <algorithm>
#include <glm/gtc/matrix_transform.hpp>
namespace extra2d {
/**
* @brief 默认构造函数
*
* 创建一个未配置的视口适配器
*/
ViewportAdapter::ViewportAdapter() = default;
/**
* @brief 构造函数
* @param logicWidth 逻辑宽度
* @param logicHeight 逻辑高度
*
* 创建一个指定逻辑尺寸的视口适配器
*/
ViewportAdapter::ViewportAdapter(float logicWidth, float logicHeight) {
config_.logicWidth = logicWidth;
config_.logicHeight = logicHeight;
}
/**
* @brief 设置视口配置
* @param config 视口配置
*
* 设置完整的视口配置并标记矩阵为脏
*/
void ViewportAdapter::setConfig(const ViewportConfig &config) {
config_ = config;
matrixDirty_ = true;
}
/**
* @brief 设置逻辑尺寸
* @param width 逻辑宽度
* @param height 逻辑高度
*
* 设置视口的逻辑尺寸并标记矩阵为脏
*/
void ViewportAdapter::setLogicSize(float width, float height) {
config_.logicWidth = width;
config_.logicHeight = height;
matrixDirty_ = true;
}
/**
* @brief 设置视口模式
* @param mode 视口模式
*
* 设置视口适配模式(宽高比、拉伸、居中等)
*/
void ViewportAdapter::setMode(ViewportMode mode) {
config_.mode = mode;
matrixDirty_ = true;
}
/**
* @brief 设置黑边位置
* @param position 黑边位置
*
* 设置黑边相对于视口的位置
*/
void ViewportAdapter::setLetterboxPosition(LetterboxPosition position) {
config_.letterboxPosition = position;
matrixDirty_ = true;
}
/**
* @brief 设置黑边颜色
* @param color 黑边颜色
*
* 设置黑边区域的填充颜色
*/
void ViewportAdapter::setLetterboxColor(const Color &color) {
config_.letterboxColor = color;
}
/**
* @brief 更新视口适配
* @param screenWidth 屏幕宽度
* @param screenHeight 屏幕高度
*
* 根据屏幕尺寸和配置计算视口参数
*/
void ViewportAdapter::update(int screenWidth, int screenHeight) {
if (screenWidth_ == screenWidth && screenHeight_ == screenHeight &&
!matrixDirty_) {
return;
}
screenWidth_ = screenWidth;
screenHeight_ = screenHeight;
matrixDirty_ = true;
result_.hasLetterbox = false;
result_.letterbox.top = Rect::Zero();
result_.letterbox.bottom = Rect::Zero();
result_.letterbox.left = Rect::Zero();
result_.letterbox.right = Rect::Zero();
switch (config_.mode) {
case ViewportMode::AspectRatio:
calculateAspectRatio();
break;
case ViewportMode::Stretch:
calculateStretch();
break;
case ViewportMode::Center:
calculateCenter();
break;
case ViewportMode::Custom:
calculateCustom();
break;
}
}
/**
* @brief 计算宽高比适配模式
*
* 保持逻辑宽高比,根据屏幕尺寸计算缩放和偏移
*/
void ViewportAdapter::calculateAspectRatio() {
if (config_.logicHeight <= 0.0f || screenHeight_ <= 0) {
result_ = ViewportResult();
return;
}
float logicAspect = config_.logicWidth / config_.logicHeight;
float screenAspect = static_cast<float>(screenWidth_) / screenHeight_;
if (screenAspect > logicAspect) {
result_.uniformScale = static_cast<float>(screenHeight_) / config_.logicHeight;
result_.scaleX = result_.uniformScale;
result_.scaleY = result_.uniformScale;
result_.viewport.size.width = config_.logicWidth * result_.uniformScale;
result_.viewport.size.height = static_cast<float>(screenHeight_);
result_.offset.x = (screenWidth_ - result_.viewport.size.width) / 2.0f;
result_.offset.y = 0.0f;
} else {
result_.uniformScale = static_cast<float>(screenWidth_) / config_.logicWidth;
result_.scaleX = result_.uniformScale;
result_.scaleY = result_.uniformScale;
result_.viewport.size.width = static_cast<float>(screenWidth_);
result_.viewport.size.height = config_.logicHeight * result_.uniformScale;
result_.offset.x = 0.0f;
result_.offset.y = (screenHeight_ - result_.viewport.size.height) / 2.0f;
}
result_.viewport.origin = result_.offset;
applyLetterboxPosition(
static_cast<float>(screenWidth_) - result_.viewport.size.width,
static_cast<float>(screenHeight_) - result_.viewport.size.height);
calculateLetterbox();
}
/**
* @brief 计算拉伸模式
*
* 拉伸逻辑视口以填满整个屏幕
*/
void ViewportAdapter::calculateStretch() {
result_.scaleX = static_cast<float>(screenWidth_) / config_.logicWidth;
result_.scaleY = static_cast<float>(screenHeight_) / config_.logicHeight;
result_.uniformScale = std::min(result_.scaleX, result_.scaleY);
result_.viewport.origin = Vec2::Zero();
result_.viewport.size.width = static_cast<float>(screenWidth_);
result_.viewport.size.height = static_cast<float>(screenHeight_);
result_.offset = Vec2::Zero();
result_.hasLetterbox = false;
}
/**
* @brief 计算居中模式
*
* 将逻辑视口居中显示,可选自动缩放
*/
void ViewportAdapter::calculateCenter() {
float displayWidth = config_.logicWidth;
float displayHeight = config_.logicHeight;
if (config_.autoScaleInCenterMode) {
float scaleX = static_cast<float>(screenWidth_) / config_.logicWidth;
float scaleY = static_cast<float>(screenHeight_) / config_.logicHeight;
result_.uniformScale = std::min(scaleX, scaleY);
if (result_.uniformScale < 1.0f) {
displayWidth = config_.logicWidth * result_.uniformScale;
displayHeight = config_.logicHeight * result_.uniformScale;
} else {
result_.uniformScale = 1.0f;
}
result_.scaleX = result_.uniformScale;
result_.scaleY = result_.uniformScale;
} else {
result_.scaleX = 1.0f;
result_.scaleY = 1.0f;
result_.uniformScale = 1.0f;
}
result_.offset.x = (screenWidth_ - displayWidth) / 2.0f;
result_.offset.y = (screenHeight_ - displayHeight) / 2.0f;
result_.viewport.origin = result_.offset;
result_.viewport.size.width = displayWidth;
result_.viewport.size.height = displayHeight;
applyLetterboxPosition(
static_cast<float>(screenWidth_) - displayWidth,
static_cast<float>(screenHeight_) - displayHeight);
calculateLetterbox();
}
/**
* @brief 计算自定义模式
*
* 使用自定义缩放和偏移参数
*/
void ViewportAdapter::calculateCustom() {
result_.scaleX = config_.customScale;
result_.scaleY = config_.customScale;
result_.uniformScale = config_.customScale;
if (config_.customViewport.empty()) {
float displayWidth = config_.logicWidth * config_.customScale;
float displayHeight = config_.logicHeight * config_.customScale;
result_.offset = config_.customOffset;
result_.viewport.origin = result_.offset;
result_.viewport.size.width = displayWidth;
result_.viewport.size.height = displayHeight;
} else {
result_.viewport = config_.customViewport;
result_.offset = config_.customViewport.origin;
}
calculateLetterbox();
}
/**
* @brief 计算黑边区域
*
* 根据视口偏移计算上下左右黑边矩形
*/
void ViewportAdapter::calculateLetterbox() {
result_.hasLetterbox = false;
float screenW = static_cast<float>(screenWidth_);
float screenH = static_cast<float>(screenHeight_);
if (result_.offset.y > 0.0f) {
result_.hasLetterbox = true;
result_.letterbox.top =
Rect(0.0f, 0.0f, screenW, result_.offset.y);
result_.letterbox.bottom =
Rect(0.0f, result_.offset.y + result_.viewport.size.height, screenW,
result_.offset.y);
}
if (result_.offset.x > 0.0f) {
result_.hasLetterbox = true;
result_.letterbox.left =
Rect(0.0f, 0.0f, result_.offset.x, screenH);
result_.letterbox.right =
Rect(result_.offset.x + result_.viewport.size.width, 0.0f,
result_.offset.x, screenH);
}
}
/**
* @brief 应用黑边位置
* @param extraWidth 额外宽度
* @param extraHeight 额外高度
*
* 根据配置调整视口偏移以实现不同的黑边位置
*/
void ViewportAdapter::applyLetterboxPosition(float extraWidth,
float extraHeight) {
if (extraWidth <= 0.0f && extraHeight <= 0.0f) {
return;
}
switch (config_.letterboxPosition) {
case LetterboxPosition::Center:
break;
case LetterboxPosition::LeftTop:
if (extraWidth > 0.0f) {
result_.offset.x = 0.0f;
}
if (extraHeight > 0.0f) {
result_.offset.y = 0.0f;
}
break;
case LetterboxPosition::RightTop:
if (extraWidth > 0.0f) {
result_.offset.x = extraWidth;
}
if (extraHeight > 0.0f) {
result_.offset.y = 0.0f;
}
break;
case LetterboxPosition::LeftBottom:
if (extraWidth > 0.0f) {
result_.offset.x = 0.0f;
}
if (extraHeight > 0.0f) {
result_.offset.y = extraHeight;
}
break;
case LetterboxPosition::RightBottom:
if (extraWidth > 0.0f) {
result_.offset.x = extraWidth;
}
if (extraHeight > 0.0f) {
result_.offset.y = extraHeight;
}
break;
}
result_.viewport.origin = result_.offset;
}
/**
* @brief 将屏幕坐标转换为逻辑坐标
* @param screenPos 屏幕坐标
* @return 逻辑坐标
*
* 根据当前缩放和偏移计算对应的逻辑坐标
*/
Vec2 ViewportAdapter::screenToLogic(const Vec2 &screenPos) const {
return Vec2((screenPos.x - result_.offset.x) / result_.scaleX,
(screenPos.y - result_.offset.y) / result_.scaleY);
}
/**
* @brief 将逻辑坐标转换为屏幕坐标
* @param logicPos 逻辑坐标
* @return 屏幕坐标
*
* 根据当前缩放和偏移计算对应的屏幕坐标
*/
Vec2 ViewportAdapter::logicToScreen(const Vec2 &logicPos) const {
return Vec2(logicPos.x * result_.scaleX + result_.offset.x,
logicPos.y * result_.scaleY + result_.offset.y);
}
/**
* @brief 将屏幕坐标转换为逻辑坐标
* @param x 屏幕X坐标
* @param y 屏幕Y坐标
* @return 逻辑坐标
*/
Vec2 ViewportAdapter::screenToLogic(float x, float y) const {
return screenToLogic(Vec2(x, y));
}
/**
* @brief 将逻辑坐标转换为屏幕坐标
* @param x 逻辑X坐标
* @param y 逻辑Y坐标
* @return 屏幕坐标
*/
Vec2 ViewportAdapter::logicToScreen(float x, float y) const {
return logicToScreen(Vec2(x, y));
}
/**
* @brief 获取视口变换矩阵
* @return 4x4变换矩阵
*
* 返回用于将逻辑坐标转换为屏幕坐标的变换矩阵
*/
glm::mat4 ViewportAdapter::getMatrix() const {
if (matrixDirty_) {
viewportMatrix_ = glm::mat4(1.0f);
viewportMatrix_ = glm::translate(viewportMatrix_,
glm::vec3(result_.offset.x, result_.offset.y, 0.0f));
viewportMatrix_ = glm::scale(viewportMatrix_,
glm::vec3(result_.scaleX, result_.scaleY, 1.0f));
matrixDirty_ = false;
}
return viewportMatrix_;
}
/**
* @brief 获取视口逆变换矩阵
* @return 4x4逆变换矩阵
*
* 返回用于将屏幕坐标转换为逻辑坐标的逆变换矩阵
*/
glm::mat4 ViewportAdapter::getInvMatrix() const {
if (matrixDirty_) {
getMatrix();
}
inverseViewportMatrix_ = glm::inverse(viewportMatrix_);
return inverseViewportMatrix_;
}
/**
* @brief 检查屏幕坐标是否在视口内
* @param screenPos 屏幕坐标
* @return 在视口内返回true否则返回false
*/
bool ViewportAdapter::isInViewport(const Vec2 &screenPos) const {
return result_.viewport.containsPoint(screenPos);
}
/**
* @brief 检查屏幕坐标是否在黑边区域内
* @param screenPos 屏幕坐标
* @return 在黑边区域内返回true否则返回false
*/
bool ViewportAdapter::isInLetterbox(const Vec2 &screenPos) const {
if (!result_.hasLetterbox) {
return false;
}
if (!result_.letterbox.top.empty() &&
result_.letterbox.top.containsPoint(screenPos)) {
return true;
}
if (!result_.letterbox.bottom.empty() &&
result_.letterbox.bottom.containsPoint(screenPos)) {
return true;
}
if (!result_.letterbox.left.empty() &&
result_.letterbox.left.containsPoint(screenPos)) {
return true;
}
if (!result_.letterbox.right.empty() &&
result_.letterbox.right.containsPoint(screenPos)) {
return true;
}
return false;
}
} // namespace extra2d