42c14fae59
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
120 lines
3.4 KiB
C++
120 lines
3.4 KiB
C++
#include "camera_2d.h"
|
|
|
|
#include <cmath>
|
|
|
|
namespace fn::cam {
|
|
|
|
using fn::math2d::Vec2;
|
|
using fn::math2d::Rect;
|
|
|
|
Vec2 world_to_screen(const Camera2D& c, Vec2 world) {
|
|
float dx = world.x - c.pos.x;
|
|
float dy = world.y - c.pos.y;
|
|
|
|
if (c.rotation != 0.0f) {
|
|
float cs = std::cos(-c.rotation);
|
|
float sn = std::sin(-c.rotation);
|
|
float rx = dx * cs - dy * sn;
|
|
float ry = dx * sn + dy * cs;
|
|
dx = rx;
|
|
dy = ry;
|
|
}
|
|
|
|
float sx = dx * c.zoom + (float)c.viewport_w * 0.5f;
|
|
float sy = dy * c.zoom + (float)c.viewport_h * 0.5f;
|
|
return {sx, sy};
|
|
}
|
|
|
|
Vec2 screen_to_world(const Camera2D& c, Vec2 screen) {
|
|
float dx = (screen.x - (float)c.viewport_w * 0.5f) / c.zoom;
|
|
float dy = (screen.y - (float)c.viewport_h * 0.5f) / c.zoom;
|
|
|
|
if (c.rotation != 0.0f) {
|
|
float cs = std::cos(c.rotation);
|
|
float sn = std::sin(c.rotation);
|
|
float rx = dx * cs - dy * sn;
|
|
float ry = dx * sn + dy * cs;
|
|
dx = rx;
|
|
dy = ry;
|
|
}
|
|
|
|
return {c.pos.x + dx, c.pos.y + dy};
|
|
}
|
|
|
|
Rect visible_world_rect(const Camera2D& c) {
|
|
// For rotated cameras, return the AABB that contains the rotated viewport.
|
|
float hw = (float)c.viewport_w * 0.5f / c.zoom;
|
|
float hh = (float)c.viewport_h * 0.5f / c.zoom;
|
|
|
|
if (c.rotation == 0.0f) {
|
|
return {c.pos.x - hw, c.pos.y - hh, hw * 2.0f, hh * 2.0f};
|
|
}
|
|
|
|
float cs = std::fabs(std::cos(c.rotation));
|
|
float sn = std::fabs(std::sin(c.rotation));
|
|
float ehw = hw * cs + hh * sn;
|
|
float ehh = hw * sn + hh * cs;
|
|
return {c.pos.x - ehw, c.pos.y - ehh, ehw * 2.0f, ehh * 2.0f};
|
|
}
|
|
|
|
void view_proj_matrix(const Camera2D& c, float out[16]) {
|
|
// Orthographic projection mapping a viewport-sized box around camera pos
|
|
// to clip space [-1, 1].
|
|
float hw = (float)c.viewport_w * 0.5f / c.zoom;
|
|
float hh = (float)c.viewport_h * 0.5f / c.zoom;
|
|
|
|
float l = c.pos.x - hw;
|
|
float r = c.pos.x + hw;
|
|
// Screen Y goes down, world Y can go either; we pick world-Y-up convention:
|
|
// top of screen = pos.y + hh, bottom = pos.y - hh.
|
|
float b = c.pos.y - hh;
|
|
float t = c.pos.y + hh;
|
|
|
|
float cs = std::cos(-c.rotation);
|
|
float sn = std::sin(-c.rotation);
|
|
|
|
// Build column-major:
|
|
// M = Ortho(l,r,b,t) * Rotate(-rotation around camera center)
|
|
// Compose by hand to avoid temporaries.
|
|
|
|
float ortho[16] = {
|
|
2.0f / (r - l), 0.0f, 0.0f, 0.0f,
|
|
0.0f, 2.0f / (t - b), 0.0f, 0.0f,
|
|
0.0f, 0.0f, -1.0f, 0.0f,
|
|
-(r + l) / (r - l), -(t + b) / (t - b), 0.0f, 1.0f,
|
|
};
|
|
|
|
if (c.rotation == 0.0f) {
|
|
for (int i = 0; i < 16; ++i) out[i] = ortho[i];
|
|
return;
|
|
}
|
|
|
|
// Rotation around camera pos in world space:
|
|
// T(pos) * R * T(-pos)
|
|
// We bake it as a column-major 4x4 then multiply: out = ortho * rot.
|
|
float px = c.pos.x;
|
|
float py = c.pos.y;
|
|
|
|
float rot[16] = {
|
|
cs, sn, 0.0f, 0.0f,
|
|
-sn, cs, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
px - cs * px + sn * py,
|
|
py - sn * px - cs * py,
|
|
0.0f, 1.0f,
|
|
};
|
|
|
|
// out = ortho * rot (column-major).
|
|
for (int col = 0; col < 4; ++col) {
|
|
for (int row = 0; row < 4; ++row) {
|
|
float sum = 0.0f;
|
|
for (int k = 0; k < 4; ++k) {
|
|
sum += ortho[k * 4 + row] * rot[col * 4 + k];
|
|
}
|
|
out[col * 4 + row] = sum;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace fn::cam
|