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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user