diff --git a/cpp/functions/core/orbit_camera.cpp b/cpp/functions/core/orbit_camera.cpp new file mode 100644 index 00000000..161e99a6 --- /dev/null +++ b/cpp/functions/core/orbit_camera.cpp @@ -0,0 +1,92 @@ +#include "core/orbit_camera.h" +#include "imgui.h" + +#include + +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 diff --git a/cpp/functions/core/orbit_camera.h b/cpp/functions/core/orbit_camera.h new file mode 100644 index 00000000..a7bb55a8 --- /dev/null +++ b/cpp/functions/core/orbit_camera.h @@ -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 diff --git a/cpp/functions/core/orbit_camera.md b/cpp/functions/core/orbit_camera.md new file mode 100644 index 00000000..c7f59aa0 --- /dev/null +++ b/cpp/functions/core/orbit_camera.md @@ -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 ``. + +## 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.