Rotating cube

This commit is contained in:
2025-11-18 14:59:08 +01:00
parent 9d265da31b
commit b4e1a0d7f3
3 changed files with 186 additions and 43 deletions

131
Cargo.lock generated
View File

@@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -14,6 +23,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]]
name = "bytemuck"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -59,6 +74,7 @@ dependencies = [
"embedded-graphics",
"embedded-graphics-core",
"memmap2",
"nalgebra",
]
[[package]]
@@ -73,6 +89,16 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "matrixmultiply"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
dependencies = [
"autocfg",
"rawpointer",
]
[[package]]
name = "memmap2"
version = "0.9.9"
@@ -125,6 +151,61 @@ dependencies = [
"syn",
]
[[package]]
name = "nalgebra"
version = "0.32.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4"
dependencies = [
"approx",
"matrixmultiply",
"nalgebra-macros",
"num-complex",
"num-rational",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "nalgebra-macros"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -151,6 +232,12 @@ dependencies = [
"oc2r-core",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "proc-macro2"
version = "1.0.103"
@@ -169,12 +256,40 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "safe_arch"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323"
dependencies = [
"bytemuck",
]
[[package]]
name = "simba"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae"
dependencies = [
"approx",
"num-complex",
"num-traits",
"paste",
"wide",
]
[[package]]
name = "syn"
version = "2.0.108"
@@ -186,8 +301,24 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "wide"
version = "0.7.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03"
dependencies = [
"bytemuck",
"safe_arch",
]

View File

@@ -15,4 +15,4 @@ workspace = true
memmap2 = "0.9"
embedded-graphics = "0.8"
embedded-graphics-core = "0.4"
nalgebra = "0.32"

View File

@@ -3,16 +3,18 @@
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
use std::{convert::Infallible, fs::OpenOptions, io, thread, time::Duration};
use std::{fs::OpenOptions, io, thread, time::Duration};
use embedded_graphics::{
geometry::Size,
mono_font::{MonoTextStyle, ascii::FONT_6X10},
pixelcolor::Rgb565,
prelude::*,
text::{Baseline, Text},
prelude::{
DrawTarget, Drawable, IntoStorage, OriginDimensions, Pixel, Point, Primitive, RgbColor,
},
primitives::{Line, PrimitiveStyle},
};
use memmap2::{MmapMut, MmapOptions};
use nalgebra::{Point3, Rotation3, Vector3};
const FB_PATH: &str = "/dev/fb0";
const WIDTH: u32 = 640;
@@ -25,39 +27,27 @@ struct FbDisplay {
impl FbDisplay {
fn new() -> io::Result<Self> {
println!("Opening framebuffer device at {FB_PATH}");
let file = OpenOptions::new().read(true).write(true).open(FB_PATH)?;
let fb_size = (WIDTH as usize) * (HEIGHT as usize) * BYTES_PER_PIXEL;
// Safety: map /dev/fb0 as writable memory
let fb = unsafe { MmapOptions::new().len(fb_size).map_mut(&file)? };
println!(
"Mapped framebuffer: {}x{} at {} bytes ({} bytes per pixel)",
WIDTH,
HEIGHT,
fb.len(),
BYTES_PER_PIXEL
);
Ok(Self { fb })
}
fn clear(&mut self) {
println!("Clearing framebuffer to black");
for chunk in self.fb.chunks_exact_mut(BYTES_PER_PIXEL) {
chunk.copy_from_slice(&[0x00, 0x00]); // RGB565 black
}
self.fb.fill(0);
}
fn put_pixel(&mut self, x: u32, y: u32, color: Rgb565) {
if x >= WIDTH || y >= HEIGHT {
fn put_pixel(&mut self, x: i32, y: i32, color: Rgb565) {
if x < 0 || y < 0 || x as u32 >= WIDTH || y as u32 >= HEIGHT {
return;
}
let idx = ((y * WIDTH + x) as usize) * BYTES_PER_PIXEL;
let idx = ((y as u32 * WIDTH + x as u32) as usize) * BYTES_PER_PIXEL;
if idx + 1 >= self.fb.len() {
return;
}
// Rgb565 stores 16-bit value; little endian in memory.
let raw: u16 = color.into_storage();
let bytes = raw.to_le_bytes();
self.fb[idx] = bytes[0];
@@ -67,18 +57,14 @@ impl FbDisplay {
impl DrawTarget for FbDisplay {
type Color = Rgb565;
type Error = Infallible;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(coord, color) in pixels {
let x = coord.x;
let y = coord.y;
if x >= 0 && y >= 0 {
self.put_pixel(x as u32, y as u32, color);
}
self.put_pixel(coord.x, coord.y, color);
}
Ok(())
}
@@ -94,26 +80,52 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut fb = FbDisplay::new()?;
fb.clear();
let style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
let mut cube = [
Point3::new(-1.0, -1.0, -1.0),
Point3::new(1.0, -1.0, -1.0),
Point3::new(1.0, 1.0, -1.0),
Point3::new(-1.0, 1.0, -1.0),
Point3::new(-1.0, -1.0, 1.0),
Point3::new(1.0, -1.0, 1.0),
Point3::new(1.0, 1.0, 1.0),
Point3::new(-1.0, 1.0, 1.0),
];
let text = "Hello embedded-graphics on RGB565";
let scale = 60.0;
for edge in &mut cube {
*edge *= scale;
}
let text_width = (text.len() as u32) * 6;
let text_height = 10;
let angle_step = 0.06f32;
let rot_step = Rotation3::from_axis_angle(&Vector3::x_axis(), angle_step)
* Rotation3::from_axis_angle(&Vector3::y_axis(), angle_step * 0.8)
* Rotation3::from_axis_angle(&Vector3::z_axis(), angle_step * 0.6);
let x = (WIDTH - text_width) / 2;
let y = (HEIGHT / 2) + (text_height / 2);
let mut projected = [Point::new(0, 0); 8];
let center_x = (WIDTH / 2) as i32;
let center_y = (HEIGHT / 2) as i32;
let style = PrimitiveStyle::with_stroke(Rgb565::RED, 1);
Text::with_baseline(
text,
Point::new(x as i32, y as i32),
style,
Baseline::Bottom,
)
.draw(&mut fb)?;
// Keep the framebuffer content visible
loop {
thread::sleep(Duration::from_secs(1));
fb.clear();
for (i, edge) in cube.iter_mut().enumerate() {
*edge = rot_step * *edge;
projected[i] = Point::new(edge.x as i32 + center_x, edge.y as i32 + center_y);
}
for i in 0..4 {
Line::new(projected[i], projected[(i + 1) % 4])
.into_styled(style)
.draw(&mut fb)?;
Line::new(projected[i + 4], projected[((i + 1) % 4) + 4])
.into_styled(style)
.draw(&mut fb)?;
Line::new(projected[i], projected[i + 4])
.into_styled(style)
.draw(&mut fb)?;
}
thread::sleep(Duration::from_millis(40));
}
}