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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
//! Gemini's core elements module. This and the [`view`] module make up Gemini's core rendering pipeline.
//!
//! ## Quick Start
//! Let's get started with a simple program to demonstrate how Gemini works:
//! ```rust,no_run
//! use gemini_engine::elements::{Point, Vec2D, view::{View, ColChar, Wrapping}};
//! use gemini_engine::gameloop;
//!
//! const FPS: u32 = 30;
//!
//! fn main() {
//!     let mut view = View::new(40, 8, ColChar::BACKGROUND);
//!     let mut point = Point::new(Vec2D::new(10,5), ColChar::SOLID);
//!
//!     loop {
//!         view.clear();
//!
//!         point.pos.x += 1;
//!
//!         view.blit(&point, Wrapping::Wrap);
//!         view.display_render().unwrap();
//!
//!         gameloop::sleep_fps(FPS, None);
//!     }
//! }
//! ```
//! Ok, let's go over this and see what's going on. We start by creating a [`View`] and [`Point`]. the [`View`] takes two numbers for the width and height, as well as a [`ColChar`]. The [`Point`] takes a [`Vec2D`] and a [`ColChar`].
//!
//! We use [`ColChar`] to say exactly what each pixel should look like and what colour it should be. Here we used the built in `ColChar::BACKGROUND` and `ColChar::SOLID` to keep the code simple. You can read more in the [`ColChar`] documentation.
//!
//! At its heart, [`Vec2D`] is just a pair of `isize` integers for defining things such as position, size and movement. We used it here to define the [`Point`]'s starting position, before the game loop.
//!
//! Now that we've got initialisation out of the way, let's get on to the juicy part: the main loop. In Gemini the main loop always goes as follows:
//! 1. Clear the [`View`]
//! 2. Work through any logic you might have (moving things around, taking inputs etc.)
//! 3. Blit all the [`ViewElement`]s to the screen
//! 4. print the result of `View.display_render`
//! 5. Sleep
//!
//! In our case, we want to move our [`Point`] one unit to the right every frame, so we increase its value by one here. Next we blit the [`Point`] to the [`View`] (adding it to the [`View`]'s internal canvas) and render. Rendering will display the view in the terminal (make sure your terminal is large enough to fit the whole image!). The last line of our code sleeps for `1/FPS` seconds. We pass None in place of what would normally be a Some(Duration) type, displaying the amount of time it took to blit and render everything so that [`gameloop::sleep_fps`](crate::gameloop::sleep_fps) can accomodate for the time taken to render. Since this example program is quite simple, we've just passed None. You can see how best to write a gameloop in the [`gameloop`](crate::gameloop) documentation.
//!
//! There you have it! You've written your first program with Gemini! As of me writing this now it's still very much a work in progress, so any feedback or issue requests would be appreciated :)

pub mod view;
use view::utils::{self, BlitCache};
use view::{ColChar, Modifier, ViewElement};
pub use view::{Point, Vec2D, View};

/// A `PixelContainer` only has a [`pixels`](PixelContainer::pixels) property, which gets returned directly to the View during blit
pub struct PixelContainer {
    pub pixels: Vec<Point>,
}

impl PixelContainer {
    pub fn new() -> Self {
        Self { pixels: vec![] }
    }

    pub fn push(&mut self, pixel: Point) {
        self.pixels.push(pixel);
    }

    pub fn append(&mut self, pixels: &mut Vec<Point>) {
        self.pixels.append(pixels);
    }

    /// Blit a [`ViewElement`] to the PixelContainer.
    pub fn blit<T: ViewElement>(&mut self, element: &T) {
        let mut active_pixels = element.active_pixels();

        self.append(&mut active_pixels);
    }
}

impl From<Vec<(Vec2D, ColChar)>> for PixelContainer {
    fn from(pixels: Vec<(Vec2D, ColChar)>) -> Self {
        Self {
            pixels: pixels.iter().map(|x| Point::from(*x)).collect(),
        }
    }
}

impl ViewElement for PixelContainer {
    fn active_pixels(&self) -> Vec<Point> {
        self.pixels.clone()
    }
}

/// The `Line` takes two [`Vec2D`]s and returns a line between those vertices when blit to a [`View`]
pub struct Line {
    pub pos0: Vec2D,
    pub pos1: Vec2D,
    pub fill_char: ColChar,
    cache: BlitCache<Vec2D>,
}

impl Line {
    pub fn new(pos0: Vec2D, pos1: Vec2D, fill_char: ColChar) -> Self {
        Line {
            pos0,
            pos1,
            fill_char,
            cache: BlitCache::DEFAULT,
        }
    }

    /// Generate a [`BlitCache`] if you intend for the line to not move across multiple frames. If you use this, you MUST call generate_cache if the line does move in the future. This function will not generate a new cache if the previously generated cache is still valid
    pub fn generate_cache(&mut self) {
        if !self.cache.is_cache_valid(&vec![self.pos0, self.pos1]) {
            let points = Self::draw(self.pos0, self.pos1);

            self.cache = BlitCache::new(vec![self.pos0, self.pos1], points);
        }
    }

    /// Draw a line using Bresenham's line algorithm. Returns a list of the pixels to print to
    pub fn draw(pos0: Vec2D, pos1: Vec2D) -> Vec<Vec2D> {
        // Use Bresenham's line algorithm to generate active pixels at rendertime
        let mut points = Vec::new();

        let (mut x, mut y) = pos0.as_tuple();
        let (x1, y1) = pos1.as_tuple();

        let dx = (x1 - x).abs();
        let sx = if x < x1 { 1 } else { -1 };
        let dy = -(y1 - y).abs();
        let sy = if y < y1 { 1 } else { -1 };
        let mut error = dx + dy;

        loop {
            let pixel = Vec2D::new(x, y);
            points.push(pixel);
            let e2 = error * 2;
            if e2 >= dy {
                if x == x1 {
                    break;
                };
                error += dy;
                x += sx;
            };
            if e2 <= dx {
                if y == y1 {
                    break;
                };
                error += dx;
                y += sy;
            };
        }

        points
    }
}

impl ViewElement for Line {
    fn active_pixels(&self) -> Vec<Point> {
        let cache = self.cache.dependent();
        let points = match cache {
            Some(c) => c,
            None => Self::draw(self.pos0, self.pos1),
        };

        utils::points_to_pixels(points, self.fill_char)
    }
}

/// The `Triangle` takes three [`Vec2D`]s and returns a triangle with those vertices when blit to a [`View`]
pub struct Triangle {
    pub pos0: Vec2D,
    pub pos1: Vec2D,
    pub pos2: Vec2D,
    pub fill_char: ColChar,
    cache: BlitCache<Vec2D>,
}

impl Triangle {
    pub fn new(pos0: Vec2D, pos1: Vec2D, pos2: Vec2D, fill_char: ColChar) -> Self {
        Triangle {
            pos0,
            pos1,
            pos2,
            fill_char: fill_char,
            cache: BlitCache::DEFAULT,
        }
    }

    /// Generate a [`BlitCache`] if you intend for the triangle to not move across multiple frames. If you use this, you MUST call generate_cache if the triangle does move in the future. This function will not generate a new cache if the previously generated cache is still valid
    pub fn generate_cache(&mut self) {
        if !self.cache.is_cache_valid(&vec![self.pos0, self.pos1]) {
            let points = Self::draw(self.corners());

            self.cache = BlitCache::new(self.corners().to_vec(), points);
        }
    }

    /// Return the triangle's points as an array
    pub fn corners(&self) -> [Vec2D; 3] {
        [self.pos0, self.pos1, self.pos2]
    }

    /// Takes three corner [`Vec2D`]s and returns the points you should plot to the screen to make a triangle
    pub fn draw(corners: [Vec2D; 3]) -> Vec<Vec2D> {
        let mut points = vec![];
        let mut corners = corners;
        corners.sort_unstable_by_key(|k| k.y);
        let (x0, y0) = corners[0].as_tuple();
        let (x1, y1) = corners[1].as_tuple();
        let (x2, y2) = corners[2].as_tuple();

        let mut x01 = utils::interpolate(y0, x0 as f64, y1, x1 as f64);
        let x12 = utils::interpolate(y1, x1 as f64, y2, x2 as f64);
        let x02 = utils::interpolate(y0, x0 as f64, y2, x2 as f64);

        x01.pop();
        let mut x012 = x01;
        x012.extend(x12);

        let m = (x012.len() as f64 / 2.0).floor() as usize;
        let (x_left, x_right) = match x02[m] < x012[m] {
            true => (x02, x012),
            false => (x012, x02),
        };

        for (i, y) in (y0..y2).enumerate() {
            for x in x_left[i]..x_right[i] {
                points.push(Vec2D::new(x as isize, y));
            }
        }

        points
    }
}

impl ViewElement for Triangle {
    fn active_pixels(&self) -> Vec<Point> {
        let cache = self.cache.dependent();
        let points = match cache {
            Some(c) => c,
            None => Self::draw(self.corners()),
        };

        utils::points_to_pixels(points, self.fill_char)
    }
}

/// The `Polygon` takes a vec of [`Vec2D`]s and returns a polygon with those vertices when blit to a [`View`]
pub struct Polygon {
    pub points: Vec<Vec2D>,
    pub fill_char: ColChar,
    cache: BlitCache<Vec2D>,
}

impl Polygon {
    pub fn new(points: Vec<Vec2D>, fill_char: ColChar) -> Self {
        Self {
            points,
            fill_char,
            cache: BlitCache::DEFAULT,
        }
    }

    /// Generate a [`BlitCache`] if you intend for the polygin to not move across multiple frames. If you use this, you MUST call generate_cache if the polygon does move in the future. This function will not generate a new cache if the previously generated cache is still valid
    pub fn generate_cache(&mut self) {
        if !self.cache.is_cache_valid(&self.points) {
            let points = Self::draw(self.points.clone());

            self.cache = BlitCache::new(self.points.to_vec(), points);
        }
    }

    /// Draw a polygon from points. Only supports convex polygons as of now
    pub fn draw(vertices: Vec<Vec2D>) -> Vec<Vec2D> {
        let mut points = vec![];
        for fi in 1..vertices.len() {
            points.extend(Triangle::draw([
                vertices[0],
                vertices[fi],
                vertices[(fi + 1) % vertices.len()],
            ]))
        }
        points
    }
}

impl ViewElement for Polygon {
    fn active_pixels(&self) -> Vec<Point> {
        let cache = self.cache.dependent();
        let points = match cache {
            Some(c) => c,
            None => Self::draw(self.points.clone()),
        };

        utils::points_to_pixels(points, self.fill_char)
    }
}

/// The `Polygon` takes a position and size, and returns a box at that position with that width and size when blit to a [`View`]
pub struct Box {
    pub pos: Vec2D,
    pub size: Vec2D,
    pub fill_char: ColChar,
    _private: (),
}

impl Box {
    pub fn new(pos: Vec2D, size: Vec2D, fill_char: ColChar) -> Self {
        Self {
            pos,
            size,
            fill_char,
            _private: (),
        }
    }
}

impl ViewElement for Box {
    fn active_pixels(&self) -> Vec<Point> {
        let mut points = vec![];

        for x in 0..self.size.x {
            for y in 0..self.size.y {
                points.push(self.pos + Vec2D { x, y })
            }
        }

        utils::points_to_pixels(points, self.fill_char)
    }
}

/// A `ViewElement` that takes a multi-line string as a parameter, and can be used to put ASCII art, text and other such things on the View
pub struct Sprite {
    pub pos: Vec2D,
    pub texture: String,
    pub modifier: Modifier,
    _private: (),
}
impl Sprite {
    pub fn new(pos: Vec2D, texture: &str, modifier: Modifier) -> Self {
        let mut texture = String::from(texture);
        if texture.starts_with('\n') {
            texture.pop();
        }
        Self {
            pos,
            texture,
            modifier,
            _private: (),
        }
    }
}

impl ViewElement for Sprite {
    fn active_pixels(&self) -> Vec<Point> {
        let mut pixels = vec![];

        let lines = self.texture.split("\n");
        for (y, line) in lines.enumerate() {
            for (x, char) in line.chars().enumerate() {
                if char != ' ' {
                    pixels.push(Point::new(
                        self.pos + Vec2D::new(x as isize, y as isize),
                        ColChar {
                            fill_char: char,
                            modifier: self.modifier,
                        },
                    ));
                }
            }
        }

        pixels
    }
}