feat(core): orbit_camera — camara orbital pura (matrices view/proj + drag)

State minimal (azimuth, elevation, distance, fov, aspect, near/far).
orbit_camera_matrices: lookAt+perspective row-major float[16] (subir a
GL con transpose=GL_TRUE). orbit_camera_handle_drag: dx→azimuth,
dy→elevation (clamp ±π/2-eps), wheel→distance (clamp >0.1). Sin glm,
solo <cmath>.

issue 0029
This commit is contained in:
2026-04-25 21:51:15 +02:00
parent 44e189c5cc
commit 3b662ac4c3
3 changed files with 200 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
#include "core/orbit_camera.h"
#include "imgui.h"
#include <cmath>
namespace fn::core {
namespace {
constexpr float kPi = 3.14159265358979323846f;
// row-major identity
void mat4_identity(float m[16]) {
for (int i = 0; i < 16; ++i) m[i] = 0.0f;
m[0] = m[5] = m[10] = m[15] = 1.0f;
}
// row-major: m[row*4 + col]
void make_lookat(float view[16],
float ex, float ey, float ez,
float cx, float cy, float cz,
float ux, float uy, float uz) {
// forward = normalize(center - eye)
float fx = cx - ex, fy = cy - ey, fz = cz - ez;
float fl = std::sqrt(fx*fx + fy*fy + fz*fz);
if (fl > 0) { fx /= fl; fy /= fl; fz /= fl; }
// side = normalize(cross(forward, up))
float sx = fy*uz - fz*uy;
float sy = fz*ux - fx*uz;
float sz = fx*uy - fy*ux;
float sl = std::sqrt(sx*sx + sy*sy + sz*sz);
if (sl > 0) { sx /= sl; sy /= sl; sz /= sl; }
// up' = cross(side, forward)
float ux2 = sy*fz - sz*fy;
float uy2 = sz*fx - sx*fz;
float uz2 = sx*fy - sy*fx;
mat4_identity(view);
view[0] = sx; view[1] = sy; view[2] = sz; view[3] = -(sx*ex + sy*ey + sz*ez);
view[4] = ux2; view[5] = uy2; view[6] = uz2; view[7] = -(ux2*ex + uy2*ey + uz2*ez);
view[8] = -fx; view[9] = -fy; view[10] = -fz; view[11] = (fx*ex + fy*ey + fz*ez);
view[12] = 0; view[13] = 0; view[14] = 0; view[15] = 1;
}
void make_perspective(float proj[16], float fov_deg, float aspect, float n, float f) {
float fov_rad = fov_deg * kPi / 180.0f;
float t = 1.0f / std::tan(fov_rad * 0.5f);
for (int i = 0; i < 16; ++i) proj[i] = 0.0f;
proj[0] = t / aspect;
proj[5] = t;
proj[10] = (f + n) / (n - f);
proj[11] = (2.0f * f * n) / (n - f);
proj[14] = -1.0f;
proj[15] = 0.0f;
}
} // namespace
CameraMatrices orbit_camera_matrices(const OrbitCamera& cam) {
CameraMatrices m{};
// Convert spherical (azimuth, elevation, distance) -> cartesian eye position.
// azimuth rotates around Y; elevation tilts up/down.
float ce = std::cos(cam.elevation);
float se = std::sin(cam.elevation);
float ca = std::cos(cam.azimuth);
float sa = std::sin(cam.azimuth);
float ex = cam.distance * ce * sa;
float ey = cam.distance * se;
float ez = cam.distance * ce * ca;
make_lookat(m.view, ex, ey, ez, 0, 0, 0, 0, 1, 0);
make_perspective(m.proj, cam.fov, cam.aspect, cam.near_plane, cam.far_plane);
return m;
}
void orbit_camera_handle_drag(OrbitCamera& cam, ImVec2 drag_delta, float wheel) {
constexpr float kDragSens = 0.01f;
cam.azimuth += drag_delta.x * kDragSens;
cam.elevation += drag_delta.y * kDragSens;
// clamp elevation to ±π/2 - eps to avoid gimbal flip
constexpr float kElevMax = kPi * 0.5f - 1e-3f;
if (cam.elevation > kElevMax) cam.elevation = kElevMax;
if (cam.elevation < -kElevMax) cam.elevation = -kElevMax;
if (wheel != 0.0f) {
cam.distance *= (1.0f - wheel * 0.1f);
if (cam.distance < 0.1f) cam.distance = 0.1f;
}
}
} // namespace fn::core
+40
View File
@@ -0,0 +1,40 @@
#pragma once
// orbit_camera — camara orbital pura. Estado minimo (azimuth, elevation,
// distance, fov, aspect, near/far) y dos helpers: matrices view/proj y
// handle_drag para integrar drag/wheel del mouse.
//
// Las matrices se devuelven como float[16] row-major, pero al subirlas a
// OpenGL con glUniformMatrix4fv hay que pasar transpose=GL_TRUE (porque
// GL espera column-major). Ver mesh_viewer.cpp para el patron.
struct ImVec2; // fwd-decl, definido por imgui.h
namespace fn::core {
struct OrbitCamera {
float azimuth = 0.7f; // rad, rotacion en plano XZ
float elevation = 0.4f; // rad, rotacion en plano YZ, clamp ±π/2-eps
float distance = 3.0f; // distancia al origen (target=0,0,0)
float fov = 45.0f; // grados, vertical FOV
float aspect = 1.0f; // width/height del viewport
float near_plane = 0.05f;
float far_plane = 100.0f;
};
struct CameraMatrices {
float view[16]; // row-major
float proj[16]; // row-major
};
// Calcula view (lookAt eye→origin, up=Y) y proj (perspective). Pure.
CameraMatrices orbit_camera_matrices(const OrbitCamera& cam);
// Aplica un drag de mouse (dx,dy) y rueda (wheel) sobre la camara.
// dx → azimuth (sensibilidad 0.01 rad/px)
// dy → elevation (clamp a ±π/2 - 1e-3)
// wheel → distance *= (1 - wheel*0.1), clamp >0.1
// Pure (sin side effects mas alla de mutar la struct in-place).
void orbit_camera_handle_drag(OrbitCamera& cam, ImVec2 drag_delta, float wheel);
} // namespace fn::core
+68
View File
@@ -0,0 +1,68 @@
---
name: orbit_camera
kind: function
lang: cpp
domain: core
version: "1.0.0"
purity: pure
signature: "CameraMatrices orbit_camera_matrices(const OrbitCamera&); void orbit_camera_handle_drag(OrbitCamera&, ImVec2 drag_delta, float wheel)"
description: "Camara orbital pura — estado minimal (azimuth, elevation, distance, fov, aspect, near/far). Helpers: matrices view/proj (row-major float[16]) y handle_drag para integrar drag/wheel."
tags: [camera, orbit, math, matrix, lookat, perspective, 3d]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [imgui]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/orbit_camera.cpp"
framework: imgui
params:
- name: cam
desc: "OrbitCamera in/out: azimuth (rad), elevation (rad clamped ±π/2-eps), distance, fov (deg), aspect, near/far"
- name: drag_delta
desc: "ImVec2 desplazamiento del mouse desde la ultima frame (px). dx→azimuth, dy→elevation con sensibilidad 0.01 rad/px"
- name: wheel
desc: "Float scroll wheel (Imgui::GetIO().MouseWheel). distance *= (1 - wheel*0.1), clamp >0.1"
output: "orbit_camera_matrices: CameraMatrices con view (lookAt eye→origin, up=Y) y proj (perspective). Ambas row-major; al pasar a glUniformMatrix4fv usar transpose=GL_TRUE. orbit_camera_handle_drag: muta cam in-place, sin allocacion."
---
# orbit_camera
Camara orbital trivial para inspeccionar geometria 3D. Mantiene un objetivo fijo en el origen y rota la posicion del ojo en una esfera de radio `distance`.
## Convenciones
- **Azimuth = 0, elevation = 0** → ojo en `+Z`, mirando hacia `-Z`.
- **Up vector** = `+Y`. Right-handed.
- **Matrices row-major**: al subir a OpenGL, usar `glUniformMatrix4fv(loc, 1, GL_TRUE, &m.view[0])`.
- Sin glm: ~50 LOC, sin dependencias mas alla de `<cmath>`.
## Uso tipico
```cpp
fn::core::OrbitCamera cam;
cam.aspect = (float)w / (float)h;
// En el render loop:
auto m = fn::core::orbit_camera_matrices(cam);
glUniformMatrix4fv(loc_view, 1, GL_TRUE, m.view);
glUniformMatrix4fv(loc_proj, 1, GL_TRUE, m.proj);
// Si el panel esta activo:
if (ImGui::IsItemActive()) {
fn::core::orbit_camera_handle_drag(
cam,
ImGui::GetIO().MouseDelta,
ImGui::GetIO().MouseWheel
);
}
```
## Notas
- Elevation se clampea a `±π/2 - 1e-3` para evitar flip de la up vector cuando se mira desde polos.
- Distance minima = 0.1 (impide cruzar el origen con el wheel).
- Si necesitas un target distinto al origen, suma `target` a `eye` y pasa `target` a la lookAt — el helper actual mira fijo a `(0,0,0)` por simplicidad.