1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
pub mod face;
pub mod vec3d;
use crate::elements::{
    view::{
        utils::{self, points_to_pixels, Wrapping},
        ColChar, Modifier,
    },
    Line, PixelContainer, Polygon, Sprite, Vec2D, View,
};
pub use face::Face;
pub use vec3d::{SpatialAxis, Vec3D};

/// `DisplayMode` determines how the [`Viewport`] renders our 3D objects. This is the Gemini equivalent of Blender's Viewport Shading options
/// - [`DisplayMode::Debug`] does the same thing, but shows the vertices as the indices that represent them (this is useful when you are constructing a mesh)
/// - [`DisplayMode::Points`] only renders the object's vertices as single pixels with the [`ColChar`] chosen with the fill_char enum parameter
/// - [`DisplayMode::Wireframe`] renders the edges of the meshes, without filling in the shapes
/// - [`DisplayMode::Solid`] render the full faces of all the meshes. This is normally the final render
pub enum DisplayMode {
    Debug,
    Points { fill_char: ColChar },
    Wireframe,
    Solid,
}

/// The `Viewport` handles printing 3D objects to a 2D [`View`], and also acts as the scene's camera.
pub struct Viewport {
    pub offset: Vec3D,
    pub rotation: Vec3D,
    pub fov: f64,
    pub origin: Vec2D,
}

impl Viewport {
    pub fn new(offset: Vec3D, rotation: Vec3D, fov: f64, origin: Vec2D) -> Self {
        Self {
            offset,
            rotation,
            fov,
            origin,
        }
    }

    pub fn spatial_to_screen(&self, pos: Vec3D) -> Vec2D {
        let f = self.fov / -pos.z;
        let (sx, sy) = (-pos.x * f, pos.y * f);

        // adjust for non-square pixels
        let sx = (sx * 2.2).round() as isize;
        let sy = sy.round() as isize;

        self.origin + Vec2D::new(sx, sy)
    }

    pub fn blit_to<T: ViewElement3D>(
        &self,
        view: &mut View,
        objects: Vec<&T>,
        display_mode: DisplayMode,
    ) {
        match display_mode {
            DisplayMode::Debug => {
                for object in objects {
                    for (i, (screen_coordinates, _z)) in
                        object.vertices_on_screen(self).iter().enumerate()
                    {
                        let index_text = format!("{}", i);
                        view.blit(
                            &Sprite::new(*screen_coordinates, index_text.as_str(), Modifier::None),
                            Wrapping::Ignore,
                        );
                    }
                }
            }
            DisplayMode::Points { fill_char } => {
                for object in objects {
                    for (screen_coordinates, _z) in object.vertices_on_screen(self) {
                        view.plot(screen_coordinates, fill_char, Wrapping::Ignore);
                    }
                }
            }
            DisplayMode::Wireframe => {
                for object in objects {
                    let screen_vertices = object.vertices_on_screen(&self);

                    for face in (*object.get_faces()).into_iter() {
                        let mut pixel_container = PixelContainer::new();
                        for fi in 0..face.v_indexes.len() {
                            let (i0, i1) = (
                                face.v_indexes[fi],
                                face.v_indexes[(fi + 1) % face.v_indexes.len()],
                            );
                            pixel_container.append(&mut points_to_pixels(
                                Line::draw(screen_vertices[i0].0, screen_vertices[i1].0),
                                face.fill_char,
                            ));
                        }

                        view.blit(&pixel_container, Wrapping::Ignore)
                    }
                }
            }
            DisplayMode::Solid => {
                let mut screen_faces = vec![];

                for object in objects {
                    let screen_vertices = object.vertices_on_screen(&self);

                    for face in (*object.get_faces()).into_iter() {
                        let mut face_vertices = vec![];
                        for vi in &face.v_indexes {
                            face_vertices.push(screen_vertices[*vi]);
                        }
                        let vertices_only = face_vertices.iter().map(|k| k.0).collect();

                        // Backface culling
                        if !utils::is_clockwise(&vertices_only) {
                            continue;
                        }

                        let mut mean_z: f64 = 0.0;
                        for (_v, z) in &face_vertices {
                            mean_z += z;
                        }
                        mean_z /= face_vertices.len() as f64;

                        screen_faces.push((vertices_only, mean_z, face.fill_char));
                    }
                }

                screen_faces.sort_by_key(|k| (k.1 * -100.0).round() as isize);

                for face in screen_faces {
                    let polygon = Polygon::new(face.0, face.2);
                    view.blit(&polygon, Wrapping::Ignore)
                }
            }
        }
    }
}

pub trait ViewElement3D {
    /// This should return the object's rotation
    fn get_pos(&self) -> Vec3D;
    /// This should return the object's rotation
    fn get_rotation(&self) -> Vec3D;
    /// This should return all of the object's vertices
    fn get_vertices(&self) -> Vec<Vec3D>;
    /// This should return all of the object's `Face`s
    fn get_faces(&self) -> Vec<Face>;
    /// This should return a list of its vertices in their screen positions, paired with their distance to the screen
    fn vertices_on_screen(&self, viewport: &Viewport) -> Vec<(Vec2D, f64)>;
}