From eb2bb12a8d81721d108177618b4f67036dea02bb Mon Sep 17 00:00:00 2001 From: Theis Pieter Hollebeek Date: Wed, 1 Apr 2026 15:07:10 +0200 Subject: [PATCH] editor --- editor/.gitignore | 2 + editor/Cargo.lock | 46 +++++ editor/Cargo.toml | 7 + editor/src/engine/error.rs | 1 + editor/src/engine/game.rs | 53 ++++++ editor/src/engine/math.rs | 211 +++++++++++++++++++++ editor/src/engine/mod.rs | 17 ++ editor/src/engine/r3d.rs | 72 +++++++ editor/src/engine/scene.rs | 68 +++++++ editor/src/engine/sdl_io.rs | 280 ++++++++++++++++++++++++++++ editor/src/engine/shapes.rs | 134 +++++++++++++ editor/src/main.rs | 361 ++++++++++++++++++++++++++++++++++++ game/src/main.rs | 68 ++++--- 13 files changed, 1299 insertions(+), 21 deletions(-) create mode 100644 editor/.gitignore create mode 100644 editor/Cargo.lock create mode 100644 editor/Cargo.toml create mode 100644 editor/src/engine/error.rs create mode 100644 editor/src/engine/game.rs create mode 100644 editor/src/engine/math.rs create mode 100644 editor/src/engine/mod.rs create mode 100644 editor/src/engine/r3d.rs create mode 100644 editor/src/engine/scene.rs create mode 100644 editor/src/engine/sdl_io.rs create mode 100644 editor/src/engine/shapes.rs create mode 100644 editor/src/main.rs diff --git a/editor/.gitignore b/editor/.gitignore new file mode 100644 index 0000000..ccb5166 --- /dev/null +++ b/editor/.gitignore @@ -0,0 +1,2 @@ +/target +.vscode \ No newline at end of file diff --git a/editor/Cargo.lock b/editor/Cargo.lock new file mode 100644 index 0000000..399e5b4 --- /dev/null +++ b/editor/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "sdl3" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce0693cede7f7901e968e5539f87174d0fb5fac5d489a3e3be52b2b4a0b49c5" +dependencies = [ + "bitflags", + "lazy_static", + "libc", + "sdl3-sys", +] + +[[package]] +name = "sdl3-sys" +version = "0.6.1+SDL-3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d73fc820b0b6b9bd0557de7b799ec9da1ca4f35fcd1cb42f7ceb85e640d1477" + +[[package]] +name = "skate-slope" +version = "0.1.0" +dependencies = [ + "sdl3", +] diff --git a/editor/Cargo.toml b/editor/Cargo.toml new file mode 100644 index 0000000..0ec6a64 --- /dev/null +++ b/editor/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "skate-slope" +version = "0.1.0" +edition = "2021" + +[dependencies] +sdl3 = "0.17.3" diff --git a/editor/src/engine/error.rs b/editor/src/engine/error.rs new file mode 100644 index 0000000..68f6df9 --- /dev/null +++ b/editor/src/engine/error.rs @@ -0,0 +1 @@ +pub type Error = String; \ No newline at end of file diff --git a/editor/src/engine/game.rs b/editor/src/engine/game.rs new file mode 100644 index 0000000..b7232d3 --- /dev/null +++ b/editor/src/engine/game.rs @@ -0,0 +1,53 @@ +use crate::engine::{ + math::{V2, V3}, + Triangle2, +}; +use std::{marker::PhantomData, time::Duration}; + +use super::error::Error; + +pub trait Io> { + fn run(&mut self, game: &mut G); +} + +pub trait Renderer { + fn draw_rect(&mut self, pos: V2, size: 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_triangle(&mut self, triangle: Triangle2, color: Color); + fn draw_triangles(&mut self, triangles: &[Triangle2], color: Color); +} + +#[derive(Clone, Copy)] +pub enum Color { + Hex(u32), + White, + Green, + Red, + Cyan, + Black, +} + +#[derive(PartialEq, Eq, Hash)] +pub enum Key { + R, + Left, + Right, + Down, + Up, + W, + A, + S, + D, +} + +pub enum Event { + KeyUp { key: Key }, + KeyDown { key: Key }, +} + +pub trait Game { + fn update(&mut self, delta_time: Duration); + fn render(&mut self, r: &mut R); + fn event(&mut self, event: Event); +} diff --git a/editor/src/engine/math.rs b/editor/src/engine/math.rs new file mode 100644 index 0000000..129aedb --- /dev/null +++ b/editor/src/engine/math.rs @@ -0,0 +1,211 @@ +use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; + +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct V2(pub f64, pub f64); + +#[derive(Clone, Copy, PartialEq, Debug)] +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) + } + + pub fn dot(&self, rhs: Self) -> f64 { + let V3(ax, ay, az) = self; + let V3(bx, by, bz) = rhs; + ax * bx + ay * by + az * bz + } + + pub fn len(&self) -> f64 { + f64::sqrt(self.0.powi(2) + self.1.powi(2) + self.2.powi(2)) + } + + pub fn unit(&self) -> Self { + self.map(|v| v / self.len()) + } + + pub fn map f64>(&self, func: F) -> Self { + V3(func(self.0), func(self.1), func(self.2)) + } + + pub fn rotate(&self, rot: Self) -> Self { + M3x3::new_rotate_z(rot.2) + * (M3x3::new_rotate_y(rot.1) * (M3x3::new_rotate_x(rot.0) * *self)) + } + + pub fn distance(&self, rhs: Self) -> f64 { + (*self - rhs).len() + } +} + +impl Add 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 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 Mul for V3 { + type Output = V3; + + fn mul(self, rhs: f64) -> Self::Output { + Self(self.0 * rhs, self.1 * rhs, self.2 * rhs) + } +} + +impl Mul for V3 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self(self.0 * rhs.0, self.1 * rhs.1, self.2 * rhs.2) + } +} + +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 for V3 { + fn mul_assign(&mut self, rhs: f64) { + *self = *self * rhs; + } +} + +#[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(&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 fn middle(&self) -> V3 { + (self.0 + self.1 + self.2).map(|v| v / 3.0) + } + + pub fn points(&self) -> [V3; 3] { + [self.0, self.1, self.2] + } +} + +struct M3x3([f64; 9]); + +impl M3x3 { + #[rustfmt::skip] + pub fn new_rotate_x(angle: f64) -> Self { + Self([ + 1.0, 0.0, 0.0, + 0.0, f64::cos(angle), -f64::sin(angle), + 0.0, f64::sin(angle), f64::cos(angle), + ]) + } + + #[rustfmt::skip] + pub fn new_rotate_y(angle: f64) -> Self { + Self([ + f64::cos(angle), 0.0, f64::sin(angle), + 0.0, 1.0, 0.0, + -f64::sin(angle), 0.0, f64::cos(angle), + ]) + } + + #[rustfmt::skip] + pub fn new_rotate_z(angle: f64) -> Self { + Self([ + f64::cos(angle), -f64::sin(angle), 0.0, + f64::sin(angle), f64::cos(angle), 0.0, + 0.0, 0.0, 1.0, + ]) + } +} + +impl Mul for M3x3 { + type Output = V3; + + fn mul(self, rhs: V3) -> Self::Output { + let V3(x, y, z) = rhs; + let [xx, xy, xz, yx, yy, yz, zx, zy, zz] = self.0; + V3( + x * xx + y * xy + z * xz, + x * yx + y * yy + z * yz, + x * zx + y * zy + z * zz, + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn v3_ops() { + let v3 = |v: f64| V3(v, v, v); + + 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); + a += v3(2.0); + assert_eq!(a, v3(3.0)); + + let mut a = v3(2.0); + a *= 2.0; + assert_eq!(a, v3(4.0)); + } +} diff --git a/editor/src/engine/mod.rs b/editor/src/engine/mod.rs new file mode 100644 index 0000000..91f2d3f --- /dev/null +++ b/editor/src/engine/mod.rs @@ -0,0 +1,17 @@ +#![allow(unused_imports)] + +mod error; +mod game; +mod math; +mod r3d; +mod scene; +mod sdl_io; +mod shapes; + +pub use error::*; +pub use game::*; +pub use math::*; +pub use r3d::*; +pub use scene::*; +pub use sdl_io::*; +pub use shapes::*; diff --git a/editor/src/engine/r3d.rs b/editor/src/engine/r3d.rs new file mode 100644 index 0000000..fe09817 --- /dev/null +++ b/editor/src/engine/r3d.rs @@ -0,0 +1,72 @@ +use crate::engine::{game::Renderer, Color, Shape, Triangle2, Triangle3, V2, V3}; + +pub struct R3d<'r, R: Renderer> { + r: &'r mut R, + triangle_buffer: Vec, +} + +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, + triangle_buffer: Vec::new(), + } + } + + 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_shape(&mut self, pos: V3, shape: &Shape, outline_color: Color, fill_color: Color) { + self.triangle_buffer.clear(); + self.triangle_buffer + .extend(shape.faces().map(|tri| tri.translate(pos).project_2d())); + + self.r.draw_triangles(&self.triangle_buffer, fill_color); + + for face in shape.faces() { + if face.normal().dot(face.0 - CAMERA_POS + pos) >= 0.0 { + continue; + } + // let pxyz = face.middle() + face.normal(); + // self.draw_line( + // face.middle() + pos, + // face.middle() + (face.1 - face.0).unit() * 0.02 + pos, + // Color::HEX(0xffaa00), + // ); + // self.draw_line( + // face.middle() + pos, + // face.middle() + (face.2 - face.1).unit() * 0.02 + pos, + // Color::HEX(0x00ffaa), + // ); + // self.draw_line( + // face.middle() + pos, + // face.middle() + (face.0 - face.2).unit() * 0.02 + pos, + // Color::HEX(0xaa00ff), + // ); + // self.draw_line(face.middle() + pos, pxyz + pos, Color::HEX(0xffffff)); + + // self.draw_triangle(face.translate(pos - CAMERA_POS), outline_color); + self.r.draw_line( + (face.0 + pos).project_2d(), + (face.1 + pos).project_2d(), + outline_color, + ); + self.r.draw_line( + (face.2 + pos).project_2d(), + (face.0 + pos).project_2d(), + outline_color, + ); + } + } +} diff --git a/editor/src/engine/scene.rs b/editor/src/engine/scene.rs new file mode 100644 index 0000000..993282c --- /dev/null +++ b/editor/src/engine/scene.rs @@ -0,0 +1,68 @@ +use crate::engine::{Color, Renderer, Shape, Triangle3, V3}; + +pub struct DrawnTriangle { + triangle: Triangle3, + outline_color: Color, + fill_color: Color, +} + +pub struct Scene { + objects: Vec, +} + +impl Scene { + pub fn new() -> Self { + Self { + objects: Vec::new(), + } + } + + pub fn render(&mut self, r: &mut R, camera: V3) { + let mut indices_with_scores = self + .objects + .iter() + .enumerate() + .map(|(i, object)| { + let mut point_scores = object.triangle.points().map(|p| p.distance(camera)); + point_scores.sort_by(|a, b| a.total_cmp(b)); + + (i, point_scores[0]) + }) + .rev() + .collect::>(); + + indices_with_scores.sort_by(|a, b| b.1.total_cmp(&a.1)); + + for (i, _) in indices_with_scores { + let object = &self.objects[i]; + + // check if behind camera + if !(object.triangle.0 .2 >= -1.0 + && object.triangle.1 .2 >= -1.0 + && object.triangle.2 .2 >= -1.0) + { + continue; + } + + if object.triangle.normal().dot(object.triangle.0 - camera) >= 0.0 { + continue; + } + + let triangle = object.triangle.project_2d(); + + r.draw_triangle(triangle.clone(), object.fill_color); + r.draw_line(triangle.0, triangle.1, object.outline_color); + r.draw_line(triangle.0, triangle.2, object.outline_color); + } + } + + pub fn draw_shape(&mut self, pos: V3, shape: &Shape, outline_color: Color, fill_color: Color) { + for triangle in shape.faces() { + self.objects.push(DrawnTriangle { + triangle: triangle.translate(pos), + outline_color, + fill_color, + }) + } + } +} diff --git a/editor/src/engine/sdl_io.rs b/editor/src/engine/sdl_io.rs new file mode 100644 index 0000000..f5c64fe --- /dev/null +++ b/editor/src/engine/sdl_io.rs @@ -0,0 +1,280 @@ +use std::time::{Duration, Instant}; + +use sdl3::{ + event::Event, + keyboard::Keycode, + pixels::{Color as SdlColor, FColor, PixelFormat}, + rect::Point, + render::{Canvas, FPoint, FRect, Vertex, VertexIndices}, + video::Window, + Sdl, VideoSubsystem, +}; + +use crate::engine::{ + error::Error, + game, + math::{V2, V3}, + Color, Renderer, Triangle2, +}; + +pub static WIDTH: f64 = 1280.0; +pub static HEIGHT: f64 = 720.0; + +pub struct SdlIo { + sdl_context: Sdl, + video_subsystem: VideoSubsystem, + canvas: Canvas, +} + +impl SdlIo { + pub fn new() -> Result { + let sdl_context = sdl3::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + let window = video_subsystem + .window("Game", WIDTH as u32, HEIGHT as u32) + .position_centered() + .build() + .unwrap(); + let mut canvas = window.into_canvas(); + + canvas.set_draw_color(SdlColor::BLACK); + canvas.clear(); + canvas.present(); + + Ok(Self { + sdl_context, + video_subsystem, + canvas, + }) + } + + pub fn run(&mut self, game: &mut impl game::Game) { + let mut event_pump = self.sdl_context.event_pump().unwrap(); + + let mut time_before = Instant::now(); + 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 { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => break 'running, + Event::KeyDown { keycode, .. } => match keycode { + Some(key) => match key { + Keycode::R => { + game.event(game::Event::KeyDown { key: game::Key::R }); + } + Keycode::W => { + game.event(game::Event::KeyDown { key: game::Key::W }); + } + Keycode::A => { + game.event(game::Event::KeyDown { key: game::Key::A }); + } + Keycode::S => { + game.event(game::Event::KeyDown { key: game::Key::S }); + } + Keycode::D => { + game.event(game::Event::KeyDown { key: game::Key::D }); + } + Keycode::Down => { + game.event(game::Event::KeyDown { + key: game::Key::Down, + }); + } + Keycode::Up => { + game.event(game::Event::KeyDown { key: game::Key::Up }); + } + Keycode::Left => { + game.event(game::Event::KeyDown { + key: game::Key::Left, + }); + } + Keycode::Right => { + game.event(game::Event::KeyDown { + key: game::Key::Right, + }); + } + _ => {} + }, + None => {} + }, + Event::KeyUp { keycode, .. } => match keycode { + Some(key) => match key { + Keycode::R => { + game.event(game::Event::KeyUp { key: game::Key::R }); + } + Keycode::W => { + game.event(game::Event::KeyUp { key: game::Key::W }); + } + Keycode::A => { + game.event(game::Event::KeyUp { key: game::Key::A }); + } + Keycode::S => { + game.event(game::Event::KeyUp { key: game::Key::S }); + } + Keycode::D => { + game.event(game::Event::KeyUp { key: game::Key::D }); + } + Keycode::Down => { + game.event(game::Event::KeyUp { + key: game::Key::Down, + }); + } + Keycode::Up => { + game.event(game::Event::KeyUp { key: game::Key::Up }); + } + Keycode::Left => { + game.event(game::Event::KeyUp { + key: game::Key::Left, + }); + } + Keycode::Right => { + game.event(game::Event::KeyUp { + key: game::Key::Right, + }); + } + _ => {} + }, + None => {} + }, + + _ => {} + } + } + + let time_now = Instant::now(); + let delta_time = time_now - time_before; + time_before = time_now; + game.update(delta_time); + + self.canvas.set_draw_color(SdlColor::BLACK); + self.canvas.clear(); + game.render(self); + 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); + } + } + } + + /// world space to screen space + 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 Renderer for SdlIo { + fn draw_rect(&mut self, pos: V2, size: V2, color: Color) { + let pos = self.point_w2s(pos); + let size = self.scale_w2s(size); + self.canvas.set_draw_color(color); + self.canvas + .fill_rect(FRect::new(pos.0 as _, pos.1 as _, size.0 as _, size.1 as _)) + .unwrap(); + } + + fn draw_point(&mut self, pos: V2, color: Color) { + let pos = self.point_w2s(pos); + let size = 4.0; + self.canvas.set_draw_color(color); + self.canvas + .fill_rect(FRect::new( + (pos.0 - size / 2.0) as _, + (pos.1 - size / 2.0) as _, + size as _, + size as _, + )) + .unwrap(); + } + + fn draw_line(&mut self, from: V2, to: V2, color: Color) { + self.canvas.set_draw_color(color); + self.canvas + .draw_line(self.point_w2s(from), self.point_w2s(to)) + .unwrap(); + } + + fn draw_triangles(&mut self, triangles: &[Triangle2], color: Color) { + let vertices = triangles + .iter() + .flat_map(|t| [t.0, t.1, t.2]) + .map(|p| self.point_w2s(p)) + .map(|p| Vertex { + position: p.into(), + color: color.into(), + tex_coord: FPoint::new(0.0, 0.0), + }) + .collect::>(); + + self.canvas + .render_geometry(&vertices, None, VertexIndices::Sequential) + .unwrap(); + } + + fn draw_triangle(&mut self, triangle: Triangle2, color: Color) { + let vertices = [triangle.0, triangle.1, triangle.2] + .into_iter() + .map(|p| self.point_w2s(p)) + .map(|p| Vertex { + position: p.into(), + color: color.into(), + tex_coord: FPoint::new(0.0, 0.0), + }) + .collect::>(); + self.canvas + .render_geometry(&vertices, None, VertexIndices::Sequential) + .unwrap(); + } +} + +impl From for FPoint { + fn from(value: V2) -> Self { + FPoint::new(value.0 as _, value.1 as _) + } +} + +impl From for SdlColor { + fn from(value: Color) -> Self { + match value { + Color::Hex(v) => SdlColor::RGB( + ((v >> 16) & 0xff) as u8, + ((v >> 8) & 0xff) as u8, + (v & 0xff) as u8, + ), + Color::White => SdlColor::WHITE, + Color::Green => SdlColor::GREEN, + Color::Red => SdlColor::RED, + Color::Cyan => SdlColor::CYAN, + Color::Black => SdlColor::BLACK, + } + } +} + +impl From for FColor { + fn from(value: Color) -> Self { + FColor::from(>::into(value)) + } +} diff --git a/editor/src/engine/shapes.rs b/editor/src/engine/shapes.rs new file mode 100644 index 0000000..dc3f0cf --- /dev/null +++ b/editor/src/engine/shapes.rs @@ -0,0 +1,134 @@ +use crate::engine::{math::V3, Triangle3, V2}; + +static CUBE_VERTICES: [(i8, i8, i8); 8] = [ + (0, 1, 0), // 0 front top left + (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 +]; + +static CUBE_EDGES: [(usize, usize); 12] = [ + (0, 1), // ftl -> ftr + (2, 3), // fbl -> fbr + (4, 5), // btl -> btr + (6, 7), // bbl -> bbr + (0, 2), // ftl -> fbl + (1, 3), // ftr -> fbr + (4, 6), // btl -> bbl + (5, 7), // btr -> bbr + (0, 4), // ftl -> btl + (1, 5), // ftr -> btr + (2, 6), // fbl -> bbl + (3, 7), // fbr -> bbr +]; + +static CUBE_FACES: [(usize, usize, usize); 12] = [ + // front + (0, 1, 2), + (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 + (3, 7, 2), + (6, 2, 7), + // left + (4, 0, 6), + (2, 6, 0), +]; + +static PLANE_VERTICES: [(i8, i8, i8); 4] = [ + // + (0, 0, 0), + (0, 0, 1), + (1, 0, 0), + (1, 0, 1), +]; +static PLANE_EDGES: [(usize, usize); 4] = [ + // + (0, 1), + (2, 3), + (0, 2), + (1, 3), +]; +static PLANE_FACES: [(usize, usize, usize); 2] = [ + // + (0, 1, 2), + (3, 2, 1), +]; + +pub struct Shape { + vertices: Vec, + edges: Vec<(usize, usize)>, + faces: Vec<(usize, usize, usize)>, +} + +impl Shape { + pub fn new_plane(dim: V3) -> Self { + Self { + vertices: PLANE_VERTICES + .iter() + .map(|p| scale_i8_vertex(*p, &dim)) + .collect(), + edges: Vec::from_iter(PLANE_EDGES), + faces: Vec::from_iter(PLANE_FACES), + } + } + + 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 vertices<'a>(&'a self) -> impl Iterator + 'a { + self.vertices.iter().cloned() + } + + pub fn edges<'a>(&'a self) -> impl Iterator + 'a { + let verts: &[V3] = &self.vertices; + self.edges.iter().map(|(a, b)| (verts[*a], verts[*b])) + } + + pub fn faces<'a>(&'a self) -> impl Iterator + 'a { + let verts: &[V3] = &self.vertices; + self.faces + .iter() + .map(|(a, b, c)| Triangle3(verts[*a], verts[*b], verts[*c])) + } + + pub fn rotate(&self, rot: V3) -> Self { + Self { + vertices: self.vertices.iter().map(|v| v.rotate(rot)).collect(), + edges: self.edges.clone(), + faces: self.faces.clone(), + } + } + + pub fn translate(&self, trans: V3) -> Self { + Self { + vertices: self.vertices.iter().map(|v| *v + trans).collect(), + edges: self.edges.clone(), + faces: self.faces.clone(), + } + } +} + +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) +} diff --git a/editor/src/main.rs b/editor/src/main.rs new file mode 100644 index 0000000..0200d33 --- /dev/null +++ b/editor/src/main.rs @@ -0,0 +1,361 @@ +#![allow(dead_code)] + +use std::{collections::HashSet, f64::consts::PI, time::Duration}; + +use crate::engine::{Color, Key, Renderer, Scene, Shape, V3}; + +mod engine; + +struct Game { + objects: Vec, + next_object_id: u32, + keys_pressed: HashSet, +} + +impl Game { + fn new() -> Self { + Self { + objects: Vec::new(), + next_object_id: 0, + keys_pressed: HashSet::new(), + } + } + + fn spawn(&mut self, object_kind: ObjectKind) { + let object = Object { + kind: object_kind, + id: self.next_object_id, + }; + self.objects.push(object); + self.next_object_id += 1 + } + + fn despawn(&mut self, id: u32) { + let index = self + .objects + .iter() + .position(|o| o.id == id) + .expect("doesn't exist"); + self.objects.remove(index); + } +} + +impl engine::Game for Game { + fn update(&mut self, delta_time: Duration) { + for object in &mut self.objects { + if self.keys_pressed.contains(&Key::A) { + match &mut object.kind { + ObjectKind::SkateBoard { pivot_deg, .. } => { + *pivot_deg -= 18.0 * delta_time.as_secs_f64(); + if *pivot_deg < -12.5 { + *pivot_deg = -12.5; + } + } + } + } + if self.keys_pressed.contains(&Key::D) { + match &mut object.kind { + ObjectKind::SkateBoard { + pivot_deg: pivot_factor, + .. + } => { + *pivot_factor += 18.0 * delta_time.as_secs_f64(); + if *pivot_factor > 12.5 { + *pivot_factor = 12.5; + } + } + } + } + if !(self.keys_pressed.contains(&Key::A) || self.keys_pressed.contains(&Key::D)) { + match &mut object.kind { + ObjectKind::SkateBoard { + pivot_deg: pivot_factor, + .. + } => { + let decay_rate = 1.0 - (2.0 * delta_time.as_secs_f64()); + *pivot_factor *= decay_rate; + } + } + } + if self.keys_pressed.contains(&Key::R) { + match &mut object.kind { + ObjectKind::SkateBoard { rot, pos, .. } => { + *pos = V3(0.0, -0.1, -0.6); + *rot = V3(0.0, 0.5 * PI, 0.0); + } + } + } + if self.keys_pressed.contains(&Key::W) { + match &mut object.kind { + ObjectKind::SkateBoard { rot, .. } => rot.0 += 1.0 * delta_time.as_secs_f64(), + } + } + if self.keys_pressed.contains(&Key::S) { + match &mut object.kind { + ObjectKind::SkateBoard { rot, .. } => rot.0 -= 1.0 * delta_time.as_secs_f64(), + } + } + if self.keys_pressed.contains(&Key::Left) { + match &mut object.kind { + ObjectKind::SkateBoard { rot, .. } => rot.1 -= 1.0 * delta_time.as_secs_f64(), + } + } + if self.keys_pressed.contains(&Key::Right) { + match &mut object.kind { + ObjectKind::SkateBoard { rot, .. } => rot.1 += 1.0 * delta_time.as_secs_f64(), + } + } + if self.keys_pressed.contains(&Key::Down) { + match &mut object.kind { + ObjectKind::SkateBoard { pos, .. } => pos.1 -= 0.5 * delta_time.as_secs_f64(), + } + } + if self.keys_pressed.contains(&Key::Up) { + match &mut object.kind { + ObjectKind::SkateBoard { pos, .. } => pos.1 += 0.5 * delta_time.as_secs_f64(), + } + } + object.update(delta_time); + } + } + + fn render(&mut self, r: &mut R) { + let mut scene = Scene::new(); + for object in &self.objects { + object.render(&mut scene); + } + scene.render(r, V3(0.0, 0.0, -1.0)); + } + + fn event(&mut self, event: engine::Event) { + match event { + engine::Event::KeyDown { key } => self.keys_pressed.insert(key), + engine::Event::KeyUp { key } => self.keys_pressed.remove(&key), + }; + } +} + +struct Object { + kind: ObjectKind, + id: u32, +} + +enum ObjectKind { + SkateBoard { + pos: V3, + vel: V3, + rot: V3, + nyoom_factor: f64, + pivot_deg: f64, + }, +} + +struct ShapeGroupShape { + shape: Shape, + offset: V3, +} + +struct ShapeGroup { + pub shapes: Vec, +} + +impl ShapeGroup { + pub fn new(shapes: Vec) -> Self { + Self { shapes } + } + pub fn rotate(mut self, rot: V3) -> Self { + for shape in &mut self.shapes { + shape.shape = shape + .shape + .translate(shape.offset) + .rotate(rot) + .translate(shape.offset * -1.0); + } + self + } + pub fn translate(mut self, offset: V3) -> Self { + for shape in &mut self.shapes { + shape.shape = shape.shape.translate(offset); + } + self + } + pub fn draw(self, pos: V3, scene: &mut Scene, outline_color: Color, fill_color: Color) { + for shape in self.shapes { + scene.draw_shape( + pos, + &shape.shape.translate(shape.offset), + outline_color, + fill_color, + ); + } + } +} + +impl Object { + fn update(&mut self, delta_time: Duration) { + match &mut self.kind { + ObjectKind::SkateBoard { + pos, + vel, + nyoom_factor, + pivot_deg: pivot_factor, + .. + } => { + vel.0 = *pivot_factor * delta_time.as_secs_f64(); + *pos += *vel * delta_time.as_secs_f64(); + *nyoom_factor += 16.0 * delta_time.as_secs_f64(); + + // rot.0 += delta_time.as_secs_f64() * PI * 1.0; + // rot.1 += delta_time.as_secs_f64() * PI * 0.2; + // rot.2 += delta_time.as_secs_f64() * PI * 2.0; + } + } + } + + fn render(&self, scene: &mut Scene) { + match self.kind { + ObjectKind::SkateBoard { + pos, + rot, + nyoom_factor, + pivot_deg: pivot_factor, + .. + } => { + let board_size = V3(0.175, 0.005, 0.05); + let trucks = { + let anchor_size = V3(0.005, 0.01, 0.005); + let anchor_y = -anchor_size.1 - board_size.1 * 0.5; + let pivot = vec![ + ShapeGroupShape { + shape: Shape::new_cube(anchor_size), + offset: V3( + anchor_size.0 * -0.5 + board_size.0 * 0.4, + anchor_y, + anchor_size.2 * -0.5, + ), + }, + ShapeGroupShape { + shape: Shape::new_cube(anchor_size), + offset: V3( + anchor_size.0 * -0.5 - board_size.0 * 0.4, + anchor_y, + anchor_size.2 * -0.5, + ), + }, + ]; + let wheel_rail_size = V3(anchor_size.0, anchor_size.0, board_size.2 * 0.8); + let wheel_rail_y = anchor_y - wheel_rail_size.1; + let wheel_rail = vec![ + ShapeGroupShape { + shape: Shape::new_cube(wheel_rail_size), + offset: V3( + wheel_rail_size.0 * -0.5 + board_size.0 * 0.4, + wheel_rail_y, + wheel_rail_size.2 * -0.5, + ), + }, + ShapeGroupShape { + shape: Shape::new_cube(wheel_rail_size), + offset: V3( + wheel_rail_size.0 * -0.5 - board_size.0 * 0.4, + wheel_rail_y, + wheel_rail_size.2 * -0.5, + ), + }, + ]; + let wheel_size = V3(0.01, 0.01, 0.0025); + let wheel_y = wheel_rail_y - wheel_size.1 * 0.25; + let wheel_rot = V3(0.0, 0.0, nyoom_factor); + let wheel_rot_trans = V3(0.5, 0.5, 0.0); + let wheel = vec![ + ShapeGroupShape { + shape: Shape::new_cube(wheel_size) + .translate(wheel_size * wheel_rot_trans * -1.0) + .rotate(wheel_rot) + .translate(wheel_size * wheel_rot_trans), + offset: V3( + wheel_size.0 * -0.5 + board_size.0 * 0.4, + wheel_y, + wheel_rail_size.2 * -0.5 - wheel_size.2, + ), + }, + ShapeGroupShape { + shape: Shape::new_cube(wheel_size) + .translate(wheel_size * wheel_rot_trans * -1.0) + .rotate(wheel_rot) + .translate(wheel_size * wheel_rot_trans), + + offset: V3( + wheel_size.0 * -0.5 - board_size.0 * 0.4, + wheel_y, + wheel_rail_size.2 * -0.5 - wheel_size.2, + ), + }, + ShapeGroupShape { + shape: Shape::new_cube(wheel_size) + .translate(wheel_size * wheel_rot_trans * -1.0) + .rotate(wheel_rot) + .translate(wheel_size * wheel_rot_trans), + + offset: V3( + wheel_size.0 * -0.5 + board_size.0 * 0.4, + wheel_y, + wheel_rail_size.2 * 0.5, + ), + }, + ShapeGroupShape { + shape: Shape::new_cube(wheel_size) + .translate(wheel_size * wheel_rot_trans * -1.0) + .rotate(wheel_rot) + .translate(wheel_size * wheel_rot_trans), + + offset: V3( + wheel_size.0 * -0.5 - board_size.0 * 0.4, + wheel_y, + wheel_rail_size.2 * 0.5, + ), + }, + ]; + vec![pivot, wheel_rail, wheel].into_iter().flatten() + }; + let board_pivot_trans = V3(0.0, 0.5, 0.5); + let mut board = vec![ShapeGroupShape { + shape: Shape::new_cube(board_size) + .translate(board_size * board_pivot_trans * -1.0) + .rotate(V3(pivot_factor * (PI / 180.0), 0.0, 0.0)) + .translate(board_size * board_pivot_trans), + offset: board_size * -0.5, + }]; + board.extend(trucks); + let board = ShapeGroup::new(board).rotate(rot); + board.draw(pos, scene, Color::Green, Color::Black); + scene.draw_shape( + V3(-0.5, -5.0, 0.0), + &Shape::new_cube(V3(1.0, 0.0, 100.0)), + Color::Red, + Color::Red, + ); + } + } + } +} + +fn main() -> Result<(), Box> { + let mut sdl_io = engine::SdlIo::new()?; + let mut game = Game::new(); + let mut objects: Vec = Vec::new(); + objects.push(ObjectKind::SkateBoard { + pos: V3(0.0, -0.1, -0.6), + vel: V3(0.0, 0.0, 0.0), + rot: V3(0.0, PI * 0.5, 0.0), + nyoom_factor: 0.0, + pivot_deg: 0.0, + }); + + for object in objects { + game.spawn(object) + } + + sdl_io.run(&mut game); + Ok(()) +} diff --git a/game/src/main.rs b/game/src/main.rs index 70be3c1..cca1e88 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -98,6 +98,7 @@ enum ObjectKind { pos: V3, vel: V3, rot: V3, + nyoom_factor: f64, }, Obstacle { pos: V3, @@ -158,11 +159,17 @@ impl ShapeGroup { impl Object { fn update(&mut self, delta_time: Duration) { match &mut self.kind { - ObjectKind::SkateBoard { pos, vel, rot } => { + ObjectKind::SkateBoard { + pos, + vel, + nyoom_factor, + rot, + } => { *pos += *vel * delta_time.as_secs_f64(); + *nyoom_factor += 16.0 * delta_time.as_secs_f64(); // rot.0 += delta_time.as_secs_f64() * PI * 1.0; - // rot.1 += delta_time.as_secs_f64() * PI * 1.0; + rot.1 += delta_time.as_secs_f64() * PI * 0.2; // rot.2 += delta_time.as_secs_f64() * PI * 2.0; } ObjectKind::Obstacle { pos, vel } => { @@ -187,24 +194,42 @@ impl Object { fn render(&self, scene: &mut Scene) { match self.kind { - ObjectKind::SkateBoard { pos, rot, .. } => { - let camera_pos = V3(0.0, 0.0, -1.0); - let board = ShapeGroup::new(vec![ - ShapeGroupShape { - shape: Shape::new_cube(V3(0.2, 0.01, 0.05)), - offset: V3(0.0, 0.0, 0.0), - }, - ShapeGroupShape { - shape: Shape::new_cube(V3(0.02, 0.02, 0.05)), - offset: V3(0.04, -0.02, 0.0), - }, - ShapeGroupShape { - shape: Shape::new_cube(V3(0.02, 0.02, 0.05)), - offset: V3(0.14, -0.02, 0.0), - }, - ]) - .translate(V3(-0.1, -0.005, -0.0025)) - .rotate(rot); + ObjectKind::SkateBoard { + pos, + rot, + nyoom_factor, + .. + } => { + let trucks = { + vec![ + ShapeGroupShape { + shape: Shape::new_cube(V3(0.005, 0.01, 0.005)), + offset: V3(0.04 - 0.0025, -0.005, 0.025 - 0.0025), + }, + ShapeGroupShape { + shape: Shape::new_cube(V3(0.005, 0.01, 0.005)), + offset: V3(0.14 - 0.0025, -0.005, 0.025 - 0.0025), + }, + ShapeGroupShape { + shape: Shape::new_cube(V3(0.0025, 0.0025, 0.05)), + offset: V3(0.04 - 0.0025, -0.005 - 0.01, 0.0), + }, + ShapeGroupShape { + shape: Shape::new_cube(V3(0.0025, 0.0025, 0.05)), + offset: V3(0.14 - 0.0025, -0.005 - 0.01, 0.0), + }, + ] + }; + let wheels = {}; + let mut board = vec![ShapeGroupShape { + shape: Shape::new_cube(V3(0.175, 0.005, 0.05)), + offset: V3(0.0, 0.0, 0.0), + }]; + board.extend(trucks); + board.extend(wheels); + let board = ShapeGroup::new(board) + .translate(V3(-0.1, 0.005, -0.0025)) + .rotate(rot); board.draw(pos, scene, Color::Green, Color::Black); } ObjectKind::Obstacle { pos, .. } => { @@ -257,7 +282,8 @@ fn main() -> Result<(), Box> { objects.push(ObjectKind::SkateBoard { pos: V3(0.0, -0.1, -0.7), vel: V3(0.0, 0.0, 0.0), - rot: V3(0.0, PI * 0.5, 0.0), + rot: V3(0.0, PI * 0.33, 0.0), + nyoom_factor: 0.0, }); objects.push(ObjectKind::Ground { original_pos: V3(0.0, -0.25, -0.6),