Skip to main content

siglus_scene_vm/
render_math.rs

1use crate::layer::Sprite;
2
3#[derive(Debug, Clone, Copy)]
4pub struct ProjectedPoint {
5    pub x: f32,
6    pub y: f32,
7    pub depth: f32,
8}
9
10#[derive(Debug, Clone, Copy)]
11struct Vec3 {
12    x: f32,
13    y: f32,
14    z: f32,
15}
16
17impl Vec3 {
18    fn new(x: f32, y: f32, z: f32) -> Self {
19        Self { x, y, z }
20    }
21
22    fn sub(self, rhs: Self) -> Self {
23        Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
24    }
25
26    fn add(self, rhs: Self) -> Self {
27        Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
28    }
29
30    fn dot(self, rhs: Self) -> f32 {
31        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
32    }
33
34    fn cross(self, rhs: Self) -> Self {
35        Self::new(
36            self.y * rhs.z - self.z * rhs.y,
37            self.z * rhs.x - self.x * rhs.z,
38            self.x * rhs.y - self.y * rhs.x,
39        )
40    }
41
42    fn normalize(self) -> Self {
43        let len2 = self.dot(self);
44        if len2 <= f32::EPSILON {
45            return Self::new(0.0, 0.0, 0.0);
46        }
47        let inv = len2.sqrt().recip();
48        Self::new(self.x * inv, self.y * inv, self.z * inv)
49    }
50}
51
52fn rotate_x(v: Vec3, angle: f32) -> Vec3 {
53    let (s, c) = angle.sin_cos();
54    Vec3::new(v.x, v.y * c - v.z * s, v.y * s + v.z * c)
55}
56
57fn rotate_y(v: Vec3, angle: f32) -> Vec3 {
58    let (s, c) = angle.sin_cos();
59    Vec3::new(v.x * c + v.z * s, v.y, -v.x * s + v.z * c)
60}
61
62fn rotate_z(v: Vec3, angle: f32) -> Vec3 {
63    let (s, c) = angle.sin_cos();
64    Vec3::new(v.x * c - v.y * s, v.x * s + v.y * c, v.z)
65}
66
67fn uses_3d(sprite: &Sprite) -> bool {
68    sprite.billboard
69        || sprite.camera_enabled
70        || sprite.z.abs() > f32::EPSILON
71        || sprite.pivot_z.abs() > f32::EPSILON
72        || (sprite.scale_z - 1.0).abs() > 1e-6
73        || sprite.rotate_x.abs() > f32::EPSILON
74        || sprite.rotate_y.abs() > f32::EPSILON
75}
76
77fn transform_local_point(sprite: &Sprite, px: f32, py: f32, dst_x: f32, dst_y: f32) -> Vec3 {
78    let (anchor_x, anchor_y, mut p) = if sprite.object_anchor {
79        (
80            dst_x,
81            dst_y,
82            Vec3::new(
83                px - sprite.texture_center_x - sprite.pivot_x,
84                py - sprite.texture_center_y - sprite.pivot_y,
85                -sprite.pivot_z,
86            ),
87        )
88    } else {
89        (
90            dst_x + sprite.pivot_x,
91            dst_y + sprite.pivot_y,
92            Vec3::new(px - sprite.pivot_x, py - sprite.pivot_y, -sprite.pivot_z),
93        )
94    };
95    p.x *= sprite.scale_x;
96    p.y *= sprite.scale_y;
97    p.z *= sprite.scale_z;
98    p = rotate_x(p, sprite.rotate_x);
99    p = rotate_y(p, sprite.rotate_y);
100    p = rotate_z(p, sprite.rotate);
101    p.add(Vec3::new(anchor_x, anchor_y, sprite.z + sprite.pivot_z))
102}
103
104fn camera_basis(sprite: &Sprite) -> (Vec3, Vec3, Vec3, Vec3) {
105    let eye = Vec3::new(
106        sprite.camera_eye[0],
107        sprite.camera_eye[1],
108        sprite.camera_eye[2],
109    );
110    let target = Vec3::new(
111        sprite.camera_target[0],
112        sprite.camera_target[1],
113        sprite.camera_target[2],
114    );
115    let up = Vec3::new(
116        sprite.camera_up[0],
117        sprite.camera_up[1],
118        sprite.camera_up[2],
119    );
120    let forward = target.sub(eye).normalize();
121    let right = up.cross(forward).normalize();
122    let up2 = forward.cross(right).normalize();
123    (eye, forward, right, up2)
124}
125
126fn transform_billboard_point(sprite: &Sprite, px: f32, py: f32, dst_x: f32, dst_y: f32) -> Vec3 {
127    let (_, _, right, up) = camera_basis(sprite);
128    let lx = (px - sprite.pivot_x) * sprite.scale_x;
129    let ly = (py - sprite.pivot_y) * sprite.scale_y;
130    let (s, c) = sprite.rotate.sin_cos();
131    let rx = lx * c - ly * s;
132    let ry = lx * s + ly * c;
133    let anchor = Vec3::new(
134        dst_x + sprite.pivot_x,
135        dst_y + sprite.pivot_y,
136        sprite.z + sprite.pivot_z,
137    );
138    anchor.add(Vec3::new(
139        right.x * rx + up.x * ry,
140        right.y * rx + up.y * ry,
141        right.z * rx + up.z * ry,
142    ))
143}
144
145fn project_point(sprite: &Sprite, p: Vec3, win_w: f32, win_h: f32) -> Option<ProjectedPoint> {
146    if !sprite.camera_enabled {
147        let depth = (0.5 - p.z / 100000.0).clamp(0.0, 1.0);
148        return Some(ProjectedPoint {
149            x: p.x,
150            y: p.y,
151            depth,
152        });
153    }
154
155    let (eye, zaxis, xaxis, yaxis) = camera_basis(sprite);
156
157    let rel = p.sub(eye);
158    let cx = rel.dot(xaxis);
159    let cy = rel.dot(yaxis);
160    let cz = rel.dot(zaxis);
161    if cz <= 1e-3 {
162        return None;
163    }
164
165    let aspect = if win_h.abs() > f32::EPSILON {
166        win_w / win_h
167    } else {
168        1.0
169    };
170    let hfov = sprite
171        .camera_view_angle_deg
172        .to_radians()
173        .clamp(1e-3, std::f32::consts::PI - 1e-3);
174    let tan_half_h = (hfov * 0.5).tan().max(1e-3);
175    let tan_half_v = (tan_half_h / aspect.max(1e-3)).max(1e-3);
176
177    let x_ndc = cx / (cz * tan_half_h);
178    let y_ndc = cy / (cz * tan_half_v);
179
180    let sx = (x_ndc + 1.0) * 0.5 * win_w;
181    let sy = (1.0 - y_ndc) * 0.5 * win_h;
182    let depth = ((cz - 1.0) / 20000.0).clamp(0.0, 1.0);
183    Some(ProjectedPoint {
184        x: sx,
185        y: sy,
186        depth,
187    })
188}
189
190fn signed_area(a: (f32, f32), b: (f32, f32), c: (f32, f32)) -> f32 {
191    (b.0 - a.0) * (c.1 - a.1) - (b.1 - a.1) * (c.0 - a.0)
192}
193
194pub fn sprite_quad_points(
195    sprite: &Sprite,
196    dst_x: f32,
197    dst_y: f32,
198    dst_w: f32,
199    dst_h: f32,
200    win_w: f32,
201    win_h: f32,
202) -> Option<[ProjectedPoint; 4]> {
203    if !uses_3d(sprite) {
204        let p0 = transform_local_point(sprite, 0.0, 0.0, dst_x, dst_y);
205        let p1 = transform_local_point(sprite, dst_w, 0.0, dst_x, dst_y);
206        let p2 = transform_local_point(sprite, dst_w, dst_h, dst_x, dst_y);
207        let p3 = transform_local_point(sprite, 0.0, dst_h, dst_x, dst_y);
208        return Some([
209            ProjectedPoint {
210                x: p0.x,
211                y: p0.y,
212                depth: 0.0,
213            },
214            ProjectedPoint {
215                x: p1.x,
216                y: p1.y,
217                depth: 0.0,
218            },
219            ProjectedPoint {
220                x: p2.x,
221                y: p2.y,
222                depth: 0.0,
223            },
224            ProjectedPoint {
225                x: p3.x,
226                y: p3.y,
227                depth: 0.0,
228            },
229        ]);
230    }
231
232    let xf = if sprite.billboard {
233        transform_billboard_point as fn(&Sprite, f32, f32, f32, f32) -> Vec3
234    } else {
235        transform_local_point as fn(&Sprite, f32, f32, f32, f32) -> Vec3
236    };
237
238    let p0 = project_point(sprite, xf(sprite, 0.0, 0.0, dst_x, dst_y), win_w, win_h)?;
239    let p1 = project_point(sprite, xf(sprite, dst_w, 0.0, dst_x, dst_y), win_w, win_h)?;
240    let p2 = project_point(sprite, xf(sprite, dst_w, dst_h, dst_x, dst_y), win_w, win_h)?;
241    let p3 = project_point(sprite, xf(sprite, 0.0, dst_h, dst_x, dst_y), win_w, win_h)?;
242
243    if sprite.culling && signed_area((p0.x, p0.y), (p1.x, p1.y), (p2.x, p2.y)) <= 0.0 {
244        return None;
245    }
246
247    Some([p0, p1, p2, p3])
248}
249
250pub fn project_model_point(
251    sprite: &Sprite,
252    local: [f32; 3],
253    anchor_x: f32,
254    anchor_y: f32,
255    win_w: f32,
256    win_h: f32,
257) -> Option<ProjectedPoint> {
258    let mut p = Vec3::new(
259        local[0] - sprite.pivot_x,
260        local[1] - sprite.pivot_y,
261        local[2] - sprite.pivot_z,
262    );
263    p.x *= sprite.scale_x;
264    p.y *= sprite.scale_y;
265    p.z *= sprite.scale_z;
266    p = rotate_x(p, sprite.rotate_x);
267    p = rotate_y(p, sprite.rotate_y);
268    p = rotate_z(p, sprite.rotate);
269    p = p.add(Vec3::new(
270        anchor_x + sprite.pivot_x,
271        anchor_y + sprite.pivot_y,
272        sprite.z + sprite.pivot_z,
273    ));
274    project_point(sprite, p, win_w, win_h)
275}