Compare commits

...
2 Commits
Author SHA1 Message Date
melfely ae15968417 okay, code complete enough. Done with it now. 2026-09-18 19:10:43 -05:00
melfely 147969056c the code works. 99%. It makes a gif, not a video.
Video in rust is a GIANT pain. A gif gets the same point across.
2026-09-18 18:50:58 -05:00
3 changed files with 1057 additions and 61 deletions
+840 -2
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -5,5 +5,6 @@ edition = "2024"
[dependencies] [dependencies]
anyhow = "1.0.104" anyhow = "1.0.104"
image = "0.25.10"
minifb = "0.28.0" minifb = "0.28.0"
nalgebra = "0.35.0" nalgebra = "0.35.0"
+216 -59
View File
@@ -195,12 +195,12 @@ fn init() -> AnyResult<ng::Matrix3x4<f64>> {
} }
//Returns (u,v) //Returns (u,v)
fn convert_3d_to_2d(m: &ng::Matrix3x4<f64>, x: f64, y: f64, z: f64) -> (i32, i32) { fn convert_3d_to_2d(m: &ng::Matrix3x4<f64>, point: &Point3d) -> Point2d {
let dim_3 = ng::Matrix4x1::new(x, y, z, 1.0); let dim_3 = ng::Matrix4x1::new(point.x, point.y, point.z, 1.0);
let dim_2 = m * dim_3; let dim_2 = m * dim_3;
((dim_2[0] / dim_2[2]) as i32, (dim_2[1] / dim_2[2]) as i32) Point2d::new((dim_2[0] / dim_2[2]) as i32, (dim_2[1] / dim_2[2]) as i32)
} }
const WINDOW_HEIGHT: usize = 480; const WINDOW_HEIGHT: usize = 480;
@@ -216,47 +216,28 @@ fn main() -> AnyResult<()> {
fb::WindowOptions::default(), fb::WindowOptions::default(),
)?; )?;
let mut x = Vec::new(); let mut points_3d = Vec::new();
let mut y = Vec::new();
let mut z = Vec::new();
for i in 0..(10 * 10) { for i in 0..(10 * 10) {
// Constant 0 points_3d.push(Point3d::new((i / 10) as f64, (i % 10) as f64, 0.0));
z.push(0);
// x will count up, the reset every 10
x.push(i % 10);
//y will only count up once every 10
y.push(i / 10);
} }
for i in 0..(10 * 10) { for i in 0..(10 * 10) {
// Constant 10 points_3d.push(Point3d::new((i % 10) as f64, 10.0, (i / 10) as f64));
y.push(10);
// x will count up, the reset every 10
x.push(i % 10);
//z will only count up once every 10
z.push(i / 10);
} }
for i in 0..(10 * 10) { for i in 0..(10 * 10) {
// Constant 10 points_3d.push(Point3d::new(10.0, (i % 10) as f64, (i / 10) as f64));
x.push(10);
// z will count up, the reset every 10
z.push(i % 10);
//y will only count up once every 10
y.push(i / 10);
} }
let (u, v) = x let points = points_3d
.into_iter() .into_iter()
.zip(y) .map(|point| convert_3d_to_2d(&m, &point))
.zip(z) .collect::<Vec<Point2d>>();
.map(|((x, y), z)| convert_3d_to_2d(&m, x as f64, y as f64, z as f64))
.collect::<(Vec<i32>, Vec<i32>)>();
let mut buffer = vec![0 as u32; WINDOW_WIDTH * WINDOW_HEIGHT]; let mut buffer = vec![0 as u32; WINDOW_WIDTH * WINDOW_HEIGHT];
update_buffer_with_pixels(&mut buffer, u, v); update_buffer_with_pixels(&mut buffer, points);
//Clone this, so we can clone this back into buffer later, for each frame. So we can keep the BG pixels. //Clone this, so we can clone this back into buffer later, for each frame. So we can keep the BG pixels.
let background_buffer = buffer.clone(); let background_buffer = buffer.clone();
@@ -264,29 +245,52 @@ fn main() -> AnyResult<()> {
window.update_with_buffer(&buffer, WINDOW_WIDTH, WINDOW_HEIGHT)?; window.update_with_buffer(&buffer, WINDOW_WIDTH, WINDOW_HEIGHT)?;
window.set_target_fps(30); window.set_target_fps(30);
let mut frame = 0.0; let trb = Point3d::new(1.0, 1.0, 1.0);
let trf = Point3d::new(0.0, 1.0, 1.0);
let tlb = Point3d::new(1.0, 1.0, 0.0);
let tlf = Point3d::new(0.0, 1.0, 0.0);
let blf = Point3d::new(0.0, 0.0, 0.0);
let blb = Point3d::new(1.0, 0.0, 0.0);
let brf = Point3d::new(0.0, 0.0, 1.0);
let brb = Point3d::new(1.0, 0.0, 1.0);
let mut cube = Cube::new(trb, trf, tlb, tlf, blf, blb, brf, brb);
let mut gif_buffer: Vec<Vec<u8>> = Vec::new();
let mut x_trans = 0.0;
let mut y_trans = 0.0;
let mut z_trans = 0.0;
const X_TRANS_RATE: f64 = 1.0 / 30.0;
const Y_TRANS_RATE: f64 = 0.5 / 30.0;
const Z_TRANS_RATE: f64 = 2.0 / 30.0;
const MAX_TRANS: f64 = 6.0;
while window.is_open() { while window.is_open() {
//Reload the background for each frame //Reload the background for each frame
buffer = background_buffer.clone(); let mut buffer = background_buffer.clone();
let u = vec![0; 0]; cube.translate(Point3d::new(X_TRANS_RATE, Y_TRANS_RATE, Z_TRANS_RATE));
let v = vec![0; 0];
let p1 = (2.0 + (frame / 30.0), 2.0, 2.0); update_buffer_with_pixels(&mut buffer, cube.render(&m));
let p2 = (8.0 + (frame / 30.0), 8.0, 8.0);
let p1 = convert_3d_to_2d(&m, p1.0, p1.1, p1.2); x_trans += X_TRANS_RATE;
let p2 = convert_3d_to_2d(&m, p2.0, p2.1, p2.2); y_trans += Y_TRANS_RATE;
z_trans += Z_TRANS_RATE;
let line = generate_line_between_verticies(p1.0, p1.1, p2.0, p2.1); if x_trans > MAX_TRANS {
cube.translate(Point3d::new(-x_trans, 0.0, 0.0));
update_buffer_with_pixels(&mut buffer, line.0, line.1); x_trans = 0.0;
}
frame += 1.0; if y_trans > MAX_TRANS {
cube.translate(Point3d::new(0.0, -y_trans, 0.0));
if frame > 150.0 { y_trans = 0.0;
frame = 0.0; }
if z_trans > MAX_TRANS {
cube.translate(Point3d::new(0.0, 0.0, -z_trans));
z_trans = 0.0;
} }
//Required to close, and update frame; //Required to close, and update frame;
@@ -294,32 +298,185 @@ fn main() -> AnyResult<()> {
break; break;
} }
window.update_with_buffer(&buffer, WINDOW_WIDTH, WINDOW_HEIGHT)?; window.update_with_buffer(&buffer, WINDOW_WIDTH, WINDOW_HEIGHT)?;
gif_buffer.push(
buffer
.iter()
.flat_map(|pixel| pixel.to_be_bytes())
.collect(),
);
} }
export_gif(WINDOW_WIDTH, WINDOW_HEIGHT, 30, gif_buffer)?;
Ok(()) Ok(())
} }
fn update_buffer_with_pixels(buf: &mut Vec<u32>, u: Vec<i32>, v: Vec<i32>) { ///A Cube defined by 8 3d points.
u.into_iter().zip(v).for_each(|(x, y)| { #[derive(Debug)]
let index = (y as usize) * WINDOW_WIDTH + (x as usize); struct Cube {
pub trb: Point3d,
pub trf: Point3d,
pub tlb: Point3d,
pub tlf: Point3d,
pub blf: Point3d,
pub blb: Point3d,
pub brf: Point3d,
pub brb: Point3d,
}
impl Cube {
pub fn new(
trb: Point3d,
trf: Point3d,
tlb: Point3d,
tlf: Point3d,
blf: Point3d,
blb: Point3d,
brf: Point3d,
brb: Point3d,
) -> Self {
Cube {
trb,
trf,
tlb,
tlf,
blf,
blb,
brf,
brb,
}
}
pub fn render(&self, m: &ng::Matrix3x4<f64>) -> Vec<Point2d> {
let mut points = Vec::new();
let trb = self.trb.project(m);
let trf = self.trf.project(m);
let tlb = self.tlb.project(m);
let tlf = self.tlf.project(m);
let blf = self.blf.project(m);
let blb = self.blb.project(m);
let brf = self.brf.project(m);
let brb = self.brb.project(m);
points.append(&mut generate_line_between_verticies(&trf, &trb)); // Line 1
points.append(&mut generate_line_between_verticies(&trb, &tlb)); // Line 2
points.append(&mut generate_line_between_verticies(&tlb, &tlf)); // Line 3
points.append(&mut generate_line_between_verticies(&tlf, &trf)); // Line 4
points.append(&mut generate_line_between_verticies(&blf, &brf)); // Line 5
points.append(&mut generate_line_between_verticies(&brf, &brb)); // Line 6
points.append(&mut generate_line_between_verticies(&brb, &blb)); // Line 7
points.append(&mut generate_line_between_verticies(&blb, &blf)); // Line 8
points.append(&mut generate_line_between_verticies(&blf, &tlf)); // Line 9
points.append(&mut generate_line_between_verticies(&blb, &tlb)); // Line 10
points.append(&mut generate_line_between_verticies(&brb, &trb)); // Line 11
points.append(&mut generate_line_between_verticies(&brf, &trf)); // Line 12
points
}
///translates the cube by Point amount
pub fn translate(&mut self, translate_by: Point3d) {
self.trb.translate(&translate_by);
self.trf.translate(&translate_by);
self.tlb.translate(&translate_by);
self.tlf.translate(&translate_by);
self.blf.translate(&translate_by);
self.blb.translate(&translate_by);
self.brf.translate(&translate_by);
self.brb.translate(&translate_by);
}
}
#[derive(Debug)]
struct Point3d {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Point3d {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Point3d { x, y, z }
}
pub fn project(&self, m: &ng::Matrix3x4<f64>) -> Point2d {
convert_3d_to_2d(m, self)
}
pub fn translate(&mut self, translate_by: &Point3d) {
self.x += translate_by.x;
self.y += translate_by.y;
self.z += translate_by.z;
}
}
#[derive(Debug)]
struct Point2d {
pub x: i32,
pub y: i32,
}
impl Point2d {
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
fn update_buffer_with_pixels(buf: &mut Vec<u32>, points: Vec<Point2d>) {
points.into_iter().for_each(|point| {
let index = (point.y as usize) * WINDOW_WIDTH + (point.x as usize);
buf[index] = u32::MAX buf[index] = u32::MAX
}); });
} }
fn generate_line_between_verticies(u_1: i32, v_1: i32, u_2: i32, v_2: i32) -> (Vec<i32>, Vec<i32>) { fn generate_line_between_verticies(point_1: &Point2d, point_2: &Point2d) -> Vec<Point2d> {
let mut u = vec![0; 0]; let mut points = Vec::new();
let mut v = vec![0; 0];
let u_diff = u_2 - u_1; let x_diff = point_2.x - point_1.x;
let v_diff = v_2 - v_1; let y_diff = point_2.y - point_1.y;
let u_change = u_diff / 100; let x_change = x_diff as f64 / 100.0;
let v_change = v_diff / 100; let y_change = y_diff as f64 / 100.0;
for i in 0..100 { for i in 0..100 {
u.push(u_1 + (i * u_change)); points.push(Point2d::new(
v.push(v_1 + (i * v_change)); point_1.x + (i as f64 * x_change) as i32,
point_1.y + (i as f64 * y_change) as i32,
));
} }
(u, v) points
}
use image::codecs::gif::{GifEncoder, Repeat};
use image::{Delay, Frame, RgbaImage};
fn export_gif(
width: usize,
height: usize,
fps: u32,
frames: Vec<Vec<u8>>, // Each inner Vec is width * height * 4 bytes (RGBA)
) -> AnyResult<()> {
let file = File::create("animation.gif")?;
let mut encoder = GifEncoder::new(file);
// Loop forever
encoder.set_repeat(Repeat::Infinite)?;
for raw_buffer in frames {
// Construct the image from your raw pixel slice
let img = RgbaImage::from_raw(width as u32, height as u32, raw_buffer).unwrap();
// Calculate frame timing (e.g., 1000 / 30 = 33.3ms)
let delay = Delay::from_numer_denom_ms(1000, fps);
let frame = Frame::from_parts(img, 0, 0, delay);
encoder.encode_frame(frame)?;
}
Ok(())
} }