pixel_engine_draw/
traits.rs

1use super::graphics::{Color, PixelMode, Sprite};
2use super::vector2::{Vi2d, Vu2d};
3
4use crate::graphics::DrawSpriteTrait;
5
6macro_rules! impl_trait {
7    ($trait:ident) => {
8        impl<T: SmartDrawingTrait> $trait for T {}
9    };
10}
11
12///The Basic Drawing Trait
13///All that is needed to draw one pixel one the target
14pub trait SmartDrawingTrait: DrawSpriteTrait {
15    /// Get the size of the target
16    fn get_size(&self) -> Vu2d;
17    /// Get The textsheet (A [`Sprite`])
18    fn get_textsheet(&self) -> &'static Sprite;
19    /// Clear the Sprite With the given [`Color`]
20    fn clear(&mut self, col: Color);
21    /// Set the pixel data at the given coordinates to the given Color
22    /// Will use the current [`PixelMode`]
23    fn draw<P: Into<Vi2d>>(&mut self, pos: P, col: Color);
24    /// Get the Pixel Data at the given coordinates
25    fn get_pixel<P: Into<Vi2d>>(&self, pos: P) -> Option<Color>;
26    /// Return the [`PixelMode`]
27    fn get_pixel_mode(&self) -> PixelMode;
28    /// Set the [`PixelMode`]
29    fn set_pixel_mode(&mut self, mode: PixelMode);
30    /// Get the Blend Factor
31    /// Used for alpha calculations
32    fn get_blend_factor(&self) -> f32;
33    /// Set the Blend Factor
34    /// Used for alpha calculations
35    fn set_blend_factor(&mut self, f: f32);
36}
37
38pub trait DottedShapeTrait: SmartDrawingTrait {
39    /// Draw a dotted line
40    fn draw_line_dotted<P: Into<Vi2d>>(&mut self, p1: P, p2: P, col: Color, mut pattern: u32) {
41        let mut rol = || {
42            pattern = (pattern << 1) | (pattern >> 31);
43            pattern & 1 > 0
44        };
45        let p1: Vi2d = p1.into();
46        let p2: Vi2d = p2.into();
47        if p1.x == p2.x {
48            // VERTICAL LINE
49            for y in if p2.y > p1.y {
50                p1.y..=p2.y
51            } else {
52                p2.y..=p1.y
53            } {
54                if rol() {
55                    self.draw((p1.x, y), col);
56                }
57            }
58        } else if p1.y == p2.y {
59            // HORIZONTAL LINE
60            for x in if p2.x > p1.x {
61                p1.x..=p2.x
62            } else {
63                p2.x..=p1.x
64            } {
65                if rol() {
66                    self.draw((x, p1.y), col);
67                }
68            }
69        } else {
70            let (mut x0, mut y0) = (p1.x as i32, p1.y as i32);
71            let (mut x1, mut y1) = (p2.x as i32, p2.y as i32);
72            if (y1 - y0).abs() < (x1 - x0).abs() {
73                if x0 > x1 {
74                    std::mem::swap(&mut x0, &mut x1);
75                    std::mem::swap(&mut y0, &mut y1);
76                }
77                let dx = x1 - x0;
78                let mut dy = y1 - y0;
79                let mut yi = 1;
80                if dy < 0 {
81                    yi = -1;
82                    dy = -dy;
83                }
84                let mut d = 2 * dy - dx;
85                let mut y = y0;
86
87                for x in x0..x1 {
88                    if x >= 0 && y >= 0 && rol() {
89                        self.draw((x, y), col);
90                    }
91                    if d > 0 {
92                        y += yi;
93                        d -= 2 * dx;
94                    }
95                    d += 2 * dy;
96                }
97            } else {
98                if y0 > y1 {
99                    std::mem::swap(&mut x0, &mut x1);
100                    std::mem::swap(&mut y0, &mut y1);
101                }
102                let mut dx = x1 - x0;
103                let dy = y1 - y0;
104                let mut xi = 1;
105                if dx < 0 {
106                    xi = -1;
107                    dx = -dx;
108                }
109                let mut d = 2 * dx - dy;
110                let mut x = x0;
111
112                for y in y0..=y1 {
113                    if x >= 0 && y >= 0 && rol() {
114                        self.draw((x, y), col);
115                    }
116                    if d > 0 {
117                        x += xi;
118                        d -= 2 * dy;
119                    }
120                    d += 2 * dx;
121                }
122            }
123        }
124    }
125
126    /// Draw a rectangle with the top left corner at `(x, y)`
127    /// and the bottom right corner at `(x + w, y + h)` (both inclusive)
128    /// This is the dotted form
129    fn draw_rect_dotted<P: Into<Vi2d>>(&mut self, pos: P, size: P, col: Color, pattern: u32) {
130        let Vi2d { x, y } = pos.into();
131        let Vi2d { x: w, y: h } = size.into() - Vi2d { x: 1, y: 1 };
132        self.draw_line_dotted((x, y), (x + w, y), col, pattern);
133        self.draw_line_dotted((x + w, y), (x + w, y + h), col, pattern);
134        self.draw_line_dotted((x + w, y + h), (x, y + h), col, pattern);
135        self.draw_line_dotted((x, y + h), (x, y), col, pattern);
136    }
137
138    /// Draw the edges of a triangle between the three points
139    /// This is the dotted form
140    fn draw_triangle_dotted<P: Into<Vi2d>>(
141        &mut self,
142        pts1: P,
143        pts2: P,
144        pts3: P,
145        col: Color,
146        pattern: u32,
147    ) {
148        let pts1: Vi2d = pts1.into();
149        let pts2: Vi2d = pts2.into();
150        let pts3: Vi2d = pts3.into();
151        self.draw_line_dotted(pts1, pts2, col, pattern);
152        self.draw_line_dotted(pts1, pts3, col, pattern);
153        self.draw_line_dotted(pts2, pts3, col, pattern);
154    }
155}
156
157/// A trait that regroups all the Shapes Drawing
158/// You don't need to implement anything other that [`DrawSpriteTrait`] to use it
159pub trait ShapesTrait: SmartDrawingTrait {
160    /// Draw text to the screen
161    /// `scale` must be >= 1
162    /// The textsize will be equal to `scale * 8` for the height and `scale * 8 * text.len()` for
163    /// the width
164    /// This will handle `\n` treating it as a new line, but wont do any newline stuff if it is
165    /// drawing out of the screen
166    fn draw_text<P: Into<Vi2d>>(&mut self, pos: P, scale: u32, col: Color, text: &str) {
167        #![allow(
168            clippy::cast_precision_loss,
169            clippy::cast_possible_wrap,
170            clippy::cast_sign_loss
171        )]
172        let Vi2d { x, y } = pos.into();
173        let scale: i32 = scale.try_into().unwrap();
174        let mut sx = 0;
175        let mut sy = 0;
176        for chr in text.chars() {
177            if chr == '\n' {
178                sx = 0;
179                sy += 8 * scale;
180            } else {
181                if !chr.is_ascii() {
182                    continue;
183                }
184                let ox: i32 = ((chr as u32 - 32) % 16) as i32;
185                let oy: i32 = ((chr as u32 - 32) / 16) as i32;
186                if scale > 1 {
187                    for i in 0..8i32 {
188                        for j in 0..8i32 {
189                            if self
190                                .get_textsheet()
191                                .get_pixel((i + ox * 8) as u32, (j + oy * 8) as u32)
192                                .r
193                                > 0
194                            {
195                                for is in 0..=(scale as i32) {
196                                    for js in 0..=(scale as i32) {
197                                        self.draw(
198                                            (x + sx + (i * scale) + is, y + sy + (j * scale) + js),
199                                            col,
200                                        );
201                                    }
202                                }
203                            }
204                        }
205                    }
206                } else {
207                    for i in 0..8i32 {
208                        for j in 0..8i32 {
209                            if self
210                                .get_textsheet()
211                                .get_pixel((i + ox * 8) as u32, (j + oy * 8) as u32)
212                                .r
213                                > 0
214                            {
215                                self.draw((x + sx + i, y + sy + j), col);
216                            }
217                        }
218                    }
219                }
220            }
221            sx += 8 * scale;
222        }
223    }
224
225    /// Draw a line between two points,
226    /// You don't need to do anything with the points for it to work, it will swap them it needed.
227    fn draw_line<P: Into<Vi2d>>(&mut self, p1: P, p2: P, col: Color) {
228        /* OLD Implementation by me
229         * let (p1, p2) = if p1.x < p2.x { (p1, p2) } else { (p2, p1) };
230        if p1.x == p2.x {
231            let iter = if p1.y < p2.y {
232                p1.y..=(p2.y)
233            } else {
234                p2.y..=(p1.y)
235            };
236            for y in iter {
237                self.draw(p1.x, y, col);
238            }
239        } else {
240            let a = (p1.y as f32 - p2.y as f32) / (p1.x as f32 - p2.x as f32);
241            let b = p1.y as f32 - (a * p1.x as f32);
242            /*println!(
243                "START {:?} || END: {:?} || a = {:.2} = {:.2}/{:.2} || b = {:.2}",
244                p1,
245                p2,
246                a,
247                (p1.y as f32 - p2.y as f32),
248                (p1.x as f32 - p2.x as f32),
249                b
250            );*/
251            if -1.x <= a && a <= 1.x {
252                for x in p1.x..=(p2.x) {
253                    let y = a * x as f32 + b;
254                    self.draw(x, y.round() as u32, col);
255                }
256            } else if a > 1.x || a < -1.x {
257                let iter = if p1.y < p2.y {
258                    p1.y..=p2.y
259                } else {
260                    p2.y..=p1.y
261                };
262                for y in iter {
263                    let x = ((y as f32 - b) / a).round() as u32;
264                    self.draw(x, y, col);
265                }
266            }
267        }*/
268        let p1: Vi2d = p1.into();
269        let p2: Vi2d = p2.into();
270        if p1.x == p2.x {
271            // VERTICAL LINE
272            for y in if p2.y > p1.y {
273                p1.y..=p2.y
274            } else {
275                p2.y..=p1.y
276            } {
277                self.draw((p1.x, y), col);
278            }
279        } else if p1.y == p2.y {
280            // HORIZONTAL LINE
281            for x in if p2.x > p1.x {
282                p1.x..=p2.x
283            } else {
284                p2.x..=p1.x
285            } {
286                self.draw((x, p1.y), col);
287            }
288        } else {
289            let (mut x0, mut y0) = (p1.x as i32, p1.y as i32);
290            let (mut x1, mut y1) = (p2.x as i32, p2.y as i32);
291            if (y1 - y0).abs() < (x1 - x0).abs() {
292                if x0 > x1 {
293                    std::mem::swap(&mut x0, &mut x1);
294                    std::mem::swap(&mut y0, &mut y1);
295                }
296                let dx = x1 - x0;
297                let mut dy = y1 - y0;
298                let mut yi = 1;
299                if dy < 0 {
300                    yi = -1;
301                    dy = -dy;
302                }
303                let mut d = 2 * dy - dx;
304                let mut y = y0;
305
306                for x in x0..x1 {
307                    if x >= 0 && y >= 0 {
308                        self.draw((x, y), col);
309                    }
310                    if d > 0 {
311                        y += yi;
312                        d -= 2 * dx;
313                    }
314                    d += 2 * dy;
315                }
316            } else {
317                if y0 > y1 {
318                    std::mem::swap(&mut x0, &mut x1);
319                    std::mem::swap(&mut y0, &mut y1);
320                }
321                let mut dx = x1 - x0;
322                let dy = y1 - y0;
323                let mut xi = 1;
324                if dx < 0 {
325                    xi = -1;
326                    dx = -dx;
327                }
328                let mut d = 2 * dx - dy;
329                let mut x = x0;
330
331                for y in y0..=y1 {
332                    if x >= 0 && y >= 0 {
333                        self.draw((x, y), col);
334                    }
335                    if d > 0 {
336                        x += xi;
337                        d -= 2 * dy;
338                    }
339                    d += 2 * dx;
340                }
341            }
342        }
343    }
344
345    /// Draw a rectangle with the top left corner at `(x, y)`
346    /// and the bottom right corner at `(x + w, y + h)` (both inclusive)
347    fn draw_rect<P: Into<Vi2d>>(&mut self, pos: P, size: P, col: Color) {
348        let Vi2d { x, y } = pos.into();
349        let Vi2d { x: w, y: h } = size.into() - Vi2d { x: 1, y: 1 };
350        self.draw_line((x, y), (x + w, y), col);
351        self.draw_line((x + w, y), (x + w, y + h), col);
352        self.draw_line((x + w, y + h), (x, y + h), col);
353        self.draw_line((x, y + h), (x, y), col);
354    }
355
356    /// Fill a rectangle with the top left corner at `(x, y)`
357    /// and the bottom right corner at `(x + w, y + h)` (both inclusive)
358    fn fill_rect<P: Into<Vi2d>>(&mut self, pos: P, size: P, col: Color) {
359        let Vi2d { x, y } = pos.into();
360        let Vi2d { x: w, y: h } = size.into();
361        for nx in x..(x + w) {
362            self.draw_line((nx, y), (nx, y + h), col);
363        }
364    }
365
366    /// Draw a circle with center `(x, y)` and raduis `r`
367    fn draw_circle<P: Into<Vi2d>>(&mut self, pos: P, r: u32, col: Color) {
368        let Vi2d { x, y } = pos.into();
369        let r_i32: i32 = r.try_into().unwrap();
370        let x = x as i32;
371        let y = y as i32;
372        let mut x0: i32 = 0;
373        let mut y0: i32 = r_i32;
374        let mut d: i32 = 3i32 - 2i32 * r_i32;
375        if r == 0 {
376            return;
377        }
378        while y0 >= x0 {
379            self.draw(((x + x0), (y - y0)), col);
380            self.draw(((x + y0), (y - x0)), col);
381            self.draw(((x + y0), (y + x0)), col);
382            self.draw(((x + x0), (y + y0)), col);
383
384            self.draw(((x - x0), (y + y0)), col);
385            self.draw(((x - y0), (y + x0)), col);
386            self.draw(((x - y0), (y - x0)), col);
387            self.draw(((x - x0), (y - y0)), col);
388
389            x0 += 1;
390            if d < 0 {
391                d += 4 * x0 + 6;
392            } else {
393                y0 -= 1;
394                d += 4 * (x0 - y0) + 10;
395            }
396        }
397    }
398
399    /// Fill a circle with center `(x, y)` and raduis `r`
400    fn fill_circle<P: Into<Vi2d>>(&mut self, pos: P, r: u32, col: Color) {
401        let Vi2d { x, y } = pos.into();
402        let r_i32: i32 = r.try_into().unwrap();
403        let x = x as i32;
404        let y = y as i32;
405        let mut x0: i32 = 0;
406        let mut y0: i32 = r_i32;
407        let mut d: i32 = 3 - 2 * r_i32;
408        if r == 0 {
409            return;
410        }
411        while y0 >= x0 {
412            self.draw_line((x - x0, y - y0), (x + x0, y - y0), col);
413            self.draw_line((x - y0, y - x0), (x + y0, y - x0), col);
414            self.draw_line((x - x0, y + y0), (x + x0, y + y0), col);
415            self.draw_line((x - y0, y + x0), (x + y0, y + x0), col);
416            x0 += 1;
417            if d < 0 {
418                d += 4 * x0 + 6;
419            } else {
420                y0 -= 1;
421                d += 4 * (x0 - y0) + 10;
422            }
423        }
424    }
425
426    /// Draw the edges of a triangle between the three points
427    fn draw_triangle<P: Into<Vi2d>>(&mut self, pts1: P, pts2: P, pts3: P, col: Color) {
428        let pts1: Vi2d = pts1.into();
429        let pts2: Vi2d = pts2.into();
430        let pts3: Vi2d = pts3.into();
431        self.draw_line(pts1, pts2, col);
432        self.draw_line(pts1, pts3, col);
433        self.draw_line(pts2, pts3, col);
434    }
435
436    /// Fill the given triangle
437    fn fill_triangle<P: Into<Vi2d>>(&mut self, pts1: P, pts2: P, pts3: P, col: Color) {
438        #![allow(clippy::cast_precision_loss)]
439        use std::cmp::{max, min};
440
441        let pts1: Vi2d = pts1.into();
442        let pts2: Vi2d = pts2.into();
443        let pts3: Vi2d = pts3.into();
444        self.draw_triangle(pts1, pts2, pts3, col);
445
446        let pts1 = (pts1.x as i32, pts1.y as i32);
447        let pts2 = (pts2.x as i32, pts2.y as i32);
448        let pts3 = (pts3.x as i32, pts3.y as i32);
449        let centroid = (
450            ((pts1.0 + pts2.0 + pts3.0) as f32 / 3f32),
451            ((pts1.1 + pts2.1 + pts3.1) as f32 / 3f32),
452        );
453        let lines = (
454            (
455                pts1.1 - pts2.1,
456                pts2.0 - pts1.0,
457                (pts1.1 - pts2.1) * pts2.0 + (pts2.0 - pts1.0) * pts2.1,
458            ),
459            (
460                pts3.1 - pts1.1,
461                pts1.0 - pts3.0,
462                (pts3.1 - pts1.1) * pts1.0 + (pts1.0 - pts3.0) * pts1.1,
463            ),
464            (
465                pts2.1 - pts3.1,
466                pts3.0 - pts2.0,
467                (pts2.1 - pts3.1) * pts3.0 + (pts3.0 - pts2.0) * pts3.1,
468            ),
469        );
470        //dbg!(&lines);
471        let iterx = {
472            let x1 = min(min(pts1.0, pts2.0), pts3.0);
473            let x2 = max(max(pts1.0, pts2.0), pts3.0);
474            if x1 > x2 {
475                x2..=x1
476            } else {
477                x1..=x2
478            }
479        };
480        let itery = {
481            let y1 = min(min(pts1.1, pts2.1), pts3.1);
482            let y2 = max(max(pts1.1, pts2.1), pts3.1);
483
484            if y1 > y2 {
485                y2..=y1
486            } else {
487                y1..=y2
488            }
489        };
490        let l_mul = (
491            if (lines.0).0 as f32 * centroid.0 + (lines.0).1 as f32 * centroid.1
492                - (lines.0).2 as f32
493                >= 0f32
494            {
495                1
496            } else {
497                -1
498            },
499            if (lines.1).0 as f32 * centroid.0 + (lines.1).1 as f32 * centroid.1
500                - (lines.1).2 as f32
501                >= 0f32
502            {
503                1
504            } else {
505                -1
506            },
507            if (lines.2).0 as f32 * centroid.0 + (lines.2).1 as f32 * centroid.1
508                - (lines.2).2 as f32
509                >= 0f32
510            {
511                1
512            } else {
513                -1
514            },
515        );
516
517        for x in iterx {
518            for y in itery.clone() {
519                if ((lines.0).0 * x + (lines.0).1 * y - (lines.0).2) * l_mul.0 >= 0
520                    && ((lines.1).0 * x + (lines.1).1 * y - (lines.1).2) * l_mul.1 >= 0
521                    && ((lines.2).0 * x + (lines.2).1 * y - (lines.2).2) * l_mul.2 >= 0
522                {
523                    self.draw((x, y), col);
524                }
525            }
526        }
527    }
528}
529/// A trait that will handle the drawing of Sprite onto the Target
530pub trait SpriteTrait: SmartDrawingTrait {
531    /// Draw a Sprite with the top left corner at `(x, y)`
532    /// the flip arguement will allow fliping of the axis
533    /// flip: (horizontal, vertical)
534    /// scale is the scale of the result (must be >= 1)
535    fn draw_sprite<P: Into<Vi2d>>(
536        &mut self,
537        pos: P,
538        scale: u32,
539        sprite: &Sprite,
540        flip: (bool, bool),
541    ) {
542        #![allow(
543            clippy::cast_precision_loss,
544            clippy::cast_possible_wrap,
545            clippy::cast_sign_loss
546        )]
547        let Vi2d { x, y } = pos.into();
548        let (mut fxs, mut fxm) = (0i32, 1i32);
549        let (mut fys, mut fym) = (0i32, 1i32);
550        let mut fx: i32;
551        let mut fy: i32;
552        if flip.0 {
553            fxs = sprite.width() as i32 - 1;
554            fxm = -1;
555        }
556        if flip.1 {
557            fys = sprite.height() as i32 - 1;
558            fym = -1;
559        }
560        if scale > 1 {
561            fx = fxs;
562            for i in 0..(sprite.width() as i32) {
563                fy = fys;
564                for j in 0..(sprite.height() as i32) {
565                    for is in 0..(scale as i32) {
566                        for js in 0..(scale as i32) {
567                            self.draw(
568                                (x + i * (scale as i32) + is, y + j * (scale as i32) + js),
569                                sprite.get_pixel(fx as u32, fy as u32),
570                            );
571                        }
572                    }
573                    fy += fym;
574                }
575                fx += fxm;
576            }
577        } else {
578            fx = fxs;
579            for i in 0..(sprite.width() as i32) {
580                fy = fys;
581                for j in 0..(sprite.height() as i32) {
582                    self.draw((x + i, y + j), sprite.get_pixel(fx as u32, fy as u32));
583                    fy += fym;
584                }
585                fx += fxm;
586            }
587        }
588    }
589    /// Draw a chunk of the given [`Sprite`] onto the Target
590    /// `coords` is the top left corner of the Target
591    /// `o` is the Top left corner of the Sprite Chunk
592    /// and `size` is the `(width, height)` of the chunk
593    /// `flip` and `scale` is the same as [`SpriteTrait::draw_sprite()`]
594    fn draw_partial_sprite<P: Into<Vi2d>>(
595        &mut self,
596        coords: P,
597        sprite: &Sprite,
598        o: P,
599        size: P,
600        scale: u32,
601        flip: (bool, bool),
602    ) {
603        #![allow(
604            clippy::cast_precision_loss,
605            clippy::cast_possible_wrap,
606            clippy::cast_sign_loss
607        )]
608        let Vi2d { x, y } = coords.into();
609        let Vi2d { x: ox, y: oy } = o.into();
610        let Vi2d { x: w, y: h } = size.into();
611
612        let (mut fxs, mut fxm) = (0i32, 1i32);
613        let (mut fys, mut fym) = (0i32, 1i32);
614        let mut fx: i32;
615        let mut fy: i32;
616        if flip.0 {
617            fxs = w as i32 - 1;
618            fxm = -1;
619        }
620        if flip.1 {
621            fys = h as i32 - 1;
622            fym = -1;
623        }
624
625        if scale > 1 {
626            fx = fxs;
627            for i in 0..w {
628                fy = fys;
629                for j in 0..h {
630                    for is in 0..(scale as i32) {
631                        for js in 0..(scale as i32) {
632                            self.draw(
633                                (x + i * (scale as i32) + is, y + j * (scale as i32) + js),
634                                sprite.get_pixel((fx + ox) as u32, (fy + oy) as u32),
635                            );
636                        }
637                    }
638                    fy += fym;
639                    fx += fxm;
640                }
641            }
642        } else {
643            fx = fxs;
644            for i in 0..w {
645                fy = fys;
646                for j in 0..h {
647                    self.draw(
648                        (x + i, y + j),
649                        sprite.get_pixel(fx as u32 + ox as u32, fy as u32 + oy as u32),
650                    );
651                    fy += fym;
652                }
653                fx += fxm;
654            }
655        }
656    }
657}
658
659impl_trait!(SpriteTrait);
660impl_trait!(ShapesTrait);
661impl_trait!(DottedShapeTrait);