Skip to main content

ling/gfx/
camera.rs

1// src/gfx/camera.rs — 3-D camera: Y-then-X rotation + perspective projection.
2//
3// The camera is stored in GfxState and used by the 3-D draw builtins
4// (`วาดสามเหลี่ยม3มิติ`, `วาดเส้น3มิติ`).  Ling programs call `set_camera`
5// once per frame after computing their trig values.
6
7#[derive(Debug, Clone)]
8pub struct Camera3D {
9    /// Precomputed cos/sin of the Y-axis rotation angle.
10    pub cry: f32,
11    pub sry: f32,
12    /// Precomputed cos/sin of the X-axis rotation angle.
13    pub crx: f32,
14    pub srx: f32,
15    /// Screen-centre in pixels (set automatically when the window opens).
16    pub cx: f32,
17    pub cy: f32,
18    /// Focal length in pixels — controls field of view.
19    pub focal: f32,
20    /// Z offset added before the perspective divide (keeps objects in front of
21    /// the camera; typical value 4–6).
22    pub zdist: f32,
23    /// World-space camera position — subtracted from every point before rotation.
24    /// Move the camera with set_camera_pos / move_camera.
25    pub tx: f32,
26    pub ty: f32,
27    pub tz: f32,
28}
29
30impl Default for Camera3D {
31    fn default() -> Self {
32        Self {
33            cry: 1.0,
34            sry: 0.0,
35            crx: 1.0,
36            srx: 0.0,
37            cx: 960.0,
38            cy: 540.0,
39            focal: 1080.0,
40            zdist: 5.0,
41            tx: 0.0,
42            ty: 0.0,
43            tz: 0.0,
44        }
45    }
46}
47
48impl Camera3D {
49    /// Camera-space depth only — cheaper than a full project() when you only
50    /// need to test whether a point is in front of the camera.
51    #[inline]
52    pub fn depth(&self, wx: f32, wy: f32, wz: f32) -> f32 {
53        let wx = wx - self.tx;
54        let wy = wy - self.ty;
55        let wz = wz - self.tz;
56        let rz1 = wx * self.sry + wz * self.cry;
57        wy * self.srx + rz1 * self.crx
58    }
59
60    /// Project a world-space point to (screen_x, screen_y, camera_depth).
61    /// Pipeline: translate → Y-rotation → X-rotation → perspective divide.
62    #[inline]
63    pub fn project(&self, wx: f32, wy: f32, wz: f32) -> (f32, f32, f32) {
64        let wx = wx - self.tx;
65        let wy = wy - self.ty;
66        let wz = wz - self.tz;
67        // — Y rotation —
68        let rx = wx * self.cry - wz * self.sry;
69        let rz1 = wx * self.sry + wz * self.cry;
70        // — X rotation —
71        let ry = wy * self.crx - rz1 * self.srx;
72        let rz = wy * self.srx + rz1 * self.crx;
73        // — Perspective —
74        let d = rz + self.zdist;
75        let sx = self.cx + self.focal * rx / d;
76        let sy = self.cy + self.focal * ry / d;
77        (sx, sy, rz)
78    }
79}