fix 3d wireframe render

This commit is contained in:
sfja 2026-03-26 16:29:08 +01:00
parent 95d4c97709
commit d738478f54
7 changed files with 276 additions and 196 deletions

View File

@ -1,6 +1,4 @@
use sdl3::pixels::Color; use crate::engine::math::{V2, V3};
use crate::engine::math::{Vertex, V2, V3};
use std::{marker::PhantomData, time::Duration}; use std::{marker::PhantomData, time::Duration};
use super::error::Error; use super::error::Error;
@ -16,10 +14,15 @@ pub trait Io<R: Renderer, O: Object<R>> {
pub trait Renderer { pub trait Renderer {
fn draw_rect(&mut self, pos: V2, size: V2, color: Color); fn draw_rect(&mut self, pos: V2, size: V2, color: Color);
fn point(&mut self, pos: V2, color: Color); fn draw_point(&mut self, pos: V2, color: Color);
fn draw_line(&mut self, from: V2, to: V2, color: Color); fn draw_line(&mut self, from: V2, to: V2, color: Color);
fn draw_cube(&mut self, pos: V3, size: V3, outline_colors: Color, fill_color: Color); }
fn draw_triangle(&mut self, triangle: Vertex, color: Color);
#[derive(Clone, Copy)]
pub enum Color {
WHITE,
GREEN,
WED,
} }
pub enum Event { pub enum Event {

View File

@ -1,4 +1,4 @@
use std::ops::{Add, AddAssign, Mul, MulAssign}; use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
pub struct V2(pub f64, pub f64); pub struct V2(pub f64, pub f64);
@ -6,17 +6,34 @@ pub struct V2(pub f64, pub f64);
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
pub struct V3(pub f64, pub f64, pub f64); pub struct V3(pub f64, pub f64, pub f64);
impl V3 {
pub fn project_2d(&self) -> V2 {
// V2(self.0 / self.2, self.1 / self.2)
let c = V3(0.0, 0.0, -1.0);
let d = *self - c;
let e = V3(0.0, 0.0, 0.0) - c;
V2(e.2 / d.2 * d.0 + e.0, e.2 / d.2 * d.1 + e.1)
}
pub fn cross(&self, rhs: Self) -> Self {
let V3(ax, ay, az) = self;
let V3(bx, by, bz) = rhs;
Self(ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx)
}
}
impl Add for V3 { impl Add for V3 {
type Output = V3; type Output = Self;
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
} }
} }
impl Sub for V3 {
type Output = Self;
impl AddAssign for V3 { fn sub(self, rhs: Self) -> Self::Output {
fn add_assign(&mut self, rhs: Self) { Self(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2)
*self = *self + rhs;
} }
} }
@ -28,55 +45,53 @@ impl Mul<f64> for V3 {
} }
} }
impl Add<&Self> for V3 {
type Output = Self;
fn add(self, rhs: &Self) -> Self::Output {
Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
}
}
impl Sub<&Self> for V3 {
type Output = Self;
fn sub(self, rhs: &Self) -> Self::Output {
Self(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2)
}
}
impl AddAssign for V3 {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl SubAssign for V3 {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl MulAssign<f64> for V3 { impl MulAssign<f64> for V3 {
fn mul_assign(&mut self, rhs: f64) { fn mul_assign(&mut self, rhs: f64) {
*self = *self * rhs; *self = *self * rhs;
} }
} }
impl V3 {
pub fn project(&self) -> V2 {
V2(self.0 / self.2, self.1 / self.2)
}
pub fn delta(&self, v: V3) -> V3 {
V3(v.0 - self.0, v.1 - self.1, v.2 - self.2)
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Vertex(pub V3, pub V3, pub V3);
impl Vertex {
pub fn normal_vector(&self) -> V3 {
let vector_a = self.1.delta(self.0);
let vector_b = self.1.delta(self.2);
V3(
vector_a.1 * vector_b.2 - vector_a.2 * vector_b.1,
vector_a.2 * vector_b.0 - vector_a.0 * vector_b.2,
vector_a.0 * vector_b.1 - vector_a.1 * vector_b.0,
)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
#[test] #[test]
fn v3_ops() { fn v3_ops() {
let v2 = |v: f64| V2(v, v);
let v3 = |v: f64| V3(v, v, v); let v3 = |v: f64| V3(v, v, v);
assert_eq!(v3(1.0) + v3(2.0), v3(3.0)); assert_eq!(v3(1.0) + v3(2.0), v3(3.0));
assert_eq!(v3(3.0) - v3(2.0), v3(1.0));
assert_eq!(v3(2.0) * 2.0, v3(4.0));
let mut a = v3(1.0); let mut a = v3(1.0);
a += v3(2.0); a += v3(2.0);
assert_eq!(a, v3(3.0)); assert_eq!(a, v3(3.0));
assert_eq!(v3(2.0) * 2.0, v3(4.0));
let mut a = v3(2.0); let mut a = v3(2.0);
a *= 2.0; a *= 2.0;
assert_eq!(a, v3(4.0)); assert_eq!(a, v3(4.0));

View File

@ -1,10 +1,17 @@
#![allow(unused_imports)]
mod error; mod error;
mod game; mod game;
pub mod math; mod math;
mod r3d;
mod sdl_io; mod sdl_io;
pub mod shapes; mod shapes;
mod system; mod system;
pub use game::{Event, Game, Io, Object, Renderer}; pub use error::*;
pub use math::V3; pub use game::*;
pub use sdl_io::SdlIo; pub use math::*;
pub use r3d::*;
pub use sdl_io::*;
pub use shapes::*;
pub use system::*;

68
game/src/engine/r3d.rs Normal file
View File

@ -0,0 +1,68 @@
use crate::engine::{game::Renderer, Color, Shape, V2, V3};
#[derive(Clone, PartialEq, Debug)]
pub struct Triangle2(pub V2, pub V2, pub V2);
#[derive(Clone, PartialEq, Debug)]
pub struct Triangle3(pub V3, pub V3, pub V3);
impl Triangle3 {
pub fn normal_vector(&self) -> V3 {
(self.1 - self.0).cross(self.2 - self.1)
}
pub fn translate(&self, offset: V3) -> Self {
Self(self.0 + offset, self.1 + offset, self.2 + offset)
}
pub fn project_2d(&self) -> Triangle2 {
Triangle2(
self.0.project_2d(),
self.1.project_2d(),
self.2.project_2d(),
)
}
}
pub struct R3d<'r, R: Renderer> {
r: &'r mut R,
}
static CAMERA_POS: V3 = V3(0.0, 0.0, -1.0);
impl<'r, R: Renderer> R3d<'r, R> {
pub fn new(r: &'r mut R) -> Self {
Self { r }
}
pub fn draw_line(&mut self, from: V3, to: V3, color: Color) {
self.r
.draw_line((from).project_2d(), (to).project_2d(), color);
}
pub fn draw_triangle(&mut self, triangle: Triangle3, color: Color) {
let triangle = triangle.translate(CAMERA_POS).project_2d();
self.r.draw_line(triangle.0, triangle.1, color);
self.r.draw_line(triangle.1, triangle.2, color);
self.r.draw_line(triangle.2, triangle.0, color);
}
pub fn draw_cube(&mut self, pos: V3, size: V3, outline_color: Color, fill_color: Color) {
let shape = Shape::new_cube(size);
for face in shape.faces() {
let normal_vector = face.normal_vector();
let pxyz = face.0 + normal_vector;
self.draw_line(face.0 + pos, pxyz + pos, Color::WED);
if normal_vector.2 < 0.0 {
self.draw_triangle(face.translate(pos - CAMERA_POS), outline_color);
}
}
for vertex in shape.vertices() {
self.r
.draw_point((vertex + pos).project_2d(), outline_color);
}
}
}

View File

@ -1,9 +1,9 @@
use std::time::Instant; use std::time::{Duration, Instant};
use sdl3::{ use sdl3::{
event::Event, event::Event,
keyboard::Keycode, keyboard::Keycode,
pixels::{Color, PixelFormat}, pixels::{Color as SdlColor, PixelFormat},
rect::Point, rect::Point,
render::{Canvas, FPoint, FRect}, render::{Canvas, FPoint, FRect},
video::Window, video::Window,
@ -13,8 +13,8 @@ use sdl3::{
use crate::engine::{ use crate::engine::{
error::Error, error::Error,
game, game,
math::{Vertex, V2, V3}, math::{V2, V3},
Object, Color, Renderer,
}; };
pub static WIDTH: f64 = 1280.0; pub static WIDTH: f64 = 1280.0;
@ -37,7 +37,7 @@ impl SdlIo {
.unwrap(); .unwrap();
let mut canvas = window.into_canvas(); let mut canvas = window.into_canvas();
canvas.set_draw_color(Color::BLACK); canvas.set_draw_color(SdlColor::BLACK);
canvas.clear(); canvas.clear();
canvas.present(); canvas.present();
@ -52,7 +52,9 @@ impl SdlIo {
let mut event_pump = self.sdl_context.event_pump().unwrap(); let mut event_pump = self.sdl_context.event_pump().unwrap();
let mut time_before = Instant::now(); let mut time_before = Instant::now();
let time_per_frame = 1_000_000_000 / 60; let time_per_frame = Duration::from_secs_f64(1.0 / 60.0);
let mut print_fps_timer = Instant::now();
let mut fps_count = 0;
'running: loop { 'running: loop {
for event in event_pump.poll_iter() { for event in event_pump.poll_iter() {
@ -71,42 +73,60 @@ impl SdlIo {
time_before = time_now; time_before = time_now;
game.update(delta_time); game.update(delta_time);
self.canvas.set_draw_color(Color::BLACK); self.canvas.set_draw_color(SdlColor::BLACK);
self.canvas.clear(); self.canvas.clear();
game.render(self); game.render(self);
self.canvas.present(); self.canvas.present();
fps_count += 1;
if (time_now - print_fps_timer).as_secs_f64() > 1.0 {
println!("fps: {fps_count}");
fps_count = 0;
print_fps_timer = time_now;
}
if delta_time < time_per_frame {
std::thread::sleep(time_per_frame - delta_time);
}
} }
} }
fn world_to_screen(&self, v: V2) -> V2 { /// world space to screen space
V2(v.0 + WIDTH / 2.0, HEIGHT / 2.0 - v.1) fn scale_w2s(&self, v: V2) -> V2 {
let factor = WIDTH / 2.0;
V2(v.0 * factor, v.1 * factor)
}
/// world space to screen space.
fn translate_w2s(&self, v: V2) -> V2 {
let middle = V2(WIDTH / 2.0, HEIGHT / 2.0);
V2(v.0 + middle.0, middle.1 - v.1)
}
/// scale -> translate
fn point_w2s(&self, v: V2) -> V2 {
self.translate_w2s(self.scale_w2s(v))
} }
} }
impl game::Renderer for SdlIo { impl Renderer for SdlIo {
fn draw_rect(&mut self, pos: V2, size: V2, color: Color) { fn draw_rect(&mut self, pos: V2, size: V2, color: Color) {
let pos = self.world_to_screen(pos); let pos = self.point_w2s(pos);
let size = self.scale_w2s(size);
self.canvas.set_draw_color(color); self.canvas.set_draw_color(color);
self.canvas self.canvas
.fill_rect(FRect::new( .fill_rect(FRect::new(pos.0 as _, pos.1 as _, size.0 as _, size.1 as _))
(pos.0 - size.0 / 2.0) as _,
(pos.1 - size.1 / 2.0) as _,
size.0 as _,
size.1 as _,
))
.unwrap(); .unwrap();
} }
fn point(&mut self, pos: V2, color: Color) { fn draw_point(&mut self, pos: V2, color: Color) {
let pos = self.world_to_screen(pos); let pos = self.point_w2s(pos);
let size = V2(10.0, 10.0); let size = 4.0;
self.canvas.set_draw_color(color); self.canvas.set_draw_color(color);
self.canvas self.canvas
.fill_rect(FRect::new( .fill_rect(FRect::new(
(pos.0 - size.0 / 2.0) as _, (pos.0 - size / 2.0) as _,
(pos.1 - size.1 / 2.0) as _, (pos.1 - size / 2.0) as _,
10.0, size as _,
10.0, size as _,
)) ))
.unwrap(); .unwrap();
} }
@ -114,88 +134,23 @@ impl game::Renderer for SdlIo {
fn draw_line(&mut self, from: V2, to: V2, color: Color) { fn draw_line(&mut self, from: V2, to: V2, color: Color) {
self.canvas.set_draw_color(color); self.canvas.set_draw_color(color);
self.canvas self.canvas
.draw_line( .draw_line(self.point_w2s(from), self.point_w2s(to))
FPoint::new(from.0 as f32, from.1 as f32),
FPoint::new(to.0 as f32, to.1 as f32),
)
.unwrap(); .unwrap();
} }
}
fn draw_triangle(&mut self, triangle: Vertex, color: Color) { impl From<V2> for FPoint {
self.draw_line( fn from(value: V2) -> Self {
self.world_to_screen(triangle.0.project()), FPoint::new(value.0 as _, value.1 as _)
self.world_to_screen(triangle.1.project()),
color,
);
self.draw_line(
self.world_to_screen(triangle.1.project()),
self.world_to_screen(triangle.2.project()),
color,
);
self.draw_line(
self.world_to_screen(triangle.2.project()),
self.world_to_screen(triangle.0.project()),
color,
);
} }
}
fn draw_cube(&mut self, pos: V3, size: V3, outline_color: Color, fill_color: Color) { impl From<Color> for SdlColor {
let V3(x, y, z) = pos; fn from(value: Color) -> Self {
let V3(w, h, mut d) = size; match value {
d /= 200.0; Color::WHITE => SdlColor::WHITE,
let points = [ Color::GREEN => SdlColor::GREEN,
pos, Color::WED => SdlColor::RED,
V3(x + w, y, z),
V3(x, y + h, z),
V3(x + w, y + h, z),
V3(x, y, z + d),
V3(x + w, y, z + d),
V3(x, y + h, z + d),
V3(x + w, y + h, z + d),
];
let vertices = [
// south
Vertex(points[0], points[2], points[3]),
Vertex(points[0], points[3], points[1]),
// east
Vertex(points[1], points[3], points[7]),
Vertex(points[1], points[7], points[5]),
// north
Vertex(points[5], points[7], points[6]),
Vertex(points[5], points[6], points[4]),
// west
Vertex(points[4], points[6], points[2]),
Vertex(points[4], points[2], points[0]),
// top
Vertex(points[2], points[6], points[7]),
Vertex(points[2], points[7], points[3]),
// bottom
Vertex(points[5], points[4], points[0]),
Vertex(points[5], points[0], points[1]),
];
println!("current triangles");
for vertex in vertices {
let normal_vector = vertex.normal_vector();
// self.draw_triangle(vertex, outline_color);
println!("{}", normal_vector.2);
if normal_vector.2 > 0.0 {
self.draw_triangle(
Vertex(
vertex.0,
vertex.1,
V3(vertex.2 .0, vertex.2 .1, vertex.2 .2),
),
outline_color,
);
}
}
for point in points {
self.point(point.project(), outline_color);
} }
} }
} }

View File

@ -1,54 +1,82 @@
use crate::engine::math::{Vertex, V3}; use crate::engine::{math::V3, Triangle3};
pub struct Shape { static CUBE_VERTICES: [(i8, i8, i8); 8] = [
vertices: Vec<V3>, (0, 1, 0), // 0 front top left
fragments: Vec<(usize, usize, usize)>, (1, 1, 0), // 1 front top right
} (0, 0, 0), // 2 front bottom left
(1, 0, 0), // 3 front bottom right
(0, 1, 1), // 4 back top left
(1, 1, 1), // 5 back top right
(0, 0, 1), // 6 back bottom left
(1, 0, 1), // 7 back bottom right
];
impl Shape { static CUBE_EDGES: [(usize, usize); 12] = [
pub fn new_cube(V3(w, h, d): V3) -> Self { (0, 1), // ftl -> ftr
Self { (2, 3), // fbl -> fbr
vertices: vec![ (4, 5), // btl -> btr
V3(0.0, 0.0, 0.0), // 0 front top left (6, 7), // bbl -> bbr
V3(0.0 + w, 0.0, 0.0), // 1 front top right (0, 2), // ftl -> fbl
V3(0.0, 0.0 + h, 0.0), // 2 front bottom left (1, 3), // ftr -> fbr
V3(0.0 + w, 0.0 + h, 0.0), // 3 front bottom right (4, 6), // btl -> bbl
V3(0.0, 0.0, 0.0 + d), // 4 back top left (5, 7), // btr -> bbr
V3(0.0 + w, 0.0, 0.0 + d), // 5 back top right (0, 4), // ftl -> btl
V3(0.0, 0.0 + h, 0.0 + d), // 6 back bottom left (1, 5), // ftr -> btr
V3(0.0 + w, 0.0 + h, 0.0 + d), // 7 back bottom right (2, 6), // fbl -> bbl
], (3, 7), // fbr -> bbr
fragments: vec![ ];
// back
static CUBE_FACES: [(usize, usize, usize); 12] = [
// front
(0, 1, 2), (0, 1, 2),
(3, 2, 1), (3, 2, 1),
// top
(0, 4, 1),
(5, 1, 4),
// right
(7, 3, 5),
(1, 5, 3),
// back
(5, 4, 7),
(6, 7, 4),
// bottom // bottom
(4, 5, 0), (3, 7, 2),
(1, 0, 5), (6, 2, 7),
// left // left
(4, 0, 6), (4, 0, 6),
(2, 6, 0), (2, 6, 0),
// front ];
(5, 4, 7),
(6, 7, 4), pub struct Shape {
// top vertices: Vec<V3>,
(7, 6, 3), edges: Vec<(usize, usize)>,
(2, 3, 6), faces: Vec<(usize, usize, usize)>,
// right }
(1, 5, 3),
(7, 3, 5), impl Shape {
], pub fn new_cube(dim: V3) -> Self {
Self {
vertices: CUBE_VERTICES
.iter()
.map(|p| scale_i8_vertex(*p, &dim))
.collect(),
edges: Vec::from_iter(CUBE_EDGES),
faces: Vec::from_iter(CUBE_FACES),
} }
} }
pub fn points<'a>(&'a self) -> impl Iterator<Item = V3> + 'a { pub fn vertices<'a>(&'a self) -> impl Iterator<Item = V3> + 'a {
self.vertices.iter().cloned() self.vertices.iter().cloned()
} }
pub fn vertices<'a>(&'a self) -> impl Iterator<Item = Vertex> + 'a { pub fn faces<'a>(&'a self) -> impl Iterator<Item = Triangle3> + 'a {
let verts: &[V3] = &self.vertices; let verts: &[V3] = &self.vertices;
self.fragments self.faces
.iter() .iter()
.map(|(a, b, c)| Vertex(verts[*a], verts[*b], verts[*c])) .map(|(a, b, c)| Triangle3(verts[*a], verts[*b], verts[*c]))
} }
} }
fn scale_i8_vertex(p: (i8, i8, i8), s: &V3) -> V3 {
V3(p.0 as f64 * s.0, p.1 as f64 * s.1, p.2 as f64 * s.2)
}

View File

@ -2,20 +2,18 @@
use std::time::Duration; use std::time::Duration;
use sdl3::pixels::Color; use crate::engine::{Color, R3d, V3};
use crate::engine::math::{V2, V3};
mod engine; mod engine;
enum Object { enum MyObject {
Player { pos: V3, vel: V3 }, Player { pos: V3, vel: V3 },
} }
impl<R: engine::Renderer> engine::Object<R> for Object { impl<R: engine::Renderer> engine::Object<R> for MyObject {
fn update(&mut self, delta_time: Duration) { fn update(&mut self, delta_time: Duration) {
match self { match self {
Object::Player { pos, vel } => { MyObject::Player { pos, vel } => {
*pos += *vel * delta_time.as_secs_f64(); *pos += *vel * delta_time.as_secs_f64();
} }
} }
@ -23,9 +21,15 @@ impl<R: engine::Renderer> engine::Object<R> for Object {
fn render(&mut self, r: &mut R) { fn render(&mut self, r: &mut R) {
match self { match self {
Object::Player { pos, .. } => { MyObject::Player { pos, .. } => {
// r.draw_rect(V2(pos.0, pos.1), V2(400.0, 400.0)); let mut r3d = R3d::new(r);
r.draw_cube(*pos, V3(100.0, 100.0, 100.0), Color::GREEN, Color::WHITE); r3d.draw_cube(*pos, V3(0.2, 0.2, 0.2), Color::GREEN, Color::WHITE);
r3d.draw_cube(
*pos + V3(-0.4, -0.2, 0.0),
V3(0.2, 0.2, 0.2),
Color::GREEN,
Color::WHITE,
);
} }
} }
} }
@ -33,11 +37,11 @@ impl<R: engine::Renderer> engine::Object<R> for Object {
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut sdl_io = engine::SdlIo::new()?; let mut sdl_io = engine::SdlIo::new()?;
let mut game = engine::Game::<engine::SdlIo, Object>::new()?; let mut game = engine::Game::<engine::SdlIo, MyObject>::new()?;
let player = Object::Player { let player = MyObject::Player {
pos: V3(200.0, 100.0, 0.0), pos: V3(-0.1, -0.1, 0.0),
vel: V3(0.0, 0.0, 0.1), vel: V3(0.0, 0.1, 0.0),
}; };
game.spawn(player); game.spawn(player);