diff --git a/source/core/graphics/camera3d.cpp b/source/core/graphics/camera3d.cpp new file mode 100644 index 0000000..9fe386c --- /dev/null +++ b/source/core/graphics/camera3d.cpp @@ -0,0 +1,90 @@ +// camera3d.cpp - Implementació de la càmera 3D amb projecció en CPU +// © 2026 JailDesigner + +#include "core/graphics/camera3d.hpp" + +#include + +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 { + 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 diff --git a/source/core/graphics/camera3d.hpp b/source/core/graphics/camera3d.hpp new file mode 100644 index 0000000..0cc41fd --- /dev/null +++ b/source/core/graphics/camera3d.hpp @@ -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 + +#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; + + [[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 diff --git a/source/core/types.hpp b/source/core/types.hpp index e6433c4..ed75a6e 100644 --- a/source/core/types.hpp +++ b/source/core/types.hpp @@ -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; +}