feat(视口适配): 添加视口适配器功能并集成到相机和输入系统
实现视口适配器功能,支持多种适配模式(宽高比、拉伸、居中、自定义) 将视口适配器集成到相机系统,实现坐标转换和视口自动调整 将视口适配器集成到输入系统,支持逻辑坐标转换 移除不再使用的字符串转换工具类 优化相机矩阵计算,支持旋转和缩放 添加数学工具函数,包括角度处理和坐标转换
This commit is contained in:
parent
2767d64bf8
commit
93d07e547f
|
|
@ -15,6 +15,7 @@ class TimerManager;
|
||||||
class EventQueue;
|
class EventQueue;
|
||||||
class EventDispatcher;
|
class EventDispatcher;
|
||||||
class Camera;
|
class Camera;
|
||||||
|
class ViewportAdapter;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Application 配置
|
// Application 配置
|
||||||
|
|
@ -80,6 +81,12 @@ public:
|
||||||
EventDispatcher &eventDispatcher();
|
EventDispatcher &eventDispatcher();
|
||||||
Camera &camera();
|
Camera &camera();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视口适配器
|
||||||
|
* @return 视口适配器引用
|
||||||
|
*/
|
||||||
|
ViewportAdapter &viewportAdapter();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 便捷方法
|
// 便捷方法
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -115,6 +122,7 @@ private:
|
||||||
UniquePtr<EventQueue> eventQueue_;
|
UniquePtr<EventQueue> eventQueue_;
|
||||||
UniquePtr<EventDispatcher> eventDispatcher_;
|
UniquePtr<EventDispatcher> eventDispatcher_;
|
||||||
UniquePtr<Camera> camera_;
|
UniquePtr<Camera> camera_;
|
||||||
|
UniquePtr<ViewportAdapter> viewportAdapter_;
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,221 @@ inline float degrees(float radians) { return radians * RAD_TO_DEG; }
|
||||||
|
|
||||||
inline float radians(float degrees) { return degrees * DEG_TO_RAD; }
|
inline float radians(float degrees) { return degrees * DEG_TO_RAD; }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 角度工具函数
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 规范化角度到 [0, 360) 范围
|
||||||
|
* @param degrees 输入角度(度数)
|
||||||
|
* @return 规范化后的角度,范围 [0, 360)
|
||||||
|
*/
|
||||||
|
inline float normalizeAngle360(float degrees) {
|
||||||
|
degrees = std::fmod(degrees, 360.0f);
|
||||||
|
if (degrees < 0.0f) {
|
||||||
|
degrees += 360.0f;
|
||||||
|
}
|
||||||
|
return degrees;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 规范化角度到 [-180, 180) 范围
|
||||||
|
* @param degrees 输入角度(度数)
|
||||||
|
* @return 规范化后的角度,范围 [-180, 180)
|
||||||
|
*/
|
||||||
|
inline float normalizeAngle180(float degrees) {
|
||||||
|
degrees = std::fmod(degrees + 180.0f, 360.0f);
|
||||||
|
if (degrees < 0.0f) {
|
||||||
|
degrees += 360.0f;
|
||||||
|
}
|
||||||
|
return degrees - 180.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算两个角度之间的最短差值
|
||||||
|
* @param from 起始角度(度数)
|
||||||
|
* @param to 目标角度(度数)
|
||||||
|
* @return 从 from 到 to 的最短角度差,范围 [-180, 180]
|
||||||
|
*/
|
||||||
|
inline float angleDifference(float from, float to) {
|
||||||
|
float diff = normalizeAngle360(to - from);
|
||||||
|
if (diff > 180.0f) {
|
||||||
|
diff -= 360.0f;
|
||||||
|
}
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 线性插值角度
|
||||||
|
* @param from 起始角度(度数)
|
||||||
|
* @param to 目标角度(度数)
|
||||||
|
* @param t 插值因子 [0, 1]
|
||||||
|
* @return 插值后的角度
|
||||||
|
*/
|
||||||
|
inline float lerpAngle(float from, float to, float t) {
|
||||||
|
return from + angleDifference(from, to) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 向量工具函数
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算方向向量(从 from 指向 to 的单位向量)
|
||||||
|
* @param from 起始点
|
||||||
|
* @param to 目标点
|
||||||
|
* @return 归一化的方向向量
|
||||||
|
*/
|
||||||
|
inline Vec2 direction(const Vec2 &from, const Vec2 &to) {
|
||||||
|
return (to - from).normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算两点之间的角度
|
||||||
|
* @param from 起始点
|
||||||
|
* @param to 目标点
|
||||||
|
* @return 角度(度数),范围 [-180, 180]
|
||||||
|
*/
|
||||||
|
inline float angleBetween(const Vec2 &from, const Vec2 &to) {
|
||||||
|
Vec2 dir = to - from;
|
||||||
|
return std::atan2(dir.y, dir.x) * RAD_TO_DEG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据角度创建方向向量
|
||||||
|
* @param degrees 角度(度数),0度指向右方,逆时针为正
|
||||||
|
* @return 单位方向向量
|
||||||
|
*/
|
||||||
|
inline Vec2 angleToVector(float degrees) {
|
||||||
|
float rad = degrees * DEG_TO_RAD;
|
||||||
|
return {std::cos(rad), std::sin(rad)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将向量旋转指定角度
|
||||||
|
* @param v 原始向量
|
||||||
|
* @param degrees 旋转角度(度数),正值为逆时针旋转
|
||||||
|
* @return 旋转后的向量
|
||||||
|
*/
|
||||||
|
inline Vec2 rotateVector(const Vec2 &v, float degrees) {
|
||||||
|
float rad = degrees * DEG_TO_RAD;
|
||||||
|
float cosA = std::cos(rad);
|
||||||
|
float sinA = std::sin(rad);
|
||||||
|
return {v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 坐标系转换工具
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Y轴向上坐标转Y轴向下坐标
|
||||||
|
* @param pos Y轴向上坐标系中的位置
|
||||||
|
* @param height 画布/屏幕高度
|
||||||
|
* @return Y轴向下坐标系中的位置
|
||||||
|
*/
|
||||||
|
inline Vec2 flipY(const Vec2 &pos, float height) {
|
||||||
|
return {pos.x, height - pos.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Y轴向下坐标转Y轴向上坐标
|
||||||
|
* @param pos Y轴向下坐标系中的位置
|
||||||
|
* @param height 画布/屏幕高度
|
||||||
|
* @return Y轴向上坐标系中的位置
|
||||||
|
*/
|
||||||
|
inline Vec2 unflipY(const Vec2 &pos, float height) {
|
||||||
|
return {pos.x, height - pos.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 矩阵工具函数
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从变换矩阵提取位置
|
||||||
|
* @param matrix 4x4变换矩阵
|
||||||
|
* @return 提取的位置向量
|
||||||
|
*/
|
||||||
|
inline Vec2 extractPosition(const glm::mat4 &matrix) {
|
||||||
|
return {matrix[3][0], matrix[3][1]};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从变换矩阵提取缩放
|
||||||
|
* @param matrix 4x4变换矩阵
|
||||||
|
* @return 提取的缩放向量
|
||||||
|
*/
|
||||||
|
inline Vec2 extractScale(const glm::mat4 &matrix) {
|
||||||
|
float scaleX = std::sqrt(matrix[0][0] * matrix[0][0] + matrix[0][1] * matrix[0][1]);
|
||||||
|
float scaleY = std::sqrt(matrix[1][0] * matrix[1][0] + matrix[1][1] * matrix[1][1]);
|
||||||
|
return {scaleX, scaleY};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从变换矩阵提取旋转角度
|
||||||
|
* @param matrix 4x4变换矩阵
|
||||||
|
* @return 提取的旋转角度(度数)
|
||||||
|
*/
|
||||||
|
inline float extractRotation(const glm::mat4 &matrix) {
|
||||||
|
return std::atan2(matrix[0][1], matrix[0][0]) * RAD_TO_DEG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 碰撞检测工具
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断点是否在矩形内
|
||||||
|
* @param point 要检测的点
|
||||||
|
* @param rect 矩形区域
|
||||||
|
* @return 如果点在矩形内返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
inline bool pointInRect(const Vec2 &point, const Rect &rect) {
|
||||||
|
return point.x >= rect.left() && point.x <= rect.right() &&
|
||||||
|
point.y >= rect.top() && point.y <= rect.bottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断点是否在圆内
|
||||||
|
* @param point 要检测的点
|
||||||
|
* @param center 圆心
|
||||||
|
* @param radius 圆的半径
|
||||||
|
* @return 如果点在圆内返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
inline bool pointInCircle(const Vec2 &point, const Vec2 ¢er, float radius) {
|
||||||
|
float dx = point.x - center.x;
|
||||||
|
float dy = point.y - center.y;
|
||||||
|
return (dx * dx + dy * dy) <= (radius * radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断两个矩形是否相交
|
||||||
|
* @param a 第一个矩形
|
||||||
|
* @param b 第二个矩形
|
||||||
|
* @return 如果矩形相交返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
inline bool rectsIntersect(const Rect &a, const Rect &b) {
|
||||||
|
return a.intersects(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断两个圆是否相交
|
||||||
|
* @param center1 第一个圆的圆心
|
||||||
|
* @param radius1 第一个圆的半径
|
||||||
|
* @param center2 第二个圆的圆心
|
||||||
|
* @param radius2 第二个圆的半径
|
||||||
|
* @return 如果圆相交返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
inline bool circlesIntersect(const Vec2 ¢er1, float radius1,
|
||||||
|
const Vec2 ¢er2, float radius2) {
|
||||||
|
float dx = center2.x - center1.x;
|
||||||
|
float dy = center2.y - center1.y;
|
||||||
|
float distSq = dx * dx + dy * dy;
|
||||||
|
float radiusSum = radius1 + radius2;
|
||||||
|
return distSq <= (radiusSum * radiusSum);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace math
|
} // namespace math
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
#include <extra2d/graphics/texture.h>
|
#include <extra2d/graphics/texture.h>
|
||||||
|
|
||||||
#include <extra2d/graphics/render_target.h>
|
#include <extra2d/graphics/render_target.h>
|
||||||
|
#include <extra2d/graphics/viewport_adapter.h>
|
||||||
#include <extra2d/graphics/vram_manager.h>
|
#include <extra2d/graphics/vram_manager.h>
|
||||||
|
|
||||||
// Scene
|
// Scene
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
class ViewportAdapter;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 2D 正交相机
|
// 2D 正交相机
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -65,6 +67,20 @@ public:
|
||||||
void clearBounds();
|
void clearBounds();
|
||||||
void clampToBounds();
|
void clampToBounds();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 视口适配器
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief 设置视口适配器
|
||||||
|
* @param adapter 视口适配器指针
|
||||||
|
*/
|
||||||
|
void setViewportAdapter(ViewportAdapter* adapter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据视口适配器自动设置视口
|
||||||
|
*/
|
||||||
|
void applyViewportAdapter();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 快捷方法:看向某点
|
// 快捷方法:看向某点
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -83,6 +99,8 @@ private:
|
||||||
Rect bounds_;
|
Rect bounds_;
|
||||||
bool hasBounds_ = false;
|
bool hasBounds_ = false;
|
||||||
|
|
||||||
|
ViewportAdapter* viewportAdapter_ = nullptr;
|
||||||
|
|
||||||
mutable glm::mat4 viewMatrix_;
|
mutable glm::mat4 viewMatrix_;
|
||||||
mutable glm::mat4 projMatrix_;
|
mutable glm::mat4 projMatrix_;
|
||||||
mutable glm::mat4 vpMatrix_;
|
mutable glm::mat4 vpMatrix_;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,332 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 视口适配模式枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class ViewportMode {
|
||||||
|
AspectRatio,
|
||||||
|
Stretch,
|
||||||
|
Center,
|
||||||
|
Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 黑边位置枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class LetterboxPosition {
|
||||||
|
Center,
|
||||||
|
LeftTop,
|
||||||
|
RightTop,
|
||||||
|
LeftBottom,
|
||||||
|
RightBottom
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 视口配置结构体
|
||||||
|
// ============================================================================
|
||||||
|
struct ViewportConfig {
|
||||||
|
float logicWidth = 1920.0f;
|
||||||
|
float logicHeight = 1080.0f;
|
||||||
|
ViewportMode mode = ViewportMode::AspectRatio;
|
||||||
|
LetterboxPosition letterboxPosition = LetterboxPosition::Center;
|
||||||
|
Color letterboxColor = Colors::Black;
|
||||||
|
bool autoScaleInCenterMode = true;
|
||||||
|
float customScale = 1.0f;
|
||||||
|
Vec2 customOffset = Vec2::Zero();
|
||||||
|
Rect customViewport = Rect::Zero();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 视口计算结果结构体
|
||||||
|
// ============================================================================
|
||||||
|
struct ViewportResult {
|
||||||
|
Rect viewport;
|
||||||
|
float scaleX = 1.0f;
|
||||||
|
float scaleY = 1.0f;
|
||||||
|
float uniformScale = 1.0f;
|
||||||
|
Vec2 offset;
|
||||||
|
bool hasLetterbox = false;
|
||||||
|
|
||||||
|
struct Letterbox {
|
||||||
|
Rect top;
|
||||||
|
Rect bottom;
|
||||||
|
Rect left;
|
||||||
|
Rect right;
|
||||||
|
} letterbox;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 视口适配器类
|
||||||
|
// ============================================================================
|
||||||
|
class ViewportAdapter {
|
||||||
|
public:
|
||||||
|
ViewportAdapter();
|
||||||
|
ViewportAdapter(float logicWidth, float logicHeight);
|
||||||
|
~ViewportAdapter() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 配置设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口配置
|
||||||
|
* @param config 视口配置结构体
|
||||||
|
*/
|
||||||
|
void setConfig(const ViewportConfig &config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前视口配置
|
||||||
|
* @return 当前视口配置
|
||||||
|
*/
|
||||||
|
const ViewportConfig &getConfig() const { return config_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置逻辑分辨率
|
||||||
|
* @param width 逻辑宽度
|
||||||
|
* @param height 逻辑高度
|
||||||
|
*/
|
||||||
|
void setLogicSize(float width, float height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口适配模式
|
||||||
|
* @param mode 适配模式
|
||||||
|
*/
|
||||||
|
void setMode(ViewportMode mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置黑边位置
|
||||||
|
* @param position 黑边位置
|
||||||
|
*/
|
||||||
|
void setLetterboxPosition(LetterboxPosition position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置黑边颜色
|
||||||
|
* @param color 黑边颜色
|
||||||
|
*/
|
||||||
|
void setLetterboxColor(const Color &color);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 更新和计算
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新视口适配计算
|
||||||
|
* @param screenWidth 屏幕宽度
|
||||||
|
* @param screenHeight 屏幕高度
|
||||||
|
*/
|
||||||
|
void update(int screenWidth, int screenHeight);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取计算结果
|
||||||
|
* @return 视口计算结果
|
||||||
|
*/
|
||||||
|
const ViewportResult &getResult() const { return result_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 坐标转换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 屏幕坐标转逻辑坐标
|
||||||
|
* @param screenPos 屏幕坐标
|
||||||
|
* @return 逻辑坐标
|
||||||
|
*/
|
||||||
|
Vec2 screenToLogic(const Vec2 &screenPos) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 逻辑坐标转屏幕坐标
|
||||||
|
* @param logicPos 逻辑坐标
|
||||||
|
* @return 屏幕坐标
|
||||||
|
*/
|
||||||
|
Vec2 logicToScreen(const Vec2 &logicPos) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 屏幕坐标转逻辑坐标(分量形式)
|
||||||
|
* @param x 屏幕X坐标
|
||||||
|
* @param y 屏幕Y坐标
|
||||||
|
* @return 逻辑坐标
|
||||||
|
*/
|
||||||
|
Vec2 screenToLogic(float x, float y) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 逻辑坐标转屏幕坐标(分量形式)
|
||||||
|
* @param x 逻辑X坐标
|
||||||
|
* @param y 逻辑Y坐标
|
||||||
|
* @return 屏幕坐标
|
||||||
|
*/
|
||||||
|
Vec2 logicToScreen(float x, float y) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 矩阵获取
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视口变换矩阵
|
||||||
|
* @return 视口变换矩阵(从逻辑坐标到屏幕坐标)
|
||||||
|
*/
|
||||||
|
glm::mat4 getViewportMatrix() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取反向视口变换矩阵
|
||||||
|
* @return 反向视口变换矩阵(从屏幕坐标到逻辑坐标)
|
||||||
|
*/
|
||||||
|
glm::mat4 getInverseViewportMatrix() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 区域检测
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查屏幕坐标是否在视口内
|
||||||
|
* @param screenPos 屏幕坐标
|
||||||
|
* @return 如果在视口内返回 true
|
||||||
|
*/
|
||||||
|
bool isInViewport(const Vec2 &screenPos) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查屏幕坐标是否在黑边区域
|
||||||
|
* @param screenPos 屏幕坐标
|
||||||
|
* @return 如果在黑边区域返回 true
|
||||||
|
*/
|
||||||
|
bool isInLetterbox(const Vec2 &screenPos) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Getter 方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取逻辑宽度
|
||||||
|
* @return 逻辑宽度
|
||||||
|
*/
|
||||||
|
float getLogicWidth() const { return config_.logicWidth; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取逻辑高度
|
||||||
|
* @return 逻辑高度
|
||||||
|
*/
|
||||||
|
float getLogicHeight() const { return config_.logicHeight; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取逻辑尺寸
|
||||||
|
* @return 逻辑尺寸
|
||||||
|
*/
|
||||||
|
Size getLogicSize() const {
|
||||||
|
return Size(config_.logicWidth, config_.logicHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取屏幕宽度
|
||||||
|
* @return 屏幕宽度
|
||||||
|
*/
|
||||||
|
int getScreenWidth() const { return screenWidth_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取屏幕高度
|
||||||
|
* @return 屏幕高度
|
||||||
|
*/
|
||||||
|
int getScreenHeight() const { return screenHeight_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取屏幕尺寸
|
||||||
|
* @return 屏幕尺寸
|
||||||
|
*/
|
||||||
|
Size getScreenSize() const {
|
||||||
|
return Size(static_cast<float>(screenWidth_),
|
||||||
|
static_cast<float>(screenHeight_));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取X方向缩放比例
|
||||||
|
* @return X方向缩放比例
|
||||||
|
*/
|
||||||
|
float getScaleX() const { return result_.scaleX; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取Y方向缩放比例
|
||||||
|
* @return Y方向缩放比例
|
||||||
|
*/
|
||||||
|
float getScaleY() const { return result_.scaleY; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取统一缩放比例
|
||||||
|
* @return 统一缩放比例
|
||||||
|
*/
|
||||||
|
float getUniformScale() const { return result_.uniformScale; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视口偏移
|
||||||
|
* @return 视口偏移
|
||||||
|
*/
|
||||||
|
Vec2 getOffset() const { return result_.offset; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视口矩形
|
||||||
|
* @return 视口矩形
|
||||||
|
*/
|
||||||
|
Rect getViewport() const { return result_.viewport; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有黑边
|
||||||
|
* @return 如果有黑边返回 true
|
||||||
|
*/
|
||||||
|
bool hasLetterbox() const { return result_.hasLetterbox; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取黑边信息
|
||||||
|
* @return 黑边信息结构体
|
||||||
|
*/
|
||||||
|
const ViewportResult::Letterbox &getLetterbox() const {
|
||||||
|
return result_.letterbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief 计算宽高比适配模式
|
||||||
|
*/
|
||||||
|
void calculateAspectRatio();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算拉伸适配模式
|
||||||
|
*/
|
||||||
|
void calculateStretch();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算居中适配模式
|
||||||
|
*/
|
||||||
|
void calculateCenter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算自定义适配模式
|
||||||
|
*/
|
||||||
|
void calculateCustom();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算黑边区域
|
||||||
|
*/
|
||||||
|
void calculateLetterbox();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据黑边位置调整偏移
|
||||||
|
* @param extraWidth 额外宽度
|
||||||
|
* @param extraHeight 额外高度
|
||||||
|
*/
|
||||||
|
void applyLetterboxPosition(float extraWidth, float extraHeight);
|
||||||
|
|
||||||
|
ViewportConfig config_;
|
||||||
|
ViewportResult result_;
|
||||||
|
int screenWidth_ = 0;
|
||||||
|
int screenHeight_ = 0;
|
||||||
|
|
||||||
|
mutable glm::mat4 viewportMatrix_;
|
||||||
|
mutable glm::mat4 inverseViewportMatrix_;
|
||||||
|
mutable bool matrixDirty_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
class ViewportAdapter;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 鼠标按钮枚举
|
// 鼠标按钮枚举
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -81,6 +83,33 @@ public:
|
||||||
Vec2 getTouchPosition() const { return touchPosition_; }
|
Vec2 getTouchPosition() const { return touchPosition_; }
|
||||||
int getTouchCount() const { return touchCount_; }
|
int getTouchCount() const { return touchCount_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 视口适配器
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief 设置视口适配器
|
||||||
|
* @param adapter 视口适配器指针
|
||||||
|
*/
|
||||||
|
void setViewportAdapter(ViewportAdapter* adapter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取逻辑坐标下的鼠标位置
|
||||||
|
* @return 逻辑坐标
|
||||||
|
*/
|
||||||
|
Vec2 getMousePositionLogic() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取逻辑坐标下的触摸位置
|
||||||
|
* @return 逻辑坐标
|
||||||
|
*/
|
||||||
|
Vec2 getTouchPositionLogic() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取逻辑坐标下的鼠标增量
|
||||||
|
* @return 逻辑坐标增量
|
||||||
|
*/
|
||||||
|
Vec2 getMouseDeltaLogic() const;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 便捷方法
|
// 便捷方法
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -122,6 +151,9 @@ private:
|
||||||
Vec2 prevTouchPosition_;
|
Vec2 prevTouchPosition_;
|
||||||
int touchCount_;
|
int touchCount_;
|
||||||
|
|
||||||
|
// 视口适配器
|
||||||
|
ViewportAdapter* viewportAdapter_;
|
||||||
|
|
||||||
// 映射键盘 keyCode 到 SDL GameController 按钮 (Switch 兼容模式)
|
// 映射键盘 keyCode 到 SDL GameController 按钮 (Switch 兼容模式)
|
||||||
SDL_GameControllerButton mapKeyToButton(int keyCode) const;
|
SDL_GameControllerButton mapKeyToButton(int keyCode) const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <extra2d/event/event_queue.h>
|
#include <extra2d/event/event_queue.h>
|
||||||
#include <extra2d/graphics/camera.h>
|
#include <extra2d/graphics/camera.h>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/render_backend.h>
|
||||||
|
#include <extra2d/graphics/viewport_adapter.h>
|
||||||
#include <extra2d/graphics/vram_manager.h>
|
#include <extra2d/graphics/vram_manager.h>
|
||||||
#include <extra2d/platform/input.h>
|
#include <extra2d/platform/input.h>
|
||||||
#include <extra2d/platform/window.h>
|
#include <extra2d/platform/window.h>
|
||||||
|
|
@ -139,12 +140,32 @@ bool Application::init(const AppConfig &config) {
|
||||||
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()),
|
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()),
|
||||||
static_cast<float>(window_->getHeight()), 0);
|
static_cast<float>(window_->getHeight()), 0);
|
||||||
|
|
||||||
|
// 创建视口适配器
|
||||||
|
viewportAdapter_ = makeUnique<ViewportAdapter>();
|
||||||
|
ViewportConfig vpConfig;
|
||||||
|
vpConfig.logicWidth = static_cast<float>(config.width);
|
||||||
|
vpConfig.logicHeight = static_cast<float>(config.height);
|
||||||
|
vpConfig.mode = ViewportMode::AspectRatio;
|
||||||
|
viewportAdapter_->setConfig(vpConfig);
|
||||||
|
|
||||||
|
// 关联到各子系统
|
||||||
|
camera_->setViewportAdapter(viewportAdapter_.get());
|
||||||
|
input().setViewportAdapter(viewportAdapter_.get());
|
||||||
|
|
||||||
|
// 初始更新
|
||||||
|
viewportAdapter_->update(window_->getWidth(), window_->getHeight());
|
||||||
|
|
||||||
// 窗口大小回调
|
// 窗口大小回调
|
||||||
window_->setResizeCallback([this](int width, int height) {
|
window_->setResizeCallback([this](int width, int height) {
|
||||||
if (camera_) {
|
// 更新视口适配器
|
||||||
camera_->setViewport(0, static_cast<float>(width),
|
if (viewportAdapter_) {
|
||||||
static_cast<float>(height), 0);
|
viewportAdapter_->update(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (camera_) {
|
||||||
|
camera_->applyViewportAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
if (sceneManager_) {
|
if (sceneManager_) {
|
||||||
auto currentScene = sceneManager_->getCurrentScene();
|
auto currentScene = sceneManager_->getCurrentScene();
|
||||||
if (currentScene) {
|
if (currentScene) {
|
||||||
|
|
@ -204,6 +225,7 @@ void Application::shutdown() {
|
||||||
// ========================================
|
// ========================================
|
||||||
sceneManager_.reset(); // 场景持有纹理引用
|
sceneManager_.reset(); // 场景持有纹理引用
|
||||||
resourceManager_.reset(); // 纹理缓存持有 GPU 纹理
|
resourceManager_.reset(); // 纹理缓存持有 GPU 纹理
|
||||||
|
viewportAdapter_.reset(); // 视口适配器
|
||||||
camera_.reset(); // 相机可能持有渲染目标
|
camera_.reset(); // 相机可能持有渲染目标
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
@ -356,7 +378,16 @@ void Application::render() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
|
// 应用视口适配器
|
||||||
|
if (viewportAdapter_) {
|
||||||
|
const auto &vp = viewportAdapter_->getViewport();
|
||||||
|
renderer_->setViewport(static_cast<int>(vp.origin.x),
|
||||||
|
static_cast<int>(vp.origin.y),
|
||||||
|
static_cast<int>(vp.size.width),
|
||||||
|
static_cast<int>(vp.size.height));
|
||||||
|
} else {
|
||||||
|
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
if (sceneManager_) {
|
if (sceneManager_) {
|
||||||
sceneManager_->render(*renderer_);
|
sceneManager_->render(*renderer_);
|
||||||
|
|
@ -383,6 +414,8 @@ EventDispatcher &Application::eventDispatcher() { return *eventDispatcher_; }
|
||||||
|
|
||||||
Camera &Application::camera() { return *camera_; }
|
Camera &Application::camera() { return *camera_; }
|
||||||
|
|
||||||
|
ViewportAdapter &Application::viewportAdapter() { return *viewportAdapter_; }
|
||||||
|
|
||||||
void Application::enterScene(Ptr<Scene> scene) { enterScene(scene, nullptr); }
|
void Application::enterScene(Ptr<Scene> scene) { enterScene(scene, nullptr); }
|
||||||
|
|
||||||
void Application::enterScene(Ptr<Scene> scene,
|
void Application::enterScene(Ptr<Scene> scene,
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
std::string utf8ToGbkImpl(const std::string& utf8) {
|
|
||||||
if (utf8.empty()) return std::string();
|
|
||||||
|
|
||||||
// UTF-8 → Wide → GBK
|
|
||||||
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);
|
|
||||||
if (wideLen <= 0) return std::string();
|
|
||||||
|
|
||||||
std::wstring wide(wideLen - 1, 0);
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &wide[0], wideLen);
|
|
||||||
|
|
||||||
int gbkLen = WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
|
||||||
if (gbkLen <= 0) return std::string();
|
|
||||||
|
|
||||||
std::string gbk(gbkLen - 1, 0);
|
|
||||||
WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, &gbk[0], gbkLen, nullptr, nullptr);
|
|
||||||
|
|
||||||
return gbk;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string gbkToUtf8Impl(const std::string& gbk) {
|
|
||||||
if (gbk.empty()) return std::string();
|
|
||||||
|
|
||||||
// GBK → Wide → UTF-8
|
|
||||||
int wideLen = MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, nullptr, 0);
|
|
||||||
if (wideLen <= 0) return std::string();
|
|
||||||
|
|
||||||
std::wstring wide(wideLen - 1, 0);
|
|
||||||
MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, &wide[0], wideLen);
|
|
||||||
|
|
||||||
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
|
||||||
if (utf8Len <= 0) return std::string();
|
|
||||||
|
|
||||||
std::string utf8(utf8Len - 1, 0);
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, &utf8[0], utf8Len, nullptr, nullptr);
|
|
||||||
|
|
||||||
return utf8;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
||||||
#endif // _WIN32
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <extra2d/graphics/camera.h>
|
#include <extra2d/graphics/camera.h>
|
||||||
|
#include <extra2d/graphics/viewport_adapter.h>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <glm/gtc/matrix_inverse.hpp>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -55,12 +57,34 @@ Rect Camera::getViewport() const {
|
||||||
return Rect(left_, top_, right_ - left_, bottom_ - top_);
|
return Rect(left_, top_, right_ - left_, bottom_ - top_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视图矩阵
|
||||||
|
* @return 视图矩阵
|
||||||
|
*
|
||||||
|
* 变换顺序:平移 -> 旋转 -> 缩放(逆序应用)
|
||||||
|
* View = T(-position) × R(-rotation) × S(1/zoom)
|
||||||
|
*/
|
||||||
glm::mat4 Camera::getViewMatrix() const {
|
glm::mat4 Camera::getViewMatrix() const {
|
||||||
if (viewDirty_) {
|
if (viewDirty_) {
|
||||||
viewMatrix_ = glm::mat4(1.0f);
|
viewMatrix_ = glm::mat4(1.0f);
|
||||||
// 对于2D相机,我们只需要平移(注意Y轴方向)
|
|
||||||
|
// 1. 平移(最后应用)
|
||||||
viewMatrix_ = glm::translate(viewMatrix_,
|
viewMatrix_ = glm::translate(viewMatrix_,
|
||||||
glm::vec3(-position_.x, -position_.y, 0.0f));
|
glm::vec3(-position_.x, -position_.y, 0.0f));
|
||||||
|
|
||||||
|
// 2. 旋转(中间应用)
|
||||||
|
if (rotation_ != 0.0f) {
|
||||||
|
viewMatrix_ = glm::rotate(viewMatrix_,
|
||||||
|
-rotation_ * DEG_TO_RAD,
|
||||||
|
glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 缩放(最先应用)
|
||||||
|
if (zoom_ != 1.0f) {
|
||||||
|
viewMatrix_ = glm::scale(viewMatrix_,
|
||||||
|
glm::vec3(1.0f / zoom_, 1.0f / zoom_, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
viewDirty_ = false;
|
viewDirty_ = false;
|
||||||
}
|
}
|
||||||
return viewMatrix_;
|
return viewMatrix_;
|
||||||
|
|
@ -81,20 +105,49 @@ glm::mat4 Camera::getProjectionMatrix() const {
|
||||||
return projMatrix_;
|
return projMatrix_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视图-投影矩阵
|
||||||
|
* @return 视图-投影矩阵
|
||||||
|
*/
|
||||||
glm::mat4 Camera::getViewProjectionMatrix() const {
|
glm::mat4 Camera::getViewProjectionMatrix() const {
|
||||||
// 对于2D相机,我们主要依赖投影矩阵
|
return getProjectionMatrix() * getViewMatrix();
|
||||||
// 视口变换已经处理了坐标系转换
|
|
||||||
return getProjectionMatrix();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将屏幕坐标转换为世界坐标
|
||||||
|
* @param screenPos 屏幕坐标
|
||||||
|
* @return 世界坐标
|
||||||
|
*/
|
||||||
Vec2 Camera::screenToWorld(const Vec2 &screenPos) const {
|
Vec2 Camera::screenToWorld(const Vec2 &screenPos) const {
|
||||||
// 屏幕坐标直接映射到世界坐标(在2D中通常相同)
|
Vec2 logicPos = screenPos;
|
||||||
return screenPos;
|
|
||||||
|
// 如果有视口适配器,先转换到逻辑坐标
|
||||||
|
if (viewportAdapter_) {
|
||||||
|
logicPos = viewportAdapter_->screenToLogic(screenPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用逆视图-投影矩阵转换
|
||||||
|
glm::mat4 invVP = glm::inverse(getViewProjectionMatrix());
|
||||||
|
glm::vec4 ndc(logicPos.x, logicPos.y, 0.0f, 1.0f);
|
||||||
|
glm::vec4 world = invVP * ndc;
|
||||||
|
return Vec2(world.x, world.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将世界坐标转换为屏幕坐标
|
||||||
|
* @param worldPos 世界坐标
|
||||||
|
* @return 屏幕坐标
|
||||||
|
*/
|
||||||
Vec2 Camera::worldToScreen(const Vec2 &worldPos) const {
|
Vec2 Camera::worldToScreen(const Vec2 &worldPos) const {
|
||||||
// 世界坐标直接映射到屏幕坐标(在2D中通常相同)
|
glm::vec4 world(worldPos.x, worldPos.y, 0.0f, 1.0f);
|
||||||
return worldPos;
|
glm::vec4 screen = getViewProjectionMatrix() * world;
|
||||||
|
Vec2 logicPos(screen.x, screen.y);
|
||||||
|
|
||||||
|
// 如果有视口适配器,转换到屏幕坐标
|
||||||
|
if (viewportAdapter_) {
|
||||||
|
return viewportAdapter_->logicToScreen(logicPos);
|
||||||
|
}
|
||||||
|
return logicPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2 Camera::screenToWorld(float x, float y) const {
|
Vec2 Camera::screenToWorld(float x, float y) const {
|
||||||
|
|
@ -155,4 +208,22 @@ void Camera::lookAt(const Vec2 &target) {
|
||||||
viewDirty_ = true;
|
viewDirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口适配器
|
||||||
|
* @param adapter 视口适配器指针
|
||||||
|
*/
|
||||||
|
void Camera::setViewportAdapter(ViewportAdapter* adapter) {
|
||||||
|
viewportAdapter_ = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据视口适配器自动设置视口
|
||||||
|
*/
|
||||||
|
void Camera::applyViewportAdapter() {
|
||||||
|
if (viewportAdapter_) {
|
||||||
|
const auto& config = viewportAdapter_->getConfig();
|
||||||
|
setViewport(0.0f, config.logicWidth, config.logicHeight, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
#include <stb/stb_truetype.h>
|
#include <stb/stb_truetype.h>
|
||||||
#define STB_RECT_PACK_IMPLEMENTATION
|
#define STB_RECT_PACK_IMPLEMENTATION
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <stb/stb_rect_pack.h>
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -75,7 +75,8 @@ Vec2 GLFontAtlas::measureText(const std::string &text) {
|
||||||
float height = getAscent() - getDescent();
|
float height = getAscent() - getDescent();
|
||||||
float currentWidth = 0.0f;
|
float currentWidth = 0.0f;
|
||||||
|
|
||||||
for (char32_t codepoint : utf8ToUtf32(text)) {
|
for (char c : text) {
|
||||||
|
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||||||
if (codepoint == '\n') {
|
if (codepoint == '\n') {
|
||||||
width = std::max(width, currentWidth);
|
width = std::max(width, currentWidth);
|
||||||
currentWidth = 0.0f;
|
currentWidth = 0.0f;
|
||||||
|
|
@ -218,8 +219,8 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
||||||
// 使用预分配缓冲区
|
// 使用预分配缓冲区
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
||||||
glyphBitmapCache_.resize(pixelCount);
|
glyphBitmapCache_.resize(pixelCount);
|
||||||
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w, scale_, scale_,
|
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w,
|
||||||
static_cast<int>(codepoint));
|
scale_, scale_, static_cast<int>(codepoint));
|
||||||
|
|
||||||
// 使用 stb_rect_pack 打包矩形
|
// 使用 stb_rect_pack 打包矩形
|
||||||
stbrp_rect rect;
|
stbrp_rect rect;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <extra2d/graphics/gpu_context.h>
|
#include <extra2d/graphics/gpu_context.h>
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
#include <extra2d/graphics/opengl/gl_renderer.h>
|
||||||
|
|
@ -448,7 +447,8 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
std::vector<GLSpriteBatch::SpriteData> sprites;
|
std::vector<GLSpriteBatch::SpriteData> sprites;
|
||||||
sprites.reserve(text.size()); // 预分配空间
|
sprites.reserve(text.size()); // 预分配空间
|
||||||
|
|
||||||
for (char32_t codepoint : utf8ToUtf32(text)) {
|
for (char c : text) {
|
||||||
|
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||||||
if (codepoint == '\n') {
|
if (codepoint == '\n') {
|
||||||
cursorX = x;
|
cursorX = x;
|
||||||
cursorY += font.getLineHeight();
|
cursorY += font.getLineHeight();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,321 @@
|
||||||
|
#include <extra2d/graphics/viewport_adapter.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
ViewportAdapter::ViewportAdapter() = default;
|
||||||
|
|
||||||
|
ViewportAdapter::ViewportAdapter(float logicWidth, float logicHeight) {
|
||||||
|
config_.logicWidth = logicWidth;
|
||||||
|
config_.logicHeight = logicHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportAdapter::setConfig(const ViewportConfig &config) {
|
||||||
|
config_ = config;
|
||||||
|
matrixDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportAdapter::setLogicSize(float width, float height) {
|
||||||
|
config_.logicWidth = width;
|
||||||
|
config_.logicHeight = height;
|
||||||
|
matrixDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportAdapter::setMode(ViewportMode mode) {
|
||||||
|
config_.mode = mode;
|
||||||
|
matrixDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportAdapter::setLetterboxPosition(LetterboxPosition position) {
|
||||||
|
config_.letterboxPosition = position;
|
||||||
|
matrixDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportAdapter::setLetterboxColor(const Color &color) {
|
||||||
|
config_.letterboxColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 ViewportAdapter::screenToLogic(const Vec2 &screenPos) const {
|
||||||
|
return Vec2((screenPos.x - result_.offset.x) / result_.scaleX,
|
||||||
|
(screenPos.y - result_.offset.y) / result_.scaleY);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 ViewportAdapter::logicToScreen(const Vec2 &logicPos) const {
|
||||||
|
return Vec2(logicPos.x * result_.scaleX + result_.offset.x,
|
||||||
|
logicPos.y * result_.scaleY + result_.offset.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 ViewportAdapter::screenToLogic(float x, float y) const {
|
||||||
|
return screenToLogic(Vec2(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 ViewportAdapter::logicToScreen(float x, float y) const {
|
||||||
|
return logicToScreen(Vec2(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 ViewportAdapter::getViewportMatrix() 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_;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 ViewportAdapter::getInverseViewportMatrix() const {
|
||||||
|
if (matrixDirty_) {
|
||||||
|
getViewportMatrix();
|
||||||
|
}
|
||||||
|
inverseViewportMatrix_ = glm::inverse(viewportMatrix_);
|
||||||
|
return inverseViewportMatrix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ViewportAdapter::isInViewport(const Vec2 &screenPos) const {
|
||||||
|
return result_.viewport.containsPoint(screenPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include <extra2d/event/input_codes.h>
|
#include <extra2d/event/input_codes.h>
|
||||||
|
#include <extra2d/graphics/viewport_adapter.h>
|
||||||
#include <extra2d/platform/input.h>
|
#include <extra2d/platform/input.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
|
@ -9,7 +10,8 @@ Input::Input()
|
||||||
leftStickX_(0.0f), leftStickY_(0.0f),
|
leftStickX_(0.0f), leftStickY_(0.0f),
|
||||||
rightStickX_(0.0f), rightStickY_(0.0f),
|
rightStickX_(0.0f), rightStickY_(0.0f),
|
||||||
mouseScroll_(0.0f), prevMouseScroll_(0.0f),
|
mouseScroll_(0.0f), prevMouseScroll_(0.0f),
|
||||||
touching_(false), prevTouching_(false), touchCount_(0) {
|
touching_(false), prevTouching_(false), touchCount_(0),
|
||||||
|
viewportAdapter_(nullptr) {
|
||||||
|
|
||||||
// 初始化所有状态数组
|
// 初始化所有状态数组
|
||||||
keysDown_.fill(false);
|
keysDown_.fill(false);
|
||||||
|
|
@ -446,4 +448,39 @@ bool Input::isAnyMouseDown() const {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 视口适配器
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void Input::setViewportAdapter(ViewportAdapter* adapter) {
|
||||||
|
viewportAdapter_ = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 Input::getMousePositionLogic() const {
|
||||||
|
Vec2 screenPos = getMousePosition();
|
||||||
|
if (viewportAdapter_) {
|
||||||
|
return viewportAdapter_->screenToLogic(screenPos);
|
||||||
|
}
|
||||||
|
return screenPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 Input::getTouchPositionLogic() const {
|
||||||
|
Vec2 screenPos = getTouchPosition();
|
||||||
|
if (viewportAdapter_) {
|
||||||
|
return viewportAdapter_->screenToLogic(screenPos);
|
||||||
|
}
|
||||||
|
return screenPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 Input::getMouseDeltaLogic() const {
|
||||||
|
Vec2 delta = getMouseDelta();
|
||||||
|
if (viewportAdapter_) {
|
||||||
|
float scale = viewportAdapter_->getUniformScale();
|
||||||
|
if (scale > 0.0f) {
|
||||||
|
return delta / scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue