tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 614Markdown 274TOML 149RON 310606 Definiciones

⎇ main llimphi-3d/src/camera.rs Rust · 84 líneas · 3.1 KB Historia Quién tocó qué crudo

refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel ccab39f14 · Sergio ·

1//! Cámara 3D — produce la matriz `view_proj` que el shader aplica a cada
2//! vértice. Convención de mano derecha y profundidad `0..1` (la de wgpu/
3//! Vulkan/Metal/DX12, **no** la `-1..1` de OpenGL).
4
5use glam::{Mat4, Vec3};
6
7/// Cámara en perspectiva. `eye` mira a `target` con `up` como vertical.
8#[derive(Debug, Clone, Copy)]
9pub struct Camera3d {
10    /// Posición del ojo en mundo.
11    pub eye: Vec3,
12    /// Punto al que mira.
13    pub target: Vec3,
14    /// Vector "arriba" (normalmente `Vec3::Y`).
15    pub up: Vec3,
16    /// Campo de visión vertical, en radianes.
17    pub fovy_rad: f32,
18    /// Plano cercano (`> 0`).
19    pub znear: f32,
20    /// Plano lejano.
21    pub zfar: f32,
22}
23
24impl Default for Camera3d {
25    fn default() -> Self {
26        Self {
27            eye: Vec3::new(2.5, 2.0, 3.5),
28            target: Vec3::ZERO,
29            up: Vec3::Y,
30            fovy_rad: 60_f32.to_radians(),
31            znear: 0.1,
32            // Generoso: cubre mundos voxel de cientos de unidades. Importa desde
33            // que el pase de voxels escribe profundidad (`Scene3d`): un hit más
34            // allá de `zfar` se clamparía a 1.0 y fallaría el depth test. Float32
35            // de depth mantiene precisión de sobra en este rango para oclusión.
36            zfar: 5000.0,
37        }
38    }
39}
40
41impl Camera3d {
42    /// Cámara orbitando `target` a `dist`, con `yaw`/`pitch` en radianes.
43    /// `yaw` gira alrededor del eje Y; `pitch` sube/baja (clamp suave para no
44    /// cruzar los polos y degenerar el `up`).
45    pub fn orbit(target: Vec3, yaw: f32, pitch: f32, dist: f32) -> Self {
46        let lim = std::f32::consts::FRAC_PI_2 - 0.01;
47        let pitch = pitch.clamp(-lim, lim);
48        let (sy, cy) = yaw.sin_cos();
49        let (sp, cp) = pitch.sin_cos();
50        let offset = Vec3::new(cp * sy, sp, cp * cy) * dist;
51        Self {
52            eye: target + offset,
53            target,
54            ..Self::default()
55        }
56    }
57
58    /// Cámara **libre / primera persona**: parada en `eye`, mirando según
59    /// `yaw` (giro alrededor de Y) y `pitch` (cabeceo, clamped para no cruzar el
60    /// cenit). Complementa a [`orbit`](Self::orbit): `orbit` mira un punto desde
61    /// afuera (vista de paisaje), `fly` te pone *adentro* del mundo (vuelo / FPS).
62    /// `yaw=0` mira hacia `+Z`.
63    pub fn fly(eye: Vec3, yaw: f32, pitch: f32) -> Self {
64        let lim = std::f32::consts::FRAC_PI_2 - 0.01;
65        let pitch = pitch.clamp(-lim, lim);
66        let (sy, cy) = yaw.sin_cos();
67        let (sp, cp) = pitch.sin_cos();
68        let dir = Vec3::new(cp * sy, sp, cp * cy);
69        Self {
70            eye,
71            target: eye + dir,
72            ..Self::default()
73        }
74    }
75
76    /// Matriz `proj * view` lista para `mvp * vec4(pos, 1.0)` en el shader.
77    /// `aspect` = ancho/alto del viewport en pixels.
78    pub fn view_proj(&self, aspect: f32) -> Mat4 {
79        let view = Mat4::look_at_rh(self.eye, self.target, self.up);
80        // `perspective_rh` (no `_gl`): profundidad 0..1, la que espera wgpu.
81        let proj = Mat4::perspective_rh(self.fovy_rad, aspect.max(1e-4), self.znear, self.zfar);
82        proj * view
83    }
84}