91 lines
2.7 KiB
C++
91 lines
2.7 KiB
C++
// 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
|