feat(camera3d): afig Vec3 i Camera3D amb projecció perspectiva en CPU

This commit is contained in:
2026-05-22 08:04:45 +02:00
parent 51797e0ea7
commit 86708e0ed5
3 changed files with 260 additions and 30 deletions
+90
View File
@@ -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
+60
View File
@@ -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