feat(camera3d): afig Vec3 i Camera3D amb projecció perspectiva en CPU
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// camera3d.cpp - Implementació de la càmera 3D amb projecció en CPU
|
||||
// © 2026 JailDesigner
|
||||
|
||||
#include "core/graphics/camera3d.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace Graphics {
|
||||
|
||||
Camera3D::Camera3D(const Vec3& position, const Vec3& target, const Vec3& up_world, float fov_y_rad, float viewport_w, float viewport_h, float near_plane, float far_plane)
|
||||
: position_(position),
|
||||
target_(target),
|
||||
up_world_(up_world),
|
||||
fov_y_rad_(fov_y_rad),
|
||||
viewport_w_(viewport_w),
|
||||
viewport_h_(viewport_h),
|
||||
near_(near_plane),
|
||||
far_(far_plane) {
|
||||
recomputeBasis();
|
||||
recomputeFocal();
|
||||
}
|
||||
|
||||
void Camera3D::setPosition(const Vec3& p) {
|
||||
position_ = p;
|
||||
recomputeBasis();
|
||||
}
|
||||
|
||||
void Camera3D::setTarget(const Vec3& t) {
|
||||
target_ = t;
|
||||
recomputeBasis();
|
||||
}
|
||||
|
||||
void Camera3D::setUpWorld(const Vec3& u) {
|
||||
up_world_ = u;
|
||||
recomputeBasis();
|
||||
}
|
||||
|
||||
void Camera3D::setViewport(float w, float h) {
|
||||
viewport_w_ = w;
|
||||
viewport_h_ = h;
|
||||
recomputeFocal();
|
||||
}
|
||||
|
||||
void Camera3D::setFovY(float fov_y_rad) {
|
||||
fov_y_rad_ = fov_y_rad;
|
||||
recomputeFocal();
|
||||
}
|
||||
|
||||
void Camera3D::recomputeBasis() {
|
||||
// Forward = del position cap al target.
|
||||
forward_ = (target_ - position_).normalized();
|
||||
// Right = forward × up_world. Cau a (1,0,0) si forward ≈ up_world.
|
||||
right_ = forward_.cross(up_world_).normalized();
|
||||
// Up real ortogonal = right × forward.
|
||||
up_ = right_.cross(forward_).normalized();
|
||||
}
|
||||
|
||||
void Camera3D::recomputeFocal() {
|
||||
// Focal length en píxels: (viewport_height / 2) / tan(fov_y / 2).
|
||||
// Assumeix píxels quadrats (focal_x == focal_y).
|
||||
const float HALF_FOV = fov_y_rad_ * 0.5F;
|
||||
const float TAN_HALF = std::tan(HALF_FOV);
|
||||
focal_ = (TAN_HALF > 0.0F) ? ((viewport_h_ * 0.5F) / TAN_HALF) : 0.0F;
|
||||
centre_x_ = viewport_w_ * 0.5F;
|
||||
centre_y_ = viewport_h_ * 0.5F;
|
||||
}
|
||||
|
||||
auto Camera3D::project(const Vec3& world) const -> std::optional<ProjectedPoint> {
|
||||
const Vec3 REL = world - position_;
|
||||
const float CX = REL.dot(right_);
|
||||
const float CY = REL.dot(up_);
|
||||
const float CZ = REL.dot(forward_);
|
||||
|
||||
if (CZ <= near_) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const float SCALE = focal_ / CZ;
|
||||
return ProjectedPoint{
|
||||
.screen = Vec2{
|
||||
.x = centre_x_ + (CX * SCALE),
|
||||
// Flip Y: en pantalla Y creix cap avall.
|
||||
.y = centre_y_ - (CY * SCALE),
|
||||
},
|
||||
.scale = SCALE,
|
||||
.depth = CZ,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Graphics
|
||||
@@ -0,0 +1,60 @@
|
||||
// camera3d.hpp - Càmera 3D amb projecció en perspectiva en CPU
|
||||
// © 2026 JailDesigner
|
||||
//
|
||||
// La càmera viu en l'espai mundial (X dreta, Y amunt, Z davant). El mètode
|
||||
// project() pren un Vec3 mundial i torna les coordenades 2D en píxels lògics
|
||||
// de pantalla, més el factor d'escala focal/depth (útil per renderShape).
|
||||
// Si el punt queda darrere del near plane, torna std::nullopt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "core/types.hpp"
|
||||
|
||||
namespace Graphics {
|
||||
|
||||
class Camera3D {
|
||||
public:
|
||||
struct ProjectedPoint {
|
||||
Vec2 screen; // Píxels lògics
|
||||
float scale; // focal / depth (escala visual a aquesta Z)
|
||||
float depth; // Profunditat en l'espai de càmera (cz)
|
||||
};
|
||||
|
||||
Camera3D(const Vec3& position, const Vec3& target, const Vec3& up_world, float fov_y_rad, float viewport_w, float viewport_h, float near_plane = 0.1F, float far_plane = 2000.0F);
|
||||
|
||||
void setPosition(const Vec3& p);
|
||||
void setTarget(const Vec3& t);
|
||||
void setUpWorld(const Vec3& u);
|
||||
void setViewport(float w, float h);
|
||||
void setFovY(float fov_y_rad);
|
||||
|
||||
[[nodiscard]] auto project(const Vec3& world) const -> std::optional<ProjectedPoint>;
|
||||
|
||||
[[nodiscard]] auto position() const -> const Vec3& { return position_; }
|
||||
[[nodiscard]] auto forward() const -> const Vec3& { return forward_; }
|
||||
[[nodiscard]] auto nearPlane() const -> float { return near_; }
|
||||
[[nodiscard]] auto farPlane() const -> float { return far_; }
|
||||
|
||||
private:
|
||||
void recomputeBasis();
|
||||
void recomputeFocal();
|
||||
|
||||
Vec3 position_{};
|
||||
Vec3 target_{};
|
||||
Vec3 up_world_{};
|
||||
Vec3 right_{.x = 1.0F, .y = 0.0F, .z = 0.0F};
|
||||
Vec3 up_{.x = 0.0F, .y = 1.0F, .z = 0.0F};
|
||||
Vec3 forward_{.x = 0.0F, .y = 0.0F, .z = 1.0F};
|
||||
float fov_y_rad_{0.0F};
|
||||
float viewport_w_{0.0F};
|
||||
float viewport_h_{0.0F};
|
||||
float near_{0.1F};
|
||||
float far_{2000.0F};
|
||||
float focal_{0.0F};
|
||||
float centre_x_{0.0F};
|
||||
float centre_y_{0.0F};
|
||||
};
|
||||
|
||||
} // namespace Graphics
|
||||
+110
-30
@@ -11,39 +11,39 @@
|
||||
// y aggregate initialization clásica:
|
||||
// Vec2{1.0F, 2.0F}
|
||||
struct Vec2 {
|
||||
float x{0.0F};
|
||||
float y{0.0F};
|
||||
float x{0.0F};
|
||||
float y{0.0F};
|
||||
|
||||
constexpr auto operator+=(const Vec2& o) -> Vec2& {
|
||||
x += o.x;
|
||||
y += o.y;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator-=(const Vec2& o) -> Vec2& {
|
||||
x -= o.x;
|
||||
y -= o.y;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator*=(float s) -> Vec2& {
|
||||
x *= s;
|
||||
y *= s;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator/=(float s) -> Vec2& {
|
||||
x /= s;
|
||||
y /= s;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator+=(const Vec2& o) -> Vec2& {
|
||||
x += o.x;
|
||||
y += o.y;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator-=(const Vec2& o) -> Vec2& {
|
||||
x -= o.x;
|
||||
y -= o.y;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator*=(float s) -> Vec2& {
|
||||
x *= s;
|
||||
y *= s;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator/=(float s) -> Vec2& {
|
||||
x /= s;
|
||||
y /= s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto lengthSquared() const -> float { return (x * x) + (y * y); }
|
||||
[[nodiscard]] auto length() const -> float { return std::sqrt(lengthSquared()); }
|
||||
[[nodiscard]] auto dot(const Vec2& o) const -> float { return (x * o.x) + (y * o.y); }
|
||||
[[nodiscard]] auto lengthSquared() const -> float { return (x * x) + (y * y); }
|
||||
[[nodiscard]] auto length() const -> float { return std::sqrt(lengthSquared()); }
|
||||
[[nodiscard]] auto dot(const Vec2& o) const -> float { return (x * o.x) + (y * o.y); }
|
||||
|
||||
// Devuelve el vector normalizado; si la magnitud es 0 devuelve {0,0}.
|
||||
[[nodiscard]] auto normalized() const -> Vec2 {
|
||||
const float L = length();
|
||||
return L > 0.0F ? Vec2{.x = x / L, .y = y / L} : Vec2{};
|
||||
}
|
||||
// Devuelve el vector normalizado; si la magnitud es 0 devuelve {0,0}.
|
||||
[[nodiscard]] auto normalized() const -> Vec2 {
|
||||
const float L = length();
|
||||
return L > 0.0F ? Vec2{.x = x / L, .y = y / L} : Vec2{};
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto operator+(Vec2 a, const Vec2& b) -> Vec2 {
|
||||
@@ -70,3 +70,83 @@ constexpr auto operator-(const Vec2& v) -> Vec2 { return {.x = -v.x, .y = -v.y};
|
||||
constexpr auto operator==(const Vec2& a, const Vec2& b) -> bool {
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
// Vector 3D cartesià. Mateix patró d'aggregate que Vec2 per suportar
|
||||
// designated initializers: Vec3{.x = 1.0F, .y = 2.0F, .z = 3.0F}.
|
||||
// Convenció de mà dreta: X dreta, Y amunt, Z davant càmera.
|
||||
struct Vec3 {
|
||||
float x{0.0F};
|
||||
float y{0.0F};
|
||||
float z{0.0F};
|
||||
|
||||
constexpr auto operator+=(const Vec3& o) -> Vec3& {
|
||||
x += o.x;
|
||||
y += o.y;
|
||||
z += o.z;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator-=(const Vec3& o) -> Vec3& {
|
||||
x -= o.x;
|
||||
y -= o.y;
|
||||
z -= o.z;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator*=(float s) -> Vec3& {
|
||||
x *= s;
|
||||
y *= s;
|
||||
z *= s;
|
||||
return *this;
|
||||
}
|
||||
constexpr auto operator/=(float s) -> Vec3& {
|
||||
x /= s;
|
||||
y /= s;
|
||||
z /= s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto lengthSquared() const -> float {
|
||||
return (x * x) + (y * y) + (z * z);
|
||||
}
|
||||
[[nodiscard]] auto length() const -> float { return std::sqrt(lengthSquared()); }
|
||||
[[nodiscard]] auto dot(const Vec3& o) const -> float {
|
||||
return (x * o.x) + (y * o.y) + (z * o.z);
|
||||
}
|
||||
[[nodiscard]] auto cross(const Vec3& o) const -> Vec3 {
|
||||
return Vec3{
|
||||
.x = (y * o.z) - (z * o.y),
|
||||
.y = (z * o.x) - (x * o.z),
|
||||
.z = (x * o.y) - (y * o.x),
|
||||
};
|
||||
}
|
||||
[[nodiscard]] auto normalized() const -> Vec3 {
|
||||
const float L = length();
|
||||
return L > 0.0F ? Vec3{.x = x / L, .y = y / L, .z = z / L} : Vec3{};
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto operator+(Vec3 a, const Vec3& b) -> Vec3 {
|
||||
a += b;
|
||||
return a;
|
||||
}
|
||||
constexpr auto operator-(Vec3 a, const Vec3& b) -> Vec3 {
|
||||
a -= b;
|
||||
return a;
|
||||
}
|
||||
constexpr auto operator*(Vec3 v, float s) -> Vec3 {
|
||||
v *= s;
|
||||
return v;
|
||||
}
|
||||
constexpr auto operator*(float s, Vec3 v) -> Vec3 {
|
||||
v *= s;
|
||||
return v;
|
||||
}
|
||||
constexpr auto operator/(Vec3 v, float s) -> Vec3 {
|
||||
v /= s;
|
||||
return v;
|
||||
}
|
||||
constexpr auto operator-(const Vec3& v) -> Vec3 {
|
||||
return {.x = -v.x, .y = -v.y, .z = -v.z};
|
||||
}
|
||||
constexpr auto operator==(const Vec3& a, const Vec3& b) -> bool {
|
||||
return a.x == b.x && a.y == b.y && a.z == b.z;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user