From d992c3d674de027a7e317d887e54538c15f0af43 Mon Sep 17 00:00:00 2001 From: James Waples Date: Sat, 22 May 2021 12:13:56 +0100 Subject: [PATCH 1/6] Greebles demo --- eg-next/Cargo.toml | 1 + eg-next/examples/anim8.rs | 331 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+) create mode 100644 eg-next/examples/anim8.rs diff --git a/eg-next/Cargo.toml b/eg-next/Cargo.toml index 4178145..6cfb84d 100644 --- a/eg-next/Cargo.toml +++ b/eg-next/Cargo.toml @@ -11,6 +11,7 @@ embedded-graphics = { git = "https://github.com/embedded-graphics/embedded-graph%20embedded-graphics-simulator%20=%20%7B%20git%20="https://github.com/embedded-graphics/simulator.git" } tinybmp = { git = "https://github.com/embedded-graphics/tinybmp.git" } tinytga = { git = "https://github.com/embedded-graphics/tinytga.git" } +rand = "0.8.3" [patch.crates-io] embedded-graphics = { git = "https://github.com/embedded-graphics/embedded-graphics.git" } diff --git a/eg-next/examples/anim8.rs b/eg-next/examples/anim8.rs new file mode 100644 index 0000000..ab1c960 --- /dev/null +++ b/eg-next/examples/anim8.rs @@ -0,0 +1,331 @@ +//! # Example: Analog Clock +//! +//! ![Screenshot of clock example]( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfwAAAH9AQMAAADFwFz1AAAABlBMVEUAAAD///+l2Z/dAAAFM0lEQVR42u3dYWrjMBAF4Afz1zBX8QEEc/WBvdAcQKBNussK3EbVpDNSt+SRYMWWPrBsybQJCK+88sp/EWm32BagN+/EcoBuLRUE8K2wA6B7c9yBN2I9QK3+2eItrS0HWv0L9c9LgV6f+p6lADdcAWq6AuiVO9DRhYBYty571wBc8RGApquApkM4H+A6lvOBpkM6H+A6tvOBpkM8H6A61BcAosMetnSA6olBzpYOsI0B0Wyg4cAgSjUZIPsEgCQDoqCRD7DlAvX2Hlyo414lFSD7HJAsoI/lc3An9xGdBFRMpCYCZDOAJAKsMwBrHiCYCVka0I+MI0GAvw97vSxAMBeyLKBiMjUJIJsFJAjwX8VeMwcQzIYsB6iYTk0ByOYBCQL8V7HXzQAE8yHLAAocSQHMA5QEgNQDcALAPkDjAYEnZPFAhSs1HjAfUMIBUh/A4QB7AY0GBL6QRQMFzoQD5gVKMEBuQKIB9QIcDDC8IY0FxA9YLFDgTjBQ/UCNBcwPlFCA1A9wKMBPAE1DgeI/AwoFxN+LJ1kkUHD655NQoLrntAOokYAB6h9NgQAZnohEAvoMwFHA+9HcHuUCaCCAZwAKBOQ5wOKAcgGU7q/WLoULgB8FXMZSlVqkisq1cAHKDwJIrzdSlXZvdysU6YUrwD8ZqHR/3VKpKeRvgVoawHgOIE0DfrX7S6X35q/bpkoaIM8CFgWUy4G3VnegSr21fSt8BOAnA2+75O9cet9WQG7lNMCeBUoW0B7kBwOkzwIcB/iSD+h2oKwGnv8XMWkSgHM7wNsB2Fqg4OlzsDQAuh0o2wHS3QDO7QCvBGxw7JOUTODYDpDuBlC2A6S7AZzbAd4OwBYB/XL5xxMnA6S7AZTtAG0HYNsB3g7AWDcDR6ubAfoGQFsAtGbPAdLal4Fv0gcjAK1+EfgGN9IXgcK6GVBsBhi7gXM7oDdjK8DYDZxrANioD8dASQYYO4F/bY+NAOlu4MBu4NwNkG4FekvKBUZ/WtkEYJkAYTdwbAesz6x7AMJu4FgJ8INDnwOkeQBhN1B68dwD6FKAdNCHQ4CRBpTtgO4G+DKws4H3T9dzEihpgO4GGKuBMujDIWBZgF6G9nKAsRyQQR+OAAoD+DJXTwOaAxxYCPTCaBLVwYyWAZDuBg5sAGDosXmgpACELUAv4XB8fWwpgO0B5NKHcwAFAtz70ANoIPCvaA6AA4E+O3q+QWfEAf2CeAAJBPAUUCKBCqgPOIAaCRQwfAApLBIQnHD+iOCkUIBJvQCThgIVXgAtFCD1A4xIYDyaPtZLLFD9QB0CbqD4AYsFxA1QMMB+QGOB/nH26coIBswLSDAAN1CigeIFLBoQJ0DhAKvv6coaDZAXQDQA8wElHqg+oMYD4gLI4gF2/daTNR4gH4B4AOYBSgZQPIBlAOIAKALw3En6vm4GQDYPCDIA1Hmg5gAyDZDlAKyzAGsOQDb7fBfkAKizQM0CZBIgywJY5wDWLIBsDhBkAZCp5ztZHsD6ALZxLT/g7UU9LicaALiGE4EulfIAUQD6wVXUfj3ZMgGy2xuPAep9mASgfg7UXEAUxwCAgi0XIMM5Ak4IcgE0nKNb+aSaDbDpaDgfotkAVR1NaUdDBOBfWkj7GeYDVEdAQz6Apo+frlxTgUGls+OpwONaZ7dXAFzfA51eAfR6HehyCOBfdu/o8BIAYleg710DUNP3ADesAnrlDnR0EYBWL0Dfkw90oQPU24cA/uVYqTV8Ffj/1rT9Buv6foO1jb/D+s6vvPKKI78B89G0YRkxkl8AAAAASUVORK5CYII=) +//! +//! This example shows some more advanced usage of Embedded Graphics. It draws a round clock face +//! with hour, minute and second hands. A digital clock is drawn in the middle of the clock. The +//! whole thing is updated with your computer's local time every 50ms. + +use chrono::{Local, Timelike}; +use core::f32::consts::PI; +use embedded_graphics::{ + mono_font::{ascii::FONT_9X15, MonoTextStyle}, + pixelcolor::Rgb888, + prelude::*, + primitives::{ + self, Arc, Circle, Line, Polyline, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle, + Sector, Triangle, + }, + text::Text, +}; +use embedded_graphics_simulator::{ + OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, +}; +use rand::{thread_rng, Rng}; +use std::{ + thread, + time::{Duration, Instant}, +}; + +fn polar(circle: &Circle, angle: f32, radius: i32) -> Point { + let radius = radius as f32; + + circle.center() + + Point::new( + (angle.sin() * radius) as i32, + -(angle.cos() * radius) as i32, + ) +} + +fn draw_static_crosshair(target: &mut D) -> Result<(), D::Error> +where + D: DrawTarget, +{ + let center = Point::new(80, 80); + + // Outside + Circle::with_center(center, 149) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_GREEN, 1)) + .draw(target)?; + + // Inside + Circle::with_center(center, 15) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_GREEN, 1)) + .draw(target)?; + + // Center crosshair point + Pixel(center, Rgb888::CSS_GREEN).draw(target)?; + + // Crosshair lines + { + let offs = Size::new_equal(20); + let len = Size::new_equal(20); + + let line_style = PrimitiveStyle::with_stroke(Rgb888::CSS_LIME_GREEN, 1); + + Line::new( + center - offs.y_axis(), + center - offs.y_axis() - len.y_axis(), + ) + .into_styled(line_style) + .draw(target)?; + + Line::new( + center - offs.x_axis(), + center - offs.x_axis() - len.x_axis(), + ) + .into_styled(line_style) + .draw(target)?; + + Line::new( + center + offs.x_axis(), + center + offs.x_axis() + len.x_axis(), + ) + .into_styled(line_style) + .draw(target)?; + + Line::new( + center + offs.y_axis(), + center + offs.y_axis() + len.y_axis(), + ) + .into_styled(line_style) + .draw(target)?; + } + + Ok(()) +} + +fn draw_dynamic_crosshair(target: &mut D, t: Duration) -> Result<(), D::Error> +where + D: DrawTarget, +{ + let center = Point::new(80, 80); + + let angle = (t.as_millis() / 10 % 360) as u32; + let angle = (angle as f32).deg(); + + Arc::with_center(center, 149, 0.0.deg() + angle, 45.0.deg()) + .into_styled( + PrimitiveStyleBuilder::new() + .stroke_alignment(primitives::StrokeAlignment::Outside) + .stroke_width(5) + .stroke_color(Rgb888::CSS_LIME_GREEN) + .build(), + ) + .draw(target)?; + + Arc::with_center(center, 149, -90.0.deg() - angle, -110.0.deg()) + .into_styled( + PrimitiveStyleBuilder::new() + .stroke_alignment(primitives::StrokeAlignment::Inside) + .stroke_width(5) + .stroke_color(Rgb888::CSS_LIME_GREEN) + .build(), + ) + .draw(target)?; + + Ok(()) +} + +fn draw_radar(target: &mut D, t: Duration) -> Result<(), D::Error> +where + D: DrawTarget, +{ + let size = 89; + let center = Point::new(200, 200); + + let base_angle = (t.as_millis() / 5 % 360) as u32; + let angle = (base_angle as f32).deg(); + + let radar = Circle::with_center(center, size); + + radar + .into_styled(PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_GREEN, 1)) + .draw(target)?; + + Sector::with_center(center, size, 90.0.deg() + angle, -60.0.deg()) + .into_styled( + PrimitiveStyleBuilder::new() + .fill_color(Rgb888::CSS_LIME_GREEN) + .build(), + ) + .draw(target)?; + + { + let pos = polar(&radar, 0.0, 20); + let brightness = 255u32.saturating_sub(base_angle) as u8; + let color = Rgb888::new(0, brightness, 0); + + Circle::with_center(pos, 3) + .into_styled(PrimitiveStyle::with_fill(color)) + .draw(target)?; + } + + Ok(()) +} + +fn draw_scanners(target: &mut D, t: Duration) -> Result<(), D::Error> +where + D: DrawTarget, +{ + let frame = Rectangle::new(Point::new(200 - 89 / 2 + 10, 10), Size::new(79, 10)); + + frame + .into_styled(PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_GREEN, 1)) + .draw(target)?; + + let w = frame.bounding_box().size.width - 10; + + let pos = ((t.as_millis() as u32 / 20) % w * 2) as u32; + + let pos = if pos > w { + // Reverse + w + (w - pos) + } else { + // Forward + pos + }; + + Rectangle::new( + Point::new(200 - 89 / 2 + 10 + pos as i32, 10), + Size::new(10, 10), + ) + .into_styled(PrimitiveStyle::with_fill(Rgb888::CSS_LIME_GREEN)) + .draw(target)?; + + // --- + + let frame = Rectangle::new(Point::new(200 - 89 / 2 + 10, 30), Size::new(79, 10)); + + frame + .into_styled(PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_GREEN, 1)) + .draw(target)?; + + let w = frame.bounding_box().size.width - 10; + + let pos = ((t.as_millis() as u32 / 20 + 20) % w * 2) as u32; + + let pos = if pos > w { + // Reverse + w + (w - pos) + } else { + // Forward + pos + }; + + Rectangle::new( + Point::new( + 200 - 89 / 2 + 10 + pos as i32, + frame.bounding_box().top_left.y, + ), + Size::new(10, 10), + ) + .into_styled(PrimitiveStyle::with_fill(Rgb888::CSS_LIME_GREEN)) + .draw(target)?; + + Ok(()) +} + +fn draw_horizontal_rule(target: &mut D, t: Instant, lerp: &mut Lerp) -> Result<(), D::Error> +where + D: DrawTarget, +{ + Polyline::new(&[ + Point::new(10, 200 - 5), + Point::new(10, 200), + Point::new(130, 200), + Point::new(130, 200 - 5), + ]) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_GREEN, 1)) + .draw(target)?; + + Triangle::new(Point::zero(), Point::new(5, 10), Point::new(-5, 10)) + .translate(Point::new( + 10 + lerp.position(t.elapsed().as_millis() as u64) as i32, + 200, + )) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::CSS_LIME_GREEN, 1)) + .draw(target)?; + + Ok(()) +} + +struct Lerp { + is_animating: bool, + duration: u32, + final_position: u32, + start_position: u32, + start: u64, +} + +impl Lerp { + fn position(&mut self, t: u64) -> u32 { + let t = t - self.start; + let t = (t as u32) as f32 / self.duration as f32; + + if t >= 1.0 { + self.is_animating = false; + } + + // Floating point LERP + let pos = (1.0 - t) * self.start_position as f32 + t * self.final_position as f32; + + pos as u32 + } + + fn move_target(&mut self, new_start: u64, new_target: u32) { + self.start_position = self.final_position; + self.final_position = new_target; + self.start = new_start; + + println!( + "Reset to {} -> {}", + self.start_position, self.final_position + ); + + self.is_animating = true; + } +} + +fn main() -> Result<(), core::convert::Infallible> { + let mut display = SimulatorDisplay::::new(Size::new(256, 256)); + + let output_settings = OutputSettingsBuilder::new().scale(2).build(); + let mut window = Window::new("Greebles", &output_settings); + + let start = Instant::now(); + + let mut h_state = Lerp { + is_animating: true, + duration: 500, + start_position: 0, + final_position: 50, + start: 0, + }; + + let mut rng = thread_rng(); + + 'running: loop { + display.clear(Rgb888::BLACK)?; + + draw_static_crosshair(&mut display)?; + draw_dynamic_crosshair(&mut display, start.elapsed())?; + + draw_radar(&mut display, start.elapsed())?; + + draw_scanners(&mut display, start.elapsed())?; + + draw_horizontal_rule(&mut display, start, &mut h_state)?; + + if !h_state.is_animating { + h_state.move_target(start.elapsed().as_millis() as u64, rng.gen_range(0..=120)); + } + + window.update(&display); + + if window.events().any(|e| e == SimulatorEvent::Quit) { + break 'running Ok(()); + } + thread::sleep(Duration::from_millis(16)); + } +} From a7fdb364d045cda5f207ca409aacb47586008143 Mon Sep 17 00:00:00 2001 From: James Waples Date: Tue, 25 May 2021 21:10:44 +0100 Subject: [PATCH 2/6] Add compound invuln animation --- eg-next/examples/anim8.rs | 71 +++++++++++++++++++++++-- eg-next/examples/assets/invuln.bmp | Bin 0 -> 5138 bytes eg-next/examples/assets/soulsphere.bmp | Bin 0 -> 5138 bytes 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 eg-next/examples/assets/invuln.bmp create mode 100644 eg-next/examples/assets/soulsphere.bmp diff --git a/eg-next/examples/anim8.rs b/eg-next/examples/anim8.rs index ab1c960..dea17cc 100644 --- a/eg-next/examples/anim8.rs +++ b/eg-next/examples/anim8.rs @@ -9,8 +9,9 @@ use chrono::{Local, Timelike}; use core::f32::consts::PI; use embedded_graphics::{ + image::Image, mono_font::{ascii::FONT_9X15, MonoTextStyle}, - pixelcolor::Rgb888, + pixelcolor::{Rgb565, Rgb888}, prelude::*, primitives::{ self, Arc, Circle, Line, Polyline, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle, @@ -26,6 +27,7 @@ use std::{ thread, time::{Duration, Instant}, }; +use tinybmp::Bmp; fn polar(circle: &Circle, angle: f32, radius: i32) -> Point { let radius = radius as f32; @@ -226,7 +228,7 @@ where Ok(()) } -fn draw_horizontal_rule(target: &mut D, t: Instant, lerp: &mut Lerp) -> Result<(), D::Error> +fn draw_horizontal_rule(target: &mut D, t: Duration, lerp: &mut Lerp) -> Result<(), D::Error> where D: DrawTarget, { @@ -241,7 +243,7 @@ where Triangle::new(Point::zero(), Point::new(5, 10), Point::new(-5, 10)) .translate(Point::new( - 10 + lerp.position(t.elapsed().as_millis() as u64) as i32, + 10 + lerp.position(t.as_millis() as u64) as i32, 200, )) .into_styled(PrimitiveStyle::with_stroke(Rgb888::CSS_LIME_GREEN, 1)) @@ -250,6 +252,62 @@ where Ok(()) } +struct Invuln { + image: Bmp<'static, Rgb565>, +} + +impl Invuln { + fn new(image: Bmp<'static, Rgb565>) -> Self { + Self { image } + } + + fn draw(&mut self, display: &mut D, t: Duration) -> Result<(), D::Error> + where + D: DrawTarget, + { + let elapsed = t.as_millis() as u32; + + let index = (elapsed / 250) % 4; + + let mut display = display.color_converted(); + + let frame = self.image.sub_image(&Rectangle::new( + Point::new(self.image.size().height as i32 * index as i32, 0), + Size::new_equal(self.image.size().height), + )); + + Image::new(&frame, Point::new(200, 100)).draw(&mut display)?; + + Ok(()) + } +} + +struct Floater { + item: Invuln, +} + +impl Floater { + fn new(item: Invuln) -> Self { + Self { item } + } + + fn draw(&mut self, display: &mut D, t: Duration) -> Result<(), D::Error> + where + D: DrawTarget, + { + let elapsed = t.as_millis() as u32; + + let pos = ((elapsed as f32 / 200.0).sin() - 1.0) / 2.0 * 15.0; + let pos = pos as i32; + + let mut display = display.translated(Point::new(0, pos)); + + self.item.draw(&mut display, t)?; + + Ok(()) + } +} + struct Lerp { is_animating: bool, duration: u32, @@ -305,6 +363,9 @@ fn main() -> Result<(), core::convert::Infallible> { let mut rng = thread_rng(); + let invuln: Bmp = Bmp::from_slice(include_bytes!("./assets/invuln.bmp")).unwrap(); + let mut invuln = Floater::new(Invuln::new(invuln)); + 'running: loop { display.clear(Rgb888::BLACK)?; @@ -315,7 +376,9 @@ fn main() -> Result<(), core::convert::Infallible> { draw_scanners(&mut display, start.elapsed())?; - draw_horizontal_rule(&mut display, start, &mut h_state)?; + draw_horizontal_rule(&mut display, start.elapsed(), &mut h_state)?; + + invuln.draw(&mut display, start.elapsed())?; if !h_state.is_animating { h_state.move_target(start.elapsed().as_millis() as u64, rng.gen_range(0..=120)); diff --git a/eg-next/examples/assets/invuln.bmp b/eg-next/examples/assets/invuln.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b3a5203d486d52006ed0ee7adc6026a865429f36 GIT binary patch literal 5138 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 literal 0 HcmV?d00001 From 1f1610c7caf24fb6f239f784c41f8c8e6590087b Mon Sep 17 00:00:00 2001 From: James Waples Date: Thu, 27 May 2021 20:01:41 +0100 Subject: [PATCH 3/6] Choppa title --- eg-next/examples/choppa.rs | 192 +++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 eg-next/examples/choppa.rs diff --git a/eg-next/examples/choppa.rs b/eg-next/examples/choppa.rs new file mode 100644 index 0000000..a2e6da8 --- /dev/null +++ b/eg-next/examples/choppa.rs @@ -0,0 +1,192 @@ +//! # Example: Analog Clock +//! +//! ![Screenshot of clock example]( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfwAAAH9AQMAAADFwFz1AAAABlBMVEUAAAD///+l2Z/dAAAFM0lEQVR42u3dYWrjMBAF4Afz1zBX8QEEc/WBvdAcQKBNussK3EbVpDNSt+SRYMWWPrBsybQJCK+88sp/EWm32BagN+/EcoBuLRUE8K2wA6B7c9yBN2I9QK3+2eItrS0HWv0L9c9LgV6f+p6lADdcAWq6AuiVO9DRhYBYty571wBc8RGApquApkM4H+A6lvOBpkM6H+A6tvOBpkM8H6A61BcAosMetnSA6olBzpYOsI0B0Wyg4cAgSjUZIPsEgCQDoqCRD7DlAvX2Hlyo414lFSD7HJAsoI/lc3An9xGdBFRMpCYCZDOAJAKsMwBrHiCYCVka0I+MI0GAvw97vSxAMBeyLKBiMjUJIJsFJAjwX8VeMwcQzIYsB6iYTk0ByOYBCQL8V7HXzQAE8yHLAAocSQHMA5QEgNQDcALAPkDjAYEnZPFAhSs1HjAfUMIBUh/A4QB7AY0GBL6QRQMFzoQD5gVKMEBuQKIB9QIcDDC8IY0FxA9YLFDgTjBQ/UCNBcwPlFCA1A9wKMBPAE1DgeI/AwoFxN+LJ1kkUHD655NQoLrntAOokYAB6h9NgQAZnohEAvoMwFHA+9HcHuUCaCCAZwAKBOQ5wOKAcgGU7q/WLoULgB8FXMZSlVqkisq1cAHKDwJIrzdSlXZvdysU6YUrwD8ZqHR/3VKpKeRvgVoawHgOIE0DfrX7S6X35q/bpkoaIM8CFgWUy4G3VnegSr21fSt8BOAnA2+75O9cet9WQG7lNMCeBUoW0B7kBwOkzwIcB/iSD+h2oKwGnv8XMWkSgHM7wNsB2Fqg4OlzsDQAuh0o2wHS3QDO7QCvBGxw7JOUTODYDpDuBlC2A6S7AZzbAd4OwBYB/XL5xxMnA6S7AZTtAG0HYNsB3g7AWDcDR6ubAfoGQFsAtGbPAdLal4Fv0gcjAK1+EfgGN9IXgcK6GVBsBhi7gXM7oDdjK8DYDZxrANioD8dASQYYO4F/bY+NAOlu4MBu4NwNkG4FekvKBUZ/WtkEYJkAYTdwbAesz6x7AMJu4FgJ8INDnwOkeQBhN1B68dwD6FKAdNCHQ4CRBpTtgO4G+DKws4H3T9dzEihpgO4GGKuBMujDIWBZgF6G9nKAsRyQQR+OAAoD+DJXTwOaAxxYCPTCaBLVwYyWAZDuBg5sAGDosXmgpACELUAv4XB8fWwpgO0B5NKHcwAFAtz70ANoIPCvaA6AA4E+O3q+QWfEAf2CeAAJBPAUUCKBCqgPOIAaCRQwfAApLBIQnHD+iOCkUIBJvQCThgIVXgAtFCD1A4xIYDyaPtZLLFD9QB0CbqD4AYsFxA1QMMB+QGOB/nH26coIBswLSDAAN1CigeIFLBoQJ0DhAKvv6coaDZAXQDQA8wElHqg+oMYD4gLI4gF2/daTNR4gH4B4AOYBSgZQPIBlAOIAKALw3En6vm4GQDYPCDIA1Hmg5gAyDZDlAKyzAGsOQDb7fBfkAKizQM0CZBIgywJY5wDWLIBsDhBkAZCp5ztZHsD6ALZxLT/g7UU9LicaALiGE4EulfIAUQD6wVXUfj3ZMgGy2xuPAep9mASgfg7UXEAUxwCAgi0XIMM5Ak4IcgE0nKNb+aSaDbDpaDgfotkAVR1NaUdDBOBfWkj7GeYDVEdAQz6Apo+frlxTgUGls+OpwONaZ7dXAFzfA51eAfR6HehyCOBfdu/o8BIAYleg710DUNP3ADesAnrlDnR0EYBWL0Dfkw90oQPU24cA/uVYqTV8Ffj/1rT9Buv6foO1jb/D+s6vvPKKI78B89G0YRkxkl8AAAAASUVORK5CYII=) +//! +//! This example shows some more advanced usage of Embedded Graphics. It draws a round clock face +//! with hour, minute and second hands. A digital clock is drawn in the middle of the clock. The +//! whole thing is updated with your computer's local time every 50ms. + +use chrono::{Local, Timelike}; +use core::f32::consts::PI; +use embedded_graphics::{ + image::Image, + mono_font::{ + ascii::{FONT_10X20, FONT_6X13, FONT_7X13, FONT_8X13, FONT_9X15}, + MonoTextStyle, + }, + pixelcolor::{Rgb565, Rgb888}, + prelude::*, + primitives::{ + self, Arc, Circle, Line, Polyline, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle, + Sector, Triangle, + }, + text::{Alignment, Baseline, Text, TextStyleBuilder}, +}; +use embedded_graphics_simulator::{ + sdl2::Keycode, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, +}; +use rand::{thread_rng, Rng}; +use std::{ + thread, + time::{Duration, Instant}, +}; +use tinybmp::Bmp; + +fn polar(circle: &Circle, angle: f32, radius: i32) -> Point { + let radius = radius as f32; + + circle.center() + + Point::new( + (angle.sin() * radius) as i32, + -(angle.cos() * radius) as i32, + ) +} + +enum State { + Off, + TitleOpening { start: Duration, offset: u32 }, + TitleIdle, +} + +struct Game { + state: State, +} + +impl Game { + fn new() -> Self { + Self { state: State::Off } + } + + fn update(&mut self, target: &mut D, t: Duration) -> Result<(), D::Error> + where + D: DrawTarget, + { + let new_state = match &mut self.state { + State::Off => Some(State::TitleOpening { + start: t, + offset: 0, + }), + State::TitleOpening { start, offset } => { + if *offset > target.bounding_box().size.height / 2 { + Some(State::TitleIdle) + } else { + Self::draw_title(target, *offset, target.bounding_box().size.height / 2)?; + + *offset = ((t - *start).as_millis() / 10) as u32; + + None + } + } + State::TitleIdle => { + Self::draw_title(target, 0, 0)?; + + let opacity = 128.0 + ((((t.as_millis() / 100) as f32).sin() + 1.0) / 2.0) * 127.0; + + Self::draw_start_button(target, Rgb888::new(0, opacity as u8, 0))?; + + None + } + }; + + if let Some(new_state) = new_state { + self.state = new_state; + } + + Ok(()) + } + + fn reset(&mut self) { + self.state = State::Off + } + + fn draw_start_button(target: &mut D, color: Rgb888) -> Result<(), D::Error> + where + D: DrawTarget, + { + let small_font = MonoTextStyle::new(&FONT_6X13, color); + + Text::with_text_style( + "Press space to start", + target.bounding_box().center() + FONT_10X20.character_size.y_axis(), + small_font, + TextStyleBuilder::new() + .alignment(Alignment::Center) + .baseline(Baseline::Top) + .build(), + ) + .draw(target)?; + + Ok(()) + } + + fn draw_title(target: &mut D, offset: u32, limit: u32) -> Result<(), D::Error> + where + D: DrawTarget, + { + let limit = limit as i32; + + let small_font = MonoTextStyle::new(&FONT_6X13, Rgb888::CSS_LIME); + let big_font = MonoTextStyle::new(&FONT_10X20, Rgb888::CSS_LIME); + + let base = TextStyleBuilder::new().alignment(Alignment::Center); + + let offset = Point::new(0, offset as i32); + + Text::with_text_style( + "GET TO DA", + target.bounding_box().center() - Point::new(0, limit) + offset, + small_font, + base.baseline(Baseline::Bottom).build(), + ) + .draw(target)?; + + Text::with_text_style( + "CHOPPA", + target.bounding_box().center() + Point::new(0, limit) - offset, + big_font, + base.baseline(Baseline::Top).build(), + ) + .draw(target)?; + + Ok(()) + } +} + +fn main() -> Result<(), core::convert::Infallible> { + let mut display = SimulatorDisplay::::new(Size::new(384, 256)); + + let output_settings = OutputSettingsBuilder::new().scale(2).build(); + let mut window = Window::new("CHOPPA", &output_settings); + + let start = Instant::now(); + + let mut game = Game::new(); + + 'running: loop { + display.clear(Rgb888::BLACK)?; + + game.update(&mut display, start.elapsed())?; + + window.update(&display); + + // if window.events().any(|e| e == SimulatorEvent::Quit) { + // break 'running Ok(()); + // } + + for event in window.events() { + match event { + SimulatorEvent::Quit => break 'running Ok(()), + SimulatorEvent::KeyDown { keycode, .. } => { + match keycode { + Keycode::R => game.reset(), + _ => (), // TODO: More input + } + } + + _ => {} + } + } + + thread::sleep(Duration::from_millis(16)); + } +} From c3b2e588d73684b41987dbf2a5c6ff486451abb2 Mon Sep 17 00:00:00 2001 From: James Waples Date: Thu, 27 May 2021 20:14:59 +0100 Subject: [PATCH 4/6] Super simple chopper physics --- eg-next/examples/choppa.rs | 83 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/eg-next/examples/choppa.rs b/eg-next/examples/choppa.rs index a2e6da8..e52910f 100644 --- a/eg-next/examples/choppa.rs +++ b/eg-next/examples/choppa.rs @@ -46,6 +46,7 @@ enum State { Off, TitleOpening { start: Duration, offset: u32 }, TitleIdle, + Running { velocity: i32, position: i32 }, } struct Game { @@ -84,6 +85,13 @@ impl Game { Self::draw_start_button(target, Rgb888::new(0, opacity as u8, 0))?; + None + } + State::Running { velocity, position } => { + *position += *velocity; + + Self::draw_chopper(target, *position)?; + None } }; @@ -95,10 +103,77 @@ impl Game { Ok(()) } + fn key_down(&mut self, keycode: Keycode) { + match keycode { + Keycode::R => self.reset(), + + Keycode::Space => { + let new_state = match &mut self.state { + State::TitleIdle => Some(State::Running { + velocity: 0, + position: 0, + }), + State::Running { velocity, .. } => { + *velocity = 3; + + None + } + _ => None, + }; + + if let Some(new_state) = new_state { + self.state = new_state; + } + } + + _ => (), + } + } + + fn key_up(&mut self, keycode: Keycode) { + match keycode { + Keycode::Space => { + let new_state = match &mut self.state { + State::Running { velocity, .. } => { + *velocity = -5; + + None + } + _ => None, + }; + + if let Some(new_state) = new_state { + self.state = new_state; + } + } + _ => (), + } + } + fn reset(&mut self) { self.state = State::Off } + fn draw_chopper(target: &mut D, position: i32) -> Result<(), D::Error> + where + D: DrawTarget, + { + Circle::with_center( + Point::new(20, target.bounding_box().center().y - position), + 11, + ) + .into_styled( + PrimitiveStyleBuilder::new() + .stroke_width(1) + .stroke_color(Rgb888::RED) + .fill_color(Rgb888::WHITE) + .build(), + ) + .draw(target)?; + + Ok(()) + } + fn draw_start_button(target: &mut D, color: Rgb888) -> Result<(), D::Error> where D: DrawTarget, @@ -177,10 +252,10 @@ fn main() -> Result<(), core::convert::Infallible> { match event { SimulatorEvent::Quit => break 'running Ok(()), SimulatorEvent::KeyDown { keycode, .. } => { - match keycode { - Keycode::R => game.reset(), - _ => (), // TODO: More input - } + game.key_down(keycode); + } + SimulatorEvent::KeyUp { keycode, .. } => { + game.key_up(keycode); } _ => {} From 661603bd008ca72c64a431f6cc4fb4bdc35164f1 Mon Sep 17 00:00:00 2001 From: James Waples Date: Thu, 27 May 2021 20:39:49 +0100 Subject: [PATCH 5/6] Chopper sprite --- eg-next/examples/assets/chopper.bmp | Bin 0 -> 3338 bytes eg-next/examples/assets/chopper.xcf | Bin 0 -> 3087 bytes eg-next/examples/choppa.rs | 103 +++++++++++++++++++++------- 3 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 eg-next/examples/assets/chopper.bmp create mode 100644 eg-next/examples/assets/chopper.xcf diff --git a/eg-next/examples/assets/chopper.bmp b/eg-next/examples/assets/chopper.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c5839508bfe3f0b248e6552ff7e2015993c1d27b GIT binary patch literal 3338 zcmcgpF-`+Wzbo7+9- zdz>&=ZLx8}mNM)438wK3YGbnq1y}9=Nu+H2EJ}k4ipUV5Gj8^jGdC19_~p4|Wq0_` ziRO%PeNOZVjT6*at>T1Lm)H3T#(M^}u~~$ItM)f0QZ{}Tr9lNnWQfoiH+#yN8;TnI z@?55hrvsgK?xFvb2{ZTX4w)|JOIW!s@Av_x@(gNYvj_!O?Qcw^Z2T-rg9?hs5TP?} z_LMU>6gBweId>J$9ntFPqYrr1JN~<7jT6*at>T1Lm)H3T#(M^}u~~$ItM)f0QZ{}T zr9lNnWQfoiH+#yN8;TnI@|>@VCvKtRcbK`4yQrAC!LiSr>2jV<%egM^_z9-+3~FPu n2nAQ|Z%m|Y{47d?3W~@Op)+pwlruLJHTdN@M-|UnMWy@#>XL_6 literal 0 HcmV?d00001 diff --git a/eg-next/examples/assets/chopper.xcf b/eg-next/examples/assets/chopper.xcf new file mode 100644 index 0000000000000000000000000000000000000000..48e6186e426c2d6d1d1744531f379a8ff1efb7e6 GIT binary patch literal 3087 zcmds3J8u&~5MG}ze#g!W1Q7}s2;fAr6!YpJ53SHnx@~i`ZmU-7*q*h@<{A#+pN%Bo)Eg;d%n5F4Hq^3@uq zpBVjJ<@b%c$cdD^FmR9iGvvpv3+9reg@L($^W(>^acItydk`>miJoy|h`Tr{L+TuI zU66@yp$$s4^5_gD)E&a0iUB6(gVreg_yI1Dyj&W%%L5oPydZaIhKvq}=3ID>1Pld# zMB~N~Z>mulVxA_+r&>j=JYp#MvQG@Imy<_^tJ{1u1hJV{Kz zQ1E6LH-uCDt0r>#aMg85fsV3PEsHhc2vFs16 Rfr`PE>s?29_DEg{zX4_TVpISC literal 0 HcmV?d00001 diff --git a/eg-next/examples/choppa.rs b/eg-next/examples/choppa.rs index e52910f..db2226e 100644 --- a/eg-next/examples/choppa.rs +++ b/eg-next/examples/choppa.rs @@ -49,13 +49,57 @@ enum State { Running { velocity: i32, position: i32 }, } +struct ChopperSprite { + image: Bmp<'static, Rgb565>, +} + +impl ChopperSprite { + const SIZE: Size = Size::new(31, 10); + + fn new(image: Bmp<'static, Rgb565>) -> Self { + Self { image } + } + + fn draw(&mut self, display: &mut D, t: Duration, position: Point) -> Result<(), D::Error> + where + D: DrawTarget, + { + let elapsed = t.as_millis() as u32; + + let index = (elapsed / 30) % 9; + + let index = if index >= 5 { + // Reverse + 9 - index + } else { + // Forward + index + }; + + let mut display = display.color_converted(); + + let frame = self.image.sub_image(&Rectangle::new( + Point::new(0, Self::SIZE.height as i32 * index as i32), + Self::SIZE, + )); + + Image::new(&frame, position).draw(&mut display)?; + + Ok(()) + } +} + struct Game { state: State, + chopper: ChopperSprite, } impl Game { - fn new() -> Self { - Self { state: State::Off } + fn new(chopper: ChopperSprite) -> Self { + Self { + state: State::Off, + chopper, + } } fn update(&mut self, target: &mut D, t: Duration) -> Result<(), D::Error> @@ -73,7 +117,7 @@ impl Game { } else { Self::draw_title(target, *offset, target.bounding_box().size.height / 2)?; - *offset = ((t - *start).as_millis() / 10) as u32; + *offset = ((t - *start).as_millis() / 8) as u32; None } @@ -88,9 +132,13 @@ impl Game { None } State::Running { velocity, position } => { - *position += *velocity; + // *position += *velocity; - Self::draw_chopper(target, *position)?; + self.chopper.draw( + target, + t, + target.bounding_box().center().y_axis() + Point::new(15, *position), + )?; None } @@ -154,25 +202,27 @@ impl Game { self.state = State::Off } - fn draw_chopper(target: &mut D, position: i32) -> Result<(), D::Error> - where - D: DrawTarget, - { - Circle::with_center( - Point::new(20, target.bounding_box().center().y - position), - 11, - ) - .into_styled( - PrimitiveStyleBuilder::new() - .stroke_width(1) - .stroke_color(Rgb888::RED) - .fill_color(Rgb888::WHITE) - .build(), - ) - .draw(target)?; - - Ok(()) - } + // fn draw_chopper(target: &mut D, position: i32) -> Result<(), D::Error> + // where + // D: DrawTarget, + // { + // // Circle::with_center( + // // Point::new(20, target.bounding_box().center().y - position), + // // 11, + // // ) + // // .into_styled( + // // PrimitiveStyleBuilder::new() + // // .stroke_width(1) + // // .stroke_color(Rgb888::RED) + // // .fill_color(Rgb888::WHITE) + // // .build(), + // // ) + // // .draw(target)?; + + // self.chopper.draw(target); + + // Ok(()) + // } fn draw_start_button(target: &mut D, color: Rgb888) -> Result<(), D::Error> where @@ -235,7 +285,10 @@ fn main() -> Result<(), core::convert::Infallible> { let start = Instant::now(); - let mut game = Game::new(); + let chopper: Bmp = Bmp::from_slice(include_bytes!("./assets/chopper.bmp")).unwrap(); + let chopper = ChopperSprite::new(chopper); + + let mut game = Game::new(chopper); 'running: loop { display.clear(Rgb888::BLACK)?; From cb564da4eef494a79a47b892c471b248d67f486b Mon Sep 17 00:00:00 2001 From: James Waples Date: Thu, 27 May 2021 21:42:22 +0100 Subject: [PATCH 6/6] Basic random stage generation --- eg-next/examples/choppa.rs | 89 ++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 23 deletions(-) diff --git a/eg-next/examples/choppa.rs b/eg-next/examples/choppa.rs index db2226e..bfad980 100644 --- a/eg-next/examples/choppa.rs +++ b/eg-next/examples/choppa.rs @@ -89,9 +89,71 @@ impl ChopperSprite { } } +const NUM_POINTS: usize = 5; + +struct Stage { + position: u32, + + /// `(offset, height)` + points: [(i32, u32); NUM_POINTS], +} + +impl Stage { + fn new() -> Self { + let mut rng = thread_rng(); + + let mut points = [(0, 0); NUM_POINTS]; + + (0..NUM_POINTS).for_each(|idx| { + let offset = rng.gen_range(-50i32..=50); + let height = rng.gen_range(100u32..=150); + + points[idx] = (offset, height) + }); + + Self { + position: 0, + points, + } + } + + fn draw(&mut self, display: &mut D, t: Duration) -> Result<(), D::Error> + where + D: DrawTarget, + { + display.clear(Rgb888::CSS_LIME)?; + + let bb = display.bounding_box(); + + let spacing = bb.size.width / (NUM_POINTS - 2) as u32; + + let center = bb.center(); + + for (idx, parts) in self.points.windows(2).enumerate() { + if let [(offs_1, height_1), (offs_2, height_2)] = parts { + let x1 = (idx as u32 * spacing) as i32; + let x2 = ((idx as u32 + 1) * spacing) as i32; + + let a = Point::new(x1, center.y + offs_1 - *height_1 as i32 / 2); + let b = Point::new(x1, center.y + offs_1 + *height_1 as i32 / 2); + let c = Point::new(x2, center.y + offs_2 - *height_2 as i32 / 2); + let d = Point::new(x2, center.y + offs_2 + *height_2 as i32 / 2); + + let style = PrimitiveStyle::with_fill(Rgb888::BLACK); + + Triangle::new(a, b, c).into_styled(style).draw(display)?; + Triangle::new(b, c, d).into_styled(style).draw(display)?; + } + } + + Ok(()) + } +} + struct Game { state: State, chopper: ChopperSprite, + stage: Stage, } impl Game { @@ -99,6 +161,7 @@ impl Game { Self { state: State::Off, chopper, + stage: Stage::new(), } } @@ -132,7 +195,9 @@ impl Game { None } State::Running { velocity, position } => { - // *position += *velocity; + // *position -= *velocity; + + self.stage.draw(target, t)?; self.chopper.draw( target, @@ -202,28 +267,6 @@ impl Game { self.state = State::Off } - // fn draw_chopper(target: &mut D, position: i32) -> Result<(), D::Error> - // where - // D: DrawTarget, - // { - // // Circle::with_center( - // // Point::new(20, target.bounding_box().center().y - position), - // // 11, - // // ) - // // .into_styled( - // // PrimitiveStyleBuilder::new() - // // .stroke_width(1) - // // .stroke_color(Rgb888::RED) - // // .fill_color(Rgb888::WHITE) - // // .build(), - // // ) - // // .draw(target)?; - - // self.chopper.draw(target); - - // Ok(()) - // } - fn draw_start_button(target: &mut D, color: Rgb888) -> Result<(), D::Error> where D: DrawTarget,