commit 6154c72b0e6453ddc13609c7d387a7e89bdceb1a Author: lisk77 Date: Sat Oct 26 02:15:26 2024 +0200 initial commit diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1f4ee5c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "comet" +version = "0.1.0" +edition = "2021" + +[dependencies] +comet_app = { path = "./crates/comet_app", workspace = true } +comet_colors = { path = "./crates/comet_colors", workspace = true } +comet_math = { path = "./crates/comet_math", workspace = true } +comet_renderer = { path = "./crates/comet_renderer", workspace = true } +comet_resources = { path = "./crates/comet_resources", workspace = true } +comet_ecs = { path = "./crates/comet_ecs", workspace = true } +comet_input = { path = "./crates/comet_input", workspace = true } +comet_log = { path = "./crates/comet_log", workspace = true } + +cfg-if = "1" +anyhow = "1.0" +bytemuck = { version = "1.16", features = [ "derive" ] } +cgmath = "0.18" +env_logger = "0.10" +log = "0.4" +tobj = { version = "3.2", default-features = false, features = ["async"]} +wgpu = { version = "22.0"} +winit = { version = "0.29", features = ["rwh_05"] } +instant = "0.1" +image = { version = "0.24", default_features = false, features = ["png", "jpeg", "hdr"] } +chrono = "0.4.38" +colored = "2.1.0" + + +[build-dependencies] +anyhow = "1.0" +fs_extra = "1.2" +glob = "0.3" + +[workspace] +members = [ + "./crates/comet_app", + "./crates/comet_colors", + "./crates/comet_math", + "./crates/comet_renderer", + "./crates/comet_resources", + "./crates/comet_ecs", + "./crates/comet_input", + "./crates/comet_log" +] + +[workspace.dependencies] +comet_app = { path = "./crates/comet_app", workspace = true } +comet_colors = { path = "./crates/comet_colors", workspace = true } +comet_math = { path = "./crates/comet_math", workspace = true } +comet_renderer = { path = "./crates/comet_renderer", workspace = true } +comet_resources = { path = "./crates/comet_resources", workspace = true } +comet_ecs = { path = "./crates/comet_ecs", workspace = true } +comet_input = { path = "./crates/comet_input", workspace = true } +comet_log = { path = "./crates/comet_log", workspace = true } \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..71683f3 --- /dev/null +++ b/build.rs @@ -0,0 +1,29 @@ +use anyhow::*; +use fs_extra::copy_items; +use fs_extra::dir::CopyOptions; +use std::env; + +fn main() -> Result<()> { + // This tells cargo to rerun this script if something in /resources/ changes. + println!("cargo:rerun-if-changed=resources/materials/*"); + println!("cargo:rerun-if-changed=resources/objects/*"); + println!("cargo:rerun-if-changed=resources/textures/*"); + println!("cargo:rerun-if-changed=resources/shaders/*"); + println!("cargo:rerun-if-changed=resources/data/*"); + println!("cargo:rerun-if-changed=resources/sounds/*"); + + let out_dir = env::var("OUT_DIR")?; + let mut copy_options = CopyOptions::new(); + copy_options.overwrite = true; + let mut paths_to_copy = Vec::new(); + paths_to_copy.push("resources/materials/"); + paths_to_copy.push("resources/objects/"); + paths_to_copy.push("resources/textures/"); + paths_to_copy.push("resources/shaders/"); + paths_to_copy.push("resources/data/"); + paths_to_copy.push("resources/sounds/"); + + copy_items(&paths_to_copy, out_dir, ©_options)?; + + Ok(()) +} \ No newline at end of file diff --git a/crates/comet_app/Cargo.toml b/crates/comet_app/Cargo.toml new file mode 100644 index 0000000..3f4a110 --- /dev/null +++ b/crates/comet_app/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "comet_app" +version = "0.1.0" +edition = "2021" + +[dependencies] +comet_ecs = { path = "../comet_ecs" } +comet_renderer = { path = "../comet_renderer" } +comet_resources = { path = "../comet_resources" } +comet_colors = { path = "../comet_colors" } + +winit = { version = "0.29", features = ["rwh_05"] } +env_logger = "0.10" +pollster = "0.3" +wgpu = { version = "22.0"} +log = "0.4.22" +anyhow = "1.0.89" +bytemuck = "1.18.0" + +[dependencies.image] +version = "0.24" +default-features = false +features = ["png", "jpeg", "hdr"] diff --git a/crates/comet_app/src/app.rs b/crates/comet_app/src/app.rs new file mode 100644 index 0000000..3778689 --- /dev/null +++ b/crates/comet_app/src/app.rs @@ -0,0 +1,175 @@ +use std::sync::Arc; +use comet_ecs::World; +use comet_resources::ResourceManager; +use comet_renderer::{Renderer}; + +use winit::{ + event::{self, *}, + event_loop::{self, EventLoop, EventLoopWindowTarget}, + keyboard::{KeyCode, PhysicalKey}, + window::{Icon, Window}, +}; +use comet_colors::LinearRgba; +use comet_ecs::math::Point3; +use log::warn; +use winit::dpi::{LogicalSize, PhysicalSize}; + +pub enum ApplicationType { + App2D, + App3D +} + +pub struct App<'a> { + title: &'a str, + icon: Option, + size: Option>, + clear_color: Option, + world: World, + fullscreen: bool, + should_quit: bool +} + +impl<'a> App<'a> { + pub fn new(application_type: ApplicationType) -> Self { + let world = match application_type { + ApplicationType::App2D => World::new("2D"), + ApplicationType::App3D => World::new("3D"), + }; + + Self { + title: "Untitled", + icon: None, + size: None, + clear_color: None, + world, + fullscreen: false, + should_quit: false + } + } + + pub fn with_title(mut self, title: &'a str) -> Self { + self.title = title; + self + } + + pub fn with_icon(mut self, path: &'a str) -> Self { + self.icon = Some(Self::load_icon(std::path::Path::new(path)).unwrap()); + self + } + + pub fn with_size(mut self, width: u32, height: u32) -> Self { + self.size = Some(LogicalSize::new(width, height)); + self + } + + pub fn with_clear_color(mut self, clear_color: LinearRgba) -> Self { + self.clear_color = Some(clear_color); + self + } + + fn load_icon(path: &std::path::Path) -> Option { + let image = image::open(path).expect("Failed to open icon image"); + let rgba_image = image.to_rgba8(); + let (width, height) = rgba_image.dimensions(); + Some(Icon::from_rgba(rgba_image.into_raw(), width, height).unwrap()) + } + + pub fn world(&self) -> &World { + &self.world + } + + pub fn world_mut(&mut self) -> &mut World { + &mut self.world + } + + pub fn quit(&mut self) { + self.should_quit = true; + } + + fn create_window(app_title: &str, app_icon: &Option, window_size: &Option>, event_loop: &EventLoop<()>) -> winit::window::Window { + let winit_window = winit::window::WindowBuilder::new() + .with_title(app_title); + + let winit_window = if let Some(icon) = app_icon.clone() { + winit_window.with_window_icon(Some(icon)) + } + else { + winit_window + }; + let winit_window = if let Some(size) = window_size.clone() { + winit_window.with_inner_size(size) + } + else { + winit_window + }; + + winit_window.build(event_loop).unwrap() + } + + async fn run_app(mut self, input_manager: F, game_manager: G) { + env_logger::init(); + let event_loop = EventLoop::new().unwrap(); + let window = Self::create_window(self.title, &self.icon, &self.size ,&event_loop); + + let mut renderer = Renderer::new(&window, self.clear_color.clone()).await.unwrap(); + let mut surface_configured = false; + + window.set_maximized(true); + + event_loop.run(|event, control_flow| { + if self.should_quit { + control_flow.exit() + } + + game_manager(&mut self.world, &mut renderer); + + match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == renderer.window().id() => { + match event { + WindowEvent::CloseRequested {} => control_flow.exit(), + WindowEvent::Resized(physical_size) => { + surface_configured = true; + renderer.resize(*physical_size); + } + WindowEvent::RedrawRequested => { + renderer.window().request_redraw(); + + if !surface_configured { + return; + } + + /*if self.fullscreen && !renderer.window().fullscreen().is_some() { + renderer.resize(renderer.window().inner_size().into()); + }*/ + + renderer.update(); + //println!("{}", 1.0/dt); + match renderer.render() { + Ok(_) => {} + Err( + wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated, + ) => renderer.resize(renderer.size()), + Err(wgpu::SurfaceError::OutOfMemory) => { + log::error!("OutOfMemory"); + control_flow.exit(); + } + Err(wgpu::SurfaceError::Timeout) => { + warn!("Surface timeout") + } + } + } + _ => { input_manager(event, &mut self, &mut renderer) } + } + } + _ => {} + } + }).unwrap(); + } + + pub fn run(mut self, input_manager: F, game_manager: G) { + pollster::block_on(self.run_app(input_manager, game_manager)); + } +} \ No newline at end of file diff --git a/crates/comet_app/src/lib.rs b/crates/comet_app/src/lib.rs new file mode 100644 index 0000000..0add883 --- /dev/null +++ b/crates/comet_app/src/lib.rs @@ -0,0 +1,2 @@ +pub use app::*; +mod app; \ No newline at end of file diff --git a/crates/comet_colors/Cargo.toml b/crates/comet_colors/Cargo.toml new file mode 100644 index 0000000..11aa9c3 --- /dev/null +++ b/crates/comet_colors/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "comet_colors" +version = "0.1.0" +edition = "2021" + +[dependencies] +comet_math = { path = "../comet_math" } +wgpu = { version = "22.0" } \ No newline at end of file diff --git a/crates/comet_colors/src/hsla.rs b/crates/comet_colors/src/hsla.rs new file mode 100644 index 0000000..19aa2dc --- /dev/null +++ b/crates/comet_colors/src/hsla.rs @@ -0,0 +1,101 @@ +use crate::{sRgba, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Oklcha, Xyza}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Hsla { + hue: f32, + saturation: f32, + lightness: f32, + alpha: f32 +} + +impl Hsla { + pub fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self { + assert!((0.0..=360.0).contains(&hue) && (0.0..=1.0).contains(&saturation) && (0.0..=1.0).contains(&lightness) && (0.0..=1.0).contains(&alpha), "Hue needs to be in range 0..360\nSaturation needs to be in range 0..1\nLightness needs to be in range 0..1\nAlpha needs to be in range 0..1"); + Self { + hue, + saturation, + lightness, + alpha + } + } + + pub fn hue(&self) -> f32 { + self.hue + } + + pub fn saturation(&self) -> f32 { + self.saturation + } + + pub fn lightness(&self) -> f32 { + self.lightness + } + + pub fn alpha(&self) -> f32 { + self.alpha + } + + pub fn from_hsva(hsva: Hsva) -> Self { + let lightness = hsva.value() * (1.0 - hsva.saturation() * 0.5); + Self { + hue: hsva.hue(), + saturation: if lightness == 0.0 || lightness == 1.0 { 0.0 } else { (hsva.value() - lightness) / lightness.min(1.0 - lightness) }, + lightness, + alpha: hsva.alpha() + } + } + + pub fn from_rgba(rgba: sRgba) -> Self { + rgba + .to_hwba() + .to_hsva() + .to_hsla() + + } + + pub fn to_hsva(&self) -> Hsva { + let value = self.lightness() + self.saturation() * self.lightness().min(1.0 - self.lightness()); + Hsva::new( + self.hue(), + if value == 0.0 { 0.0 } else { 2.0 * (1.0 - self.lightness() / value) }, + value, + self.alpha() + ) + } + + pub fn to_hwba(&self) -> Hwba { + self.to_hsva().to_hwba() + } + + pub fn to_rgba(&self) -> sRgba { + self.to_hwba().to_rgba() + } + + pub fn to_rgba8(&self) -> sRgba { + self.to_hwba().to_rgba8() + } + + pub fn to_linear(&self) -> LinearRgba { + self.to_rgba().to_linear() + } + + pub fn to_xyza(&self) -> Xyza { + self.to_linear().to_xyza() + } + + pub fn to_laba(&self) -> Laba { + self.to_xyza().to_laba() + } + + pub fn to_lcha(&self) -> Lcha { + self.to_laba().to_lcha() + } + + pub fn to_oklaba(&self) -> Oklaba { + self.to_linear().to_oklaba() + } + + pub fn to_oklcha(&self) -> Oklcha { + self.to_oklaba().to_oklcha() + } +} \ No newline at end of file diff --git a/crates/comet_colors/src/hsva.rs b/crates/comet_colors/src/hsva.rs new file mode 100644 index 0000000..0261012 --- /dev/null +++ b/crates/comet_colors/src/hsva.rs @@ -0,0 +1,99 @@ +use crate::{sRgba, Hsla, Hwba, Laba, Lcha, LinearRgba, Oklaba, Oklcha, Xyza}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Hsva { + hue: f32, + saturation: f32, + value: f32, + alpha: f32 +} + +impl Hsva { + pub fn new(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self { + assert!((0.0..=360.0).contains(&hue) && (0.0..=1.0).contains(&saturation) && (0.0..=1.0).contains(&value) && (0.0..=1.0).contains(&alpha), "Hue needs to be in range 0..1\nSaturation needs to be in range 0..1\nValue needs to be in range 0..1\nAlpha needs to be in range 0..1"); + Self { + hue, + saturation, + value, + alpha + } + } + + pub fn hue(&self) -> f32 { + self.hue + } + + pub fn saturation(&self) -> f32 { + self.saturation + } + + pub fn value(&self) -> f32 { + self.value + } + + pub fn alpha(&self) -> f32 { + self.alpha + } + + pub fn from_hwba(hwba: Hwba) -> Self { + Self { + hue: hwba.hue(), + saturation: 1.0 - hwba.hue() / (1.0 - hwba.blackness()), + value: 1.0 - hwba.blackness(), + alpha: hwba.alpha() + } + } + + pub fn to_hsva(hsla: Hsla) -> Self { + let value = hsla.lightness() + hsla.saturation() * hsla.lightness().min(1.0 - hsla.lightness()); + Self { + hue: hsla.hue(), + saturation: if value == 0.0 { 0.0 } else { 2.0 * (1.0 - hsla.lightness() / value) }, + value, + alpha: hsla.alpha() + } + + + } + + pub fn to_hwba(&self) -> Hwba { + Hwba::new(self.hue, (1.0 - self.saturation) * self.value, 1.0 - self.value, self.alpha) + } + + pub fn to_hsla(&self) -> Hsla { + let l = self.value * (1.0 - self.saturation * 0.5); + Hsla::new(self.hue, if l == 0.0 || l == 1.0 { 0.0 } else { (self.value - l) / l.min(1.0 - l) }, l, self.alpha) + } + + pub fn to_rgba(&self) -> sRgba { + self.to_hwba().to_rgba() + } + + pub fn to_rgba8(&self) -> sRgba { + self.to_hwba().to_rgba8() + } + + pub fn to_linear(&self) -> LinearRgba { + self.to_rgba().to_linear() + } + + pub fn to_xyza(&self) -> Xyza { + self.to_linear().to_xyza() + } + + pub fn to_laba(&self) -> Laba { + self.to_xyza().to_laba() + } + + pub fn to_lcha(&self) -> Lcha { + self.to_laba().to_lcha() + } + + pub fn to_oklaba(&self) -> Oklaba { + self.to_linear().to_oklaba() + } + + pub fn to_oklcha(&self) -> Oklcha { + self.to_oklaba().to_oklcha() + } +} \ No newline at end of file diff --git a/crates/comet_colors/src/hwba.rs b/crates/comet_colors/src/hwba.rs new file mode 100644 index 0000000..d662f20 --- /dev/null +++ b/crates/comet_colors/src/hwba.rs @@ -0,0 +1,157 @@ +use crate::{sRgba, Hsla, Hsva, Laba, Lcha, LinearRgba, Oklaba, Oklcha, Xyza}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Hwba { + hue: f32, + whiteness: f32, + blackness: f32, + alpha: f32 +} + +impl Hwba { + pub fn new(hue: f32, whiteness: f32, blackness: f32, alpha: f32) -> Self { + assert!((0.0..=360.0).contains(&hue) && (0.0..=1.0).contains(&whiteness) && (0.0..=1.0).contains(&blackness) && (0.0..=1.0).contains(&alpha), "Hue needs to be in range 0..360\nWhiteness needs to be in range 0..1\nBlackness needs to be in range 0..1\nAlpha needs to be in range 0..1"); + Self { + hue, + whiteness, + blackness, + alpha + } + } + + pub fn hue(&self) -> f32 { + self.hue + } + + pub fn whiteness(&self) -> f32 { + self.whiteness + } + + pub fn blackness(&self) -> f32 { + self.blackness + } + + pub fn alpha(&self) -> f32 { + self.alpha + } + + pub fn from_rgba8(&self, rgba: sRgba) -> Self { + let rgba = rgba.to_rbga(); + self.from_rgba(rgba) + } + + pub fn from_rgba(&self, rgba: sRgba) -> Self { + let max_rgb = rgba.red().max(rgba.green()).max(rgba.blue()); + let whiteness = rgba.red().min(rgba.green()).min(rgba.blue()); + + let blackness = 1.0 - max_rgb; + let chroma = max_rgb - whiteness; + + let hue = if chroma == 0.0 { + 0.0 + } + else if max_rgb == rgba.red() { + 60.0 * ((rgba.green() - rgba.blue()) / chroma % 6.0) + } + else if max_rgb == rgba.green() { + 60.0 * ((rgba.blue() - rgba.red()) / chroma + 2.0) + } + else { + 60.0 * ((rgba.red() - rgba.green()) / chroma + 4.0) + }; + + let hue = if hue < 0.0 { hue + 360.0 } else { hue }; + + Self { + hue, + whiteness, + blackness, + alpha: rgba.alpha() + } + } + + pub fn from_hsva(hsva: Hsva) -> Hwba { + Hwba::new( + hsva.hue(), + (1.0 - hsva.saturation()) * hsva.value(), + 1.0 - hsva.value(), + hsva.alpha() + ) + } + + pub fn to_rgba8(&self) -> sRgba { + let rgba = self.to_rgba(); + + sRgba::::new( + (rgba.red() * 255.0) as u8, + (rgba.green() * 255.0) as u8, + (rgba.blue() * 255.0) as u8, + (rgba.alpha() * 255.0) as u8 + ) + } + + pub fn to_rgba(&self) -> sRgba { + let w = self.whiteness.min(1.0 - self.blackness); + let c = 1.0 - self.whiteness - self.blackness; + + let hue = self.hue % 360.0; + let h_prime = hue / 60.0; + + let x = c * (1.0 - (h_prime % 2.0 - 1.0).abs()); + + let (r1, g1, b1) = match h_prime as u32 { + 0 => (c, x, 0.0), + 1 => (x, c, 0.0), + 2 => (0.0, c, x), + 3 => (0.0, x, c), + 4 => (x, 0.0, c), + 5 => (c, 0.0, x), + _ => (0.0, 0.0, 0.0) + }; + + sRgba::::new( + r1 + w, + g1 + w, + b1 + w, + self.alpha() + ) + } + + pub fn to_hsva(&self) -> Hsva { + Hsva::new( + self.hue, + 1.0 - self.hue / (1.0 - self.blackness), + 1.0 - self.blackness, + self.alpha + ) + } + + pub fn to_hsla(&self) -> Hsla { + self.to_hsva().to_hsla() + } + + pub fn to_linear(&self) -> LinearRgba { + self.to_rgba().to_linear() + } + + pub fn to_xyza(&self) -> Xyza { + self.to_linear().to_xyza() + } + + pub fn to_laba(&self) -> Laba { + self.to_xyza().to_laba() + } + + pub fn to_lcha(&self) -> Lcha { + self.to_laba().to_lcha() + } + + pub fn to_oklaba(&self) -> Oklaba { + self.to_linear().to_oklaba() + } + + pub fn to_oklcha(&self) -> Oklcha { + self.to_oklaba().to_oklcha() + } + +} \ No newline at end of file diff --git a/crates/comet_colors/src/laba.rs b/crates/comet_colors/src/laba.rs new file mode 100644 index 0000000..c8d05a6 --- /dev/null +++ b/crates/comet_colors/src/laba.rs @@ -0,0 +1,143 @@ +use crate::{sRgba, Hsla, Hsva, Hwba, Lcha, LinearRgba, Oklaba, Oklcha, Xyza}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Laba { + lightness: f32, + a: f32, + b: f32, + alpha: f32 +} + +impl Laba { + pub fn new(lightness: f32, green_red: f32, blue_yellow: f32, alpha: f32) -> Self { + assert!((0.0..=1.5).contains(&lightness) && (-1.5..=1.5).contains(&green_red) && (-1.5..=1.5).contains(&blue_yellow) && (0.0..=1.0).contains(&alpha), "Ligthness needs to be in range 0..1.5\nA needs to be in range -1.5..1.5\nB needs to be in range -1.5..1.5\nAlpha needs to be in range 0..1"); + Self { + lightness, + a: green_red, + b: blue_yellow, + alpha + } + } + + pub fn lightness(&self) -> f32 { + self.lightness + } + + pub fn a(&self) -> f32 { + self.a + } + + pub fn b(&self) -> f32 { + self.b + } + + pub fn alpha(&self) -> f32 { + self.alpha + } + + pub fn from_xyza(xyza: Xyza) -> Self { + let reference_white = Xyza::new(0.95047, 1.0, 1.08883, 1.0); + + let x_r = xyza.x() / reference_white.x(); + let y_r = xyza.y() / reference_white.y(); + let z_r = xyza.z() / reference_white.z(); + + let epsilon: f32 = 0.008856; + let kappa: f32 = 903.3; + + let f_x = if x_r > epsilon { x_r.cbrt() } else { ( kappa*x_r + 16.0 ) / 116.0 }; + let f_y = if x_r > epsilon { y_r.cbrt() } else { ( kappa*y_r + 16.0 ) / 116.0 }; + let f_z = if x_r > epsilon { z_r.cbrt() } else { ( kappa*z_r + 16.0 ) / 116.0 }; + + Self { + lightness: 1.16*f_y-0.16, + a: 5.0*( f_x - f_y ), + b: 2.0*( f_y - f_z ), + alpha: xyza.alpha() + } + } + + pub fn to_lcha(&self) -> Lcha { + let atan: f32 = self.b.atan2(self.a); + Lcha::new( + self.lightness, + (self.a*self.a + self.b*self.b).sqrt(), + if atan >= 0.0 { atan } else { atan + 360.0 }, + self.alpha + ) + } + + pub fn to_xyza(&self) -> Xyza{ + let epsilon: f32 = 0.008856; + let kappa: f32 = 903.3; + + let l = 100. * self.lightness; + let a = 100. * self.a; + let b = 100. * self.b; + + let fy = (l + 16.0) / 116.0; + let fx = a / 500.0 + fy; + let fz = fy - b / 200.0; + let xr = { + let fx3 = fx.powf(3.0); + + if fx3 > epsilon { + fx3 + } else { + (116.0 * fx - 16.0) / kappa + } + }; + let yr = if l > epsilon * kappa { + ((l + 16.0) / 116.0).powf(3.0) + } else { + l / kappa + }; + let zr = { + let fz3 = fz.powf(3.0); + + if fz3 > epsilon { + fz3 + } else { + (116.0 * fz - 16.0) / kappa + } + }; + + let x = xr * 0.95047; + let y = yr * 1.0; + let z = zr * 1.08883; + + Xyza::new(x, y, z, self.alpha) + } + + pub fn to_linear(&self) -> LinearRgba { + self.to_xyza().to_linear() + } + + pub fn to_oklaba(&self) -> Oklaba { + self.to_linear().to_oklaba() + } + + pub fn to_oklcha(&self) -> Oklcha { + self.to_oklaba().to_oklcha() + } + + pub fn to_rgba(&self) -> sRgba { + self.to_linear().to_rgba() + } + + pub fn to_rgba8(&self) -> sRgba { + self.to_rgba().to_rgba8() + } + + pub fn to_hwba(&self) -> Hwba { + self.to_rgba().to_hwba() + } + + pub fn to_hsva(&self) -> Hsva { + self.to_hwba().to_hsva() + } + + pub fn to_hsla(&self) -> Hsla { + self.to_hsva().to_hsla() + } +} \ No newline at end of file diff --git a/crates/comet_colors/src/lcha.rs b/crates/comet_colors/src/lcha.rs new file mode 100644 index 0000000..743d5b3 --- /dev/null +++ b/crates/comet_colors/src/lcha.rs @@ -0,0 +1,93 @@ +use crate::{sRgba, Hsla, Hsva, Hwba, Laba, LinearRgba, Oklaba, Oklcha, Xyza}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Lcha { + lightness: f32, + chroma: f32, + hue: f32, + alpha: f32 +} + +impl Lcha { + pub fn new(lightness: f32, chroma: f32, hue: f32, alpha: f32) -> Self { + assert!((0.0..=1.5).contains(&lightness) && (0.0..=1.5).contains(&chroma) && (0.0..=360.0).contains(&hue) && (0.0..=1.0).contains(&alpha), "Ligthness needs to be in range 0..1.5\nChroma needs to be in range 0..1.5\nHue needs to be in range 0..360\nAlpha needs to be in range 0..1"); + Self { + lightness, + chroma, + hue, + alpha + } + } + + pub fn lightness(&self) -> f32 { + self.lightness + } + + pub fn chroma(&self) -> f32 { + self.chroma + } + + pub fn hue(&self) -> f32 { + self.hue + } + + pub fn alpha(&self) -> f32 { + self.alpha + } + + pub fn from_laba(laba: Laba) -> Self { + let atan: f32 = laba.b().atan2(laba.a()); + Self { + lightness: laba.lightness(), + chroma: (laba.a()*laba.a() + laba.b()*laba.b()).sqrt(), + hue: if atan >= 0.0 { atan } else { atan + 360.0 }, + alpha: laba.alpha() + } + } + + pub fn to_laba(&self) -> Laba { + Laba::new( + self.lightness, + self.chroma * self.hue.cos().to_radians(), + self.chroma * self.hue.sin().to_radians(), + self.alpha + ) + } + + pub fn to_xyza(&self) -> Xyza { + self.to_laba().to_xyza() + } + + pub fn to_linear(&self) -> LinearRgba { + self.to_xyza().to_linear() + } + + pub fn to_oklaba(&self) -> Oklaba { + self.to_linear().to_oklaba() + } + + pub fn to_oklcha(&self) -> Oklcha { + self.to_oklaba().to_oklcha() + } + + pub fn to_rgba(&self) -> sRgba { + self.to_linear().to_rgba() + } + + pub fn to_rgba8(&self) -> sRgba { + self.to_linear().to_rgba8() + } + + pub fn to_hwba(&self) -> Hwba { + self.to_rgba().to_hwba() + } + + pub fn to_hsva(&self) -> Hsva { + self.to_hwba().to_hsva() + } + + pub fn to_hsla(&self) -> Hsla { + self.to_hsva().to_hsla() + } + +} \ No newline at end of file diff --git a/crates/comet_colors/src/lib.rs b/crates/comet_colors/src/lib.rs new file mode 100644 index 0000000..da0bcdc --- /dev/null +++ b/crates/comet_colors/src/lib.rs @@ -0,0 +1,22 @@ +pub use comet_math as math; +pub use linear_rgba::*; +pub use rgba::*; +pub use hwba::*; +pub use hsva::*; +pub use hsla::*; +pub use xyza::*; +pub use laba::*; +pub use lcha::*; +pub use oklaba::*; +pub use oklcha::*; + +mod rgba; +mod linear_rgba; +mod hwba; +mod hsva; +mod hsla; +mod xyza; +mod laba; +mod lcha; +mod oklaba; +mod oklcha; \ No newline at end of file diff --git a/crates/comet_colors/src/linear_rgba.rs b/crates/comet_colors/src/linear_rgba.rs new file mode 100644 index 0000000..8b4516c --- /dev/null +++ b/crates/comet_colors/src/linear_rgba.rs @@ -0,0 +1,135 @@ +use wgpu::Color; +use crate::{sRgba, Hsla, Hsva, Hwba, Laba, Lcha, Oklaba, Oklcha, Xyza}; + +#[derive(Debug, Clone, PartialEq)] +pub struct LinearRgba { + red: f32, + green: f32, + blue: f32, + alpha: f32 +} + +impl LinearRgba { + pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self { + assert!((0.0..=1.0).contains(&red) && (0.0..=1.0).contains(&green) && (0.0..=1.0).contains(&blue) && (0.0..=1.0).contains(&alpha), "Red needs to be in range 0..1\nGreen needs to be in range 0..1\nBlue needs to be in range 0..1\nAlpha needs to be in range 0..1"); + Self { + red, + green, + blue, + alpha + } + } + + pub fn red(&self) -> f32 { + self.red + } + + pub fn green(&self) -> f32 { + self.green + } + + pub fn blue(&self) -> f32 { + self.blue + } + + pub fn alpha(&self) -> f32 { + self.alpha + } + + pub fn from_rgba(srgba: sRgba) -> Self { + Self { + red: if srgba.red() <= 0.04045 { srgba.red() / 12.92 } else { ( ( srgba.red() + 0.055 ) / 1.055 ).powf(2.4) }, + green: if srgba.green() <= 0.04045 { srgba.green() / 12.92 } else { ( ( srgba.green() + 0.055 ) / 1.055 ).powf(2.4) }, + blue: if srgba.blue() <= 0.04045 { srgba.blue() / 12.92 } else { ( ( srgba.blue() + 0.055 ) / 1.055 ).powf(2.4) }, + alpha: srgba.alpha() + } + } + + pub fn from_xyza(xyz: Xyza) -> Self { + Self { + red: 3.2404542 * xyz.x() + -1.5371385 * xyz.y() + -0.4985314 * xyz.z(), + green: -0.9692660 * xyz.x() + 1.8760108 * xyz.y() + 0.0415560 * xyz.z(), + blue: 0.0556434 * xyz.x() + -0.2040259 * xyz.y() + 1.0572252 * xyz.z(), + alpha: 1.0 + } + } + + pub fn to_rgba(&self) -> sRgba { + sRgba::::new( + if self.red <= 0.0031308 { self.red * 12.92 } else { 1.055 * self.red.powf( 1.0 / 2.4 ) - 0.055 }, + if self.green <= 0.0031308 { self.green * 12.92 } else { 1.055 * self.green.powf( 1.0 / 2.4 ) - 0.055 }, + if self.blue <= 0.0031308 { self.blue * 12.92 } else { 1.055 * self.blue.powf( 1.0 / 2.4 ) - 0.055 }, + self.alpha + ) + } + + pub fn to_rgba8(&self) -> sRgba { + let color = self.to_rgba(); + + sRgba::::new( + (color.red() * 255.0) as u8, + (color.green() * 255.0) as u8, + (color.blue() * 255.0) as u8, + (color.alpha() * 255.0) as u8, + ) + } + + pub fn to_oklaba(&self) -> Oklaba { + let l = 0.4122214708 * self.red + 0.5363325363 * self.green + 0.0514459929 * self.blue; + let m = 0.2119034982 * self.red + 0.6806995451 * self.green + 0.1073969566 * self.blue; + let s = 0.0883024619 * self.red + 0.2817188376 * self.green + 0.6299787005 * self.blue; + + let l_ = l.cbrt(); + let m_ = m.cbrt(); + let s_ = s.cbrt(); + + Oklaba::new( + 0.2104542553*l_ + 0.7936177850*m_ - 0.0040720468*s_, + 1.9779984951*l_ - 2.4285922050*m_ + 0.4505937099*s_, + 0.0259040371*l_ + 0.7827717662*m_ - 0.8086757660*s_, + self.alpha + ) + } + + pub fn to_oklcha(&self) -> Oklcha { + self.to_oklaba().to_oklcha() + } + + pub fn to_xyza(&self) -> Xyza { + Xyza::new( + 0.4124564 * self.red + 0.3575761 * self.green + 0.1804375 * self.blue, + 0.2126729 * self.red + 0.7151522 * self.green + 0.0721750 * self.blue, + 0.0193339 * self.red + 0.1191920 * self.green + 0.9503041 * self.blue, + self.alpha + ) + } + + pub fn to_laba(&self) -> Laba { + self.to_xyza().to_laba() + } + + pub fn to_lcha(&self) -> Lcha { + self.to_laba().to_lcha() + } + + pub fn to_hwba(&self) -> Hwba { + self.to_rgba().to_hwba() + } + + pub fn to_hsva(&self) -> Hsva { + self.to_hwba().to_hsva() + } + + pub fn to_hsla(&self) -> Hsla { + self.to_hsva().to_hsla() + } + + pub fn to_wgpu(&self) -> Color { + Color { + r: self.red as f64, + g: self.green as f64, + b: self.blue as f64, + a: self.alpha as f64 + } + } +} \ No newline at end of file diff --git a/crates/comet_colors/src/oklaba.rs b/crates/comet_colors/src/oklaba.rs new file mode 100644 index 0000000..67baeeb --- /dev/null +++ b/crates/comet_colors/src/oklaba.rs @@ -0,0 +1,112 @@ +use crate::{sRgba, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklcha, Xyza}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Oklaba { + lightness: f32, + a: f32, + b: f32, + alpha: f32 +} + +impl Oklaba { + pub fn new(lightness: f32, green_red: f32, blue_yellow: f32, alpha: f32) -> Self { + assert!((0.0..=1.0).contains(&lightness) && (-1.0..=1.0).contains(&green_red) && (-1.0..=1.0).contains(&blue_yellow) && (0.0..=1.0).contains(&alpha), "Ligthness needs to be in range 0..1.0\nA needs to be in range -1.0..1.0\nB needs to be in range -1.0..1.0\nAlpha needs to be in range 0..1"); + Self { + lightness, + a: green_red, + b: blue_yellow, + alpha + } + } + + pub fn lightness(&self) -> f32 { + self.lightness + } + + pub fn a(&self) -> f32 { + self.a + } + + pub fn b(&self) -> f32 { + self.b + } + + pub fn alpha(&self) -> f32 { + self.alpha + } + + pub fn from_linear(linear: LinearRgba) -> Self { + let l = 0.4122214708 * linear.red() + 0.5363325363 * linear.green() + 0.0514459929 * linear.blue(); + let m = 0.2119034982 * linear.red() + 0.6806995451 * linear.green() + 0.1073969566 * linear.blue(); + let s = 0.0883024619 * linear.red() + 0.2817188376 * linear.green() + 0.6299787005 * linear.blue(); + + let l_ = l.cbrt(); + let m_ = m.cbrt(); + let s_ = s.cbrt(); + + Self { + lightness: 0.2104542553*l_ + 0.7936177850*m_ - 0.0040720468*s_, + a: 1.9779984951*l_ - 2.4285922050*m_ + 0.4505937099*s_, + b: 0.0259040371*l_ + 0.7827717662*m_ - 0.8086757660*s_, + alpha: linear.alpha() + } + } + + pub fn to_linear(&self) -> LinearRgba { + let l_ = self.lightness + 0.3963377774 * self.a + 0.2158037573 * self.b; + let m_ = self.lightness - 0.1055613458 * self.a - 0.0638541728 * self.b; + let s_ = self.lightness - 0.0894841775 * self.a - 1.2914855480 * self.b; + + let l = l_*l_*l_; + let m = m_*m_*m_; + let s = s_*s_*s_; + + LinearRgba::new( + 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, + -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, + -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s, + self.alpha + ) + } + + pub fn to_rgba(&self) -> sRgba { + self.to_linear().to_rgba() + } + + pub fn to_rgba8(&self) -> sRgba { + self.to_rgba().to_rgba8() + } + + pub fn to_oklcha(&self) -> Oklcha { + Oklcha::new( + self.lightness, + (self.a*self.a + self.b*self.b).sqrt(), + self.b.atan2(self.a), + self.alpha + ) + } + + pub fn to_xyza(&self) -> Xyza { + self.to_linear().to_xyza() + } + + pub fn to_laba(&self) -> Laba { + self.to_xyza().to_laba() + } + + pub fn to_lcha(&self) -> Lcha { + self.to_laba().to_lcha() + } + + pub fn to_hwba(&self) -> Hwba { + self.to_rgba().to_hwba() + } + + pub fn to_hsva(&self) -> Hsva { + self.to_hwba().to_hsva() + } + + pub fn to_hsla(&self) -> Hsla { + self.to_hsva().to_hsla() + } +} \ No newline at end of file diff --git a/crates/comet_colors/src/oklcha.rs b/crates/comet_colors/src/oklcha.rs new file mode 100644 index 0000000..6db6693 --- /dev/null +++ b/crates/comet_colors/src/oklcha.rs @@ -0,0 +1,91 @@ +use crate::{sRgba, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Xyza}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Oklcha { + lightness: f32, + chroma: f32, + hue: f32, + alpha: f32 +} + +impl Oklcha { + pub fn new(lightness: f32, chroma: f32, hue: f32, alpha: f32) -> Self { + assert!((0.0..=1.0).contains(&lightness) && (0.0..=1.0).contains(&chroma) && (0.0..=360.0).contains(&hue) && (0.0..=1.0).contains(&alpha), "Ligthness needs to be in range 0..1\nChroma needs to be in range 0..1\nHue needs to be in range 0..360\nAlpha needs to be in range 0..1"); + Self { + lightness, + chroma, + hue, + alpha + } + } + + pub fn lightness(&self) -> f32 { + self.lightness + } + + pub fn chroma(&self) -> f32 { + self.chroma + } + + pub fn hue(&self) -> f32 { + self.hue + } + + pub fn alpha(&self) -> f32 { + self.alpha + } + + pub fn from_oklaba(oklaba: Oklaba) -> Self { + Self { + lightness: oklaba.lightness(), + chroma: (oklaba.a()*oklaba.a() + oklaba.b()*oklaba.b()).sqrt(), + hue: oklaba.b().atan2(oklaba.a()), + alpha: oklaba.alpha() + } + } + + pub fn to_oklaba(&self) -> Oklaba { + Oklaba::new( + self.lightness(), + self.chroma() * self.hue().cos(), + self.chroma() * self.hue().sin(), + self.alpha() + ) + } + + pub fn to_linear(&self) -> LinearRgba { + self.to_oklaba().to_linear() + } + + pub fn to_xyza(&self) -> Xyza { + self.to_linear().to_xyza() + } + + pub fn to_laba(&self) -> Laba { + self.to_xyza().to_laba() + } + + pub fn to_lcha(&self) -> Lcha { + self.to_laba().to_lcha() + } + + pub fn to_rgba(&self) -> sRgba { + self.to_linear().to_rgba() + } + + pub fn to_rgba8(&self) -> sRgba { + self.to_linear().to_rgba8() + } + + pub fn to_hwba(&self) -> Hwba { + self.to_rgba().to_hwba() + } + + pub fn to_hsva(&self) -> Hsva { + self.to_hwba().to_hsva() + } + + pub fn to_hsla(&self) -> Hsla { + self.to_hsva().to_hsla() + } +} \ No newline at end of file diff --git a/crates/comet_colors/src/rgba.rs b/crates/comet_colors/src/rgba.rs new file mode 100644 index 0000000..f11a5dc --- /dev/null +++ b/crates/comet_colors/src/rgba.rs @@ -0,0 +1,359 @@ +use crate::{math::Vec4, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Oklcha, Xyza}; + +/// sRGB representation of color +/// There are two variants: `sRgba` and `sRgba` +/// The first one is your standard 0..255 RGB and the second is the normalized version with range 0..1 +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, PartialEq)] +pub struct sRgba { + red: T, + green: T, + blue: T, + alpha: T, +} + +impl sRgba { + pub fn new(red: u8, green: u8, blue: u8, alpha: u8) -> Self { + assert!((0..=255).contains(&red) && (0..=255).contains(&green) && (0..=255).contains(&blue) && (0..=255).contains(&alpha), "Red needs to be in range 0..255\nGreen needs to be in range 0..255\nBlue needs to be in range 0..255\nAlpha needs to be in range 0..255"); + Self { + red, + green, + blue, + alpha + } + } + + pub fn red(&self) -> u8 { + self.red + } + + pub fn green(&self) -> u8 { + self.green + } + + pub fn blue(&self) -> u8 { + self.blue + } + + pub fn alpha(&self) -> u8 { + self.alpha + } + + pub fn from_hex(hex: &str) -> Self { + let hex = hex.trim_start_matches("#"); + + if hex.len() != 8 { + panic!("The length of the hex string is not equal to 8!"); + } + + let red = match u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Red part is not a hex value!") { + Ok(v) => v, + Err(err) => panic!("{}", err) + }; + + let green = match u8::from_str_radix(&hex[2..4], 16).map_err(|_| "Green part is not a hex value!") { + Ok(v) => v, + Err(err) => panic!("{}", err) + }; + + let blue = match u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Blue part is not a hex value!") { + Ok(v) => v, + Err(err) => panic!("{}", err) + }; + + let alpha = match u8::from_str_radix(&hex[6..8], 16).map_err(|_| "Alpha part is not a hex value!") { + Ok(v) => v, + Err(err) => panic!("{}", err) + }; + + Self { + red, + green, + blue, + alpha + } + } + + pub fn from_rgba(rgba: sRgba) -> Self { + rgba.to_rgba8() + } + + pub fn from_hwba(hwba: Hwba) -> Self { + hwba.to_rgba8() + } + + pub fn from_hsva(hvsa: Hsva) -> Self { + hvsa.to_rgba8() + } + + pub fn from_hsla(hsla: Hsla) -> Self { + hsla.to_rgba8() + } + + pub fn from_xyza(xyza: Xyza) -> Self { + xyza.to_rgba8() + } + + pub fn from_laba(laba: Laba) -> Self { + laba.to_rgba8() + } + + pub fn from_lcha(lcha: Lcha) -> Self { + lcha.to_rgba8() + } + + pub fn from_oklaba(oklaba: Oklaba) -> Self { + oklaba.to_rgba8() + } + + pub fn from_oklcha(oklcha: Oklcha) -> Self { + oklcha.to_rgba8() + } + + pub fn to_rbga(&self) -> sRgba { + sRgba { + red: self.red as f32/255.0, + green: self.green as f32/255.0, + blue: self.blue as f32/255.0, + alpha: self.alpha as f32/255.0 + } + } + + pub fn to_linear(&self) -> LinearRgba { + self.to_rbga().to_linear() + } + + pub fn to_hwba(&self) -> Hwba { + self.to_rbga().to_hwba() + } + + pub fn to_hsva(&self) -> Hsva { + self.to_hwba().to_hsva() + } + + pub fn to_hsla(&self) -> Hsla { + self.to_hsva().to_hsla() + } + + pub fn to_oklaba(&self) -> Oklaba { + self.to_linear().to_oklaba() + } + + pub fn to_vec(&self) -> Vec4 { + Vec4::new( + self.red as f32, + self.green as f32, + self.blue as f32, + self.alpha as f32 + ) + } +} + +impl sRgba { + pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self { + assert!((0.0..1.0).contains(&red) && (0.0..1.0).contains(&green) && (0.0..1.0).contains(&blue) && (0.0..1.0).contains(&alpha), "Red needs to be in range 0..1\nGreen needs to be in range 0..1\nBlue needs to be in range 0..1\nAlpha needs to be in range 0..1"); + Self { + red, + green, + blue, + alpha + } + } + + pub fn red(&self) -> f32 { + self.red + } + + pub fn green(&self) -> f32 { + self.green + } + + pub fn blue(&self) -> f32 { + self.blue + } + + pub fn alpha(&self) ->f32 { + self.alpha + } + + pub fn from_hex(hex: &str) -> Self { + let hex = hex.trim_start_matches("#"); + + if hex.len() != 8 { + panic!("The length of the hex string is not equal to 6!"); + } + + let r = match u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Red is not a hex value!") { + Ok(v) => v, + Err(err) => panic!("{}", err) + }; + + let g = match u8::from_str_radix(&hex[2..4], 16).map_err(|_| "Green is not a hex value!") { + Ok(v) => v, + Err(err) => panic!("{}", err) + }; + + let b = match u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Blue is not a hex value!") { + Ok(v) => v, + Err(err) => panic!("{}", err) + }; + + let a = match u8::from_str_radix(&hex[6..8], 16).map_err(|_| "Alpha is not a hex value!") { + Ok(v) => v, + Err(err) => panic!("{}", err) + }; + + Self { + red: r as f32 / 255.0, + green: g as f32 / 255.0, + blue: b as f32 / 255.0, + alpha: a as f32 / 255.0 + } + } + + pub fn from_linear(linear: LinearRgba) -> Self { + Self { + red: if linear.red() <= 0.0031308 { 12.92 * linear.red() } else { 1.055 * linear.red().powf(1.0 / 2.4) - 0.055 }, + green: if linear.green() <= 0.0031308 { 12.92 * linear.green() } else { 1.055 * linear.green().powf(1.0 / 2.4) - 0.055 }, + blue: if linear.blue() <= 0.0031308 { 12.92 * linear.blue() } else { 1.055 * linear.blue().powf(1.0 / 2.4) - 0.055 }, + alpha: linear.alpha() + } + } + + pub fn from_rgba8(rgba: sRgba) -> Self { + Self { + red: rgba.red() as f32 / 255.0, + green: rgba.green() as f32 / 255.0, + blue: rgba.blue() as f32 / 255.0, + alpha: rgba.alpha() as f32 / 255.0 + } + } + + pub fn from_hwba(hwba: Hwba) -> Self { + hwba.to_rgba() + } + + pub fn from_hsva(hvsa: Hsva) -> Self { + hvsa.to_rgba() + } + + pub fn from_hsla(hsla: Hsla) -> Self { + hsla.to_rgba() + } + + pub fn from_xyza(xyza: Xyza) -> Self { + xyza.to_rgba() + } + + pub fn from_laba(laba: Laba) -> Self { + laba.to_rgba() + } + + pub fn from_lcha(lcha: Lcha) -> Self { + lcha.to_rgba() + } + + pub fn from_oklaba(oklaba: Oklaba) -> Self { + oklaba.to_rgba() + } + + pub fn from_oklcha(oklcha: Oklcha) -> Self { + oklcha.to_rgba() + } + + pub fn to_rgba8(&self) -> sRgba { + sRgba { + red: (self.red * 255.0) as u8, + green: (self.green * 255.0) as u8, + blue: (self.blue * 255.0) as u8, + alpha: (self.alpha * 255.0) as u8 + } + } + + pub fn to_linear(&self) -> LinearRgba { + LinearRgba::new( + if self.red() <= 0.04045 { self.red() / 12.92 } else { ( ( self.red() + 0.055 ) / 1.055 ).powf(2.4) }, + if self.green() <= 0.04045 { self.green() / 12.92 } else { ( ( self.green() + 0.055 ) / 1.055 ).powf(2.4) }, + if self.blue() <= 0.04045 { self.blue() / 12.92 } else { ( ( self.blue() + 0.055 ) / 1.055 ).powf(2.4) }, + self.alpha() + ) + } + + pub fn to_oklaba(&self) -> Oklaba { + self.to_linear().to_oklaba() + } + + pub fn to_oklcha(&self) -> Oklcha { + self.to_oklaba().to_oklcha() + } + + pub fn to_xyza(&self) -> Xyza { + self.to_linear().to_xyza() + } + + pub fn to_laba(&self) -> Laba { + self.to_xyza().to_laba() + } + + pub fn to_lcha(&self) -> Lcha { + self.to_laba().to_lcha() + } + + pub fn to_hwba(&self) -> Hwba { + let w = self.red.min(self.green).min(self.blue); + let v = self.red.max(self.green).max(self.blue); + let b = 1.0 - v; + + if v == w { + return Hwba::new( + 0.0, + w, + b, + self.alpha() + ) + } + + let f = if self.red == v { + (self.green - self.blue) / (v - w) + } else if self.green == v { + (self.blue - self.red) / (v - w) + } else { + (self.red - self.green) / (v - w) + }; + + let h = if self.red == v { + (f / 6.0) % 1.0 + } else if self.green == v { + (f + 2.0) / 6.0 + } else { + (f + 4.0) / 6.0 + }; + + let mut h = if h < 0.0 { h + 1.0 } else { h }; + h *= 360.0; + + Hwba::new( + h, + w, + b, + self.alpha() as f32 / 255.0 + ) + } + + pub fn to_hsva(&self) -> Hsva { + self.to_hwba().to_hsva() + } + + pub fn to_hsla(&self) -> Hsla { + self.to_hsva().to_hsla() + } + + pub fn to_vec(&self) -> Vec4 { + Vec4::new( + self.red, + self.green, + self.blue, + self.alpha + ) + } +} \ No newline at end of file diff --git a/crates/comet_colors/src/xyza.rs b/crates/comet_colors/src/xyza.rs new file mode 100644 index 0000000..59931b9 --- /dev/null +++ b/crates/comet_colors/src/xyza.rs @@ -0,0 +1,109 @@ +use crate::{sRgba, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Oklcha}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Xyza { + x: f32, + y: f32, + z: f32, + alpha: f32 +} + +impl Xyza { + pub fn new(x: f32, y: f32, z: f32, alpha: f32) -> Self { + assert!((0.0..=1.0).contains(&x) && (0.0..=1.0).contains(&y) && (0.0..=1.5).contains(&z) && (0.0..=1.0).contains(&alpha), "X needs to be in range 0..1\nY needs to be in range 0..1\nZ needs to be in range 0..1\nAlpha needs to be in range 0..1"); + Self { + x, + y, + z, + alpha + } + } + + pub fn x(&self) -> f32 { + self.x + } + + pub fn y(&self) -> f32 { + self.y + } + + pub fn z(&self) -> f32 { + self.z + } + + pub fn alpha(&self) -> f32 { + self.alpha + } + + pub fn from_linear(linear: LinearRgba) -> Self { + Self { + x: 0.4124564 * linear.red() + 0.3575761 * linear.green() + 0.1804375 * linear.blue(), + y: 0.2126729 * linear.red() + 0.7151522 * linear.green() + 0.0721750 * linear.blue(), + z: 0.0193339 * linear.red() + 0.1191920 * linear.green() + 0.9503041 * linear.blue(), + alpha: linear.alpha() + } + } + + pub fn to_linear(&self) -> LinearRgba { + LinearRgba::new( + 3.2404542 * self.x + -1.5371385 * self.y + -0.4985314 * self.z, + -0.9692660 * self.x + 1.8760108 * self.y + 0.0415560 * self.z, + 0.0556434 * self.x + -0.2040259 * self.y + 1.0572252 * self.z, + self.alpha + ) + } + + pub fn to_laba(&self) -> Laba { + let reference_white = Xyza::new(0.95047, 1.0, 1.08883, 1.0); + + let x_r = self.x / reference_white.x; + let y_r = self.y / reference_white.y; + let z_r = self.z / reference_white.z; + + let epsilon: f32 = 0.008856; + let kappa: f32 = 903.3; + + let f_x = if x_r > epsilon { x_r.cbrt() } else { ( kappa*x_r + 16.0 ) / 116.0 }; + let f_y = if x_r > epsilon { y_r.cbrt() } else { ( kappa*y_r + 16.0 ) / 116.0 }; + let f_z = if x_r > epsilon { z_r.cbrt() } else { ( kappa*z_r + 16.0 ) / 116.0 }; + + Laba::new( + 1.16*f_y-0.16, + 5.0*( f_x - f_y ), + 2.0*( f_y - f_z ), + self.alpha + ) + } + + pub fn to_lcha(&self) -> Lcha { + self.to_laba().to_lcha() + } + + pub fn to_oklaba(&self) -> Oklaba { + self.to_linear().to_oklaba() + } + + pub fn to_oklcha(&self) -> Oklcha { + self.to_oklaba().to_oklcha() + } + + pub fn to_rgba(&self) -> sRgba { + self.to_linear().to_rgba() + } + + pub fn to_rgba8(&self) -> sRgba { + self.to_linear().to_rgba8() + } + + pub fn to_hwba(&self) -> Hwba { + self.to_rgba().to_hwba() + } + + pub fn to_hsva(&self) -> Hsva { + self.to_hwba().to_hsva() + } + + pub fn to_hsla(&self) -> Hsla { + self.to_hsva().to_hsla() + } +} \ No newline at end of file diff --git a/crates/comet_ecs/Cargo.toml b/crates/comet_ecs/Cargo.toml new file mode 100644 index 0000000..36cd445 --- /dev/null +++ b/crates/comet_ecs/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "comet_ecs" +version = "0.1.0" +edition = "2021" + +[dependencies] +bit-set = "0.8.0" +component_derive = { path = "./component_derive" } +comet_math = { path = "../comet_math" } +comet_resources = { path = "../comet_resources" } +comet_log = { path = "../comet_log" } +chrono = "0.4" \ No newline at end of file diff --git a/crates/comet_ecs/component_derive/Cargo.toml b/crates/comet_ecs/component_derive/Cargo.toml new file mode 100644 index 0000000..6fdbf02 --- /dev/null +++ b/crates/comet_ecs/component_derive/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "component_derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = "2.0" +quote = "1.0" \ No newline at end of file diff --git a/crates/comet_ecs/component_derive/src/lib.rs b/crates/comet_ecs/component_derive/src/lib.rs new file mode 100644 index 0000000..fd9fa0a --- /dev/null +++ b/crates/comet_ecs/component_derive/src/lib.rs @@ -0,0 +1,129 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput, Data, Fields}; + +// This is the procedural macro for `derive(MyTrait)` +#[proc_macro_derive(Component)] +pub fn my_trait_derive(input: TokenStream) -> TokenStream { + // Parse the input tokens into a syntax tree (AST) + let input = parse_macro_input!(input as DeriveInput); + + // Get the name of the struct + let name = &input.ident; + + let fields = if let Data::Struct(data) = &input.data { + match &data.fields { + Fields::Named(fields) => fields.named.iter().collect::>(), + Fields::Unnamed(fields) => fields.unnamed.iter().collect::>(), + Fields::Unit => Vec::new(), + } + } else { + panic!("Component derive macro only works on structs"); + }; + + let field_comparisons = fields.iter().map(|field| { + let field_name = &field.ident; // Name of the field + quote! { + self.#field_name == other.#field_name + } + }); + + let default_fields = if let Data::Struct(data) = &input.data { + match &data.fields { + Fields::Named(fields) => { + // Generate code to assign each field a default value + fields.named.iter().map(|field| { + let field_name = &field.ident; + quote! { #field_name: Default::default() } + }).collect::>() + }, + Fields::Unnamed(fields) => { + // Generate default values for tuple structs + fields.unnamed.iter().map(|_field| { + quote! { Default::default() } + }).collect::>() + }, + Fields::Unit => Vec::new(), + } + } else { + panic!("Default can only be derived for structs"); + }; + + let debug_fields = fields.iter().map(|field| { + let field_name = field.ident.as_ref().expect("Expected named fields"); + quote! { + .field(stringify!(#field_name), &self.#field_name) + } + }); + + for field in &fields { + if !is_copy_field(&field) { + panic!("All fields in the struct must implement Copy"); + } + } + + // Generate the implementation of MyTrait for the given struct + let expanded = quote! { + impl Component for #name { + fn new() -> Self { + Default::default() + } + + fn type_id() -> std::any::TypeId { + std::any::TypeId::of::() + } + + fn type_name() -> String { + std::any::type_name::().to_string() + } + } + + impl Default for #name { + fn default() -> Self { + // Construct the struct with default values + Self { + #(#default_fields),* + } + } + } + + impl Clone for #name { + fn clone(&self) -> Self { + Self { + ..*self + } + } + } + + impl Copy for #name {} + + impl std::fmt::Debug for #name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(stringify!(#name)) + #(#debug_fields)* + .finish() + } + } + + impl PartialEq for #name { + fn eq(&self, other: &Self) -> bool { + true #(&& #field_comparisons)* + } + } + }; + + // Convert the generated code into a TokenStream and return it + TokenStream::from(expanded) +} + + +fn is_copy_field(field: &syn::Field) -> bool { + // Logic to check if the field type implements Copy (this is simplified) + // You might need more sophisticated logic to check the actual type of the field. + let field_type = &field.ty; + // Implement a check for Copy trait for the field type if needed + // Return true if it implements Copy; false otherwise + true // Assuming it does, just for simplicity +} \ No newline at end of file diff --git a/crates/comet_ecs/src/component.rs b/crates/comet_ecs/src/component.rs new file mode 100644 index 0000000..9401547 --- /dev/null +++ b/crates/comet_ecs/src/component.rs @@ -0,0 +1,182 @@ +use std::path::Path; +use crate::math::{ + Vec2, + Vec3 +}; +use component_derive::Component; + +pub trait Component: Send + Sync + PartialEq + Default + 'static { + fn new() -> Self where Self: Sized; + + fn type_id() -> std::any::TypeId { + std::any::TypeId::of::() + } + + fn type_name() -> String { + std::any::type_name::().to_string() + } +} + +// ################################################## +// # BASIC # +// ################################################## + +#[derive(Component)] +pub struct Position2D { + x: f32, + y: f32 +} + +#[derive(Component)] +pub struct Position3D { + x: f32, + y: f32, + z: f32 +} + +#[derive(Component)] +pub struct Rotation2D { + theta: f32 +} + +#[derive(Component)] +pub struct Rotation3D { + theta_x: f32, + theta_y: f32, + theta_z: f32 +} + +#[derive(Component)] +pub struct Renderer2D { + is_visible: bool, + texture: &'static str, + scale: f32 +} + +impl Position2D { + pub fn from_vec(vec: Vec2) -> Self { + Self { + x: vec.x(), + y: vec.y() + } + } + + pub fn as_vec(&self) -> Vec2 { + Vec2::new( + self.x, + self.y + ) + } + + pub fn x(&self) -> &f32 { + &self.x + } + + pub fn y(&self) -> &f32 { + &self.y + } + + pub fn set_x(&mut self, new_x: f32) { + self.x = new_x; + } + + pub fn set_y(&mut self, new_y: f32) { + self.y = new_y; + } +} + +impl Position3D { + pub fn from_vec(vec: Vec3) -> Self { + Self { + x: vec.x(), + y: vec.y(), + z: vec.z() + } + } + + pub fn as_vec(&self) -> Vec3 { + Vec3::new( + self.x, + self.y, + self.z + ) + } + + pub fn x(&self) -> &f32 { + &self.x + } + + pub fn y(&self) -> &f32 { + &self.y + } + + pub fn z(&self) -> &f32 { + &self.z + } + + pub fn set_x(&mut self, new_x: f32) { + self.x = new_x; + } + + pub fn set_y(&mut self, new_y: f32) { + self.y = new_y; + } + + pub fn set_z(&mut self, new_z: f32) { + self.z = new_z + } +} + +// ################################################## +// # BUNDLES # +// ################################################## + +#[derive(Component)] +pub struct Transform2D { + position: Position2D, + rotation: Rotation2D +} + +#[derive(Component)] +pub struct Transform3D { + position: Position3D, + rotation: Rotation3D +} + +impl Transform2D { + pub fn position(&self) -> &Position2D { + &self.position + } + + pub fn position_mut(&mut self) -> &mut Position2D { + &mut self.position + } + + pub fn rotation(&self) -> &Rotation2D { + &self.rotation + } + + pub fn rotation_mut(&mut self) -> &mut Rotation2D { + &mut self.rotation + } +} + +impl Transform3D { + pub fn position(&self) -> &Position3D { + &self.position + } + + pub fn position_mut(&mut self) -> &mut Position3D { + &mut self.position + } + + pub fn rotation(&self) -> &Rotation3D { + &self.rotation + } + + pub fn rotation_mut(&mut self) -> &mut Rotation3D { + &mut self.rotation + } +} + + diff --git a/crates/comet_ecs/src/entity.rs b/crates/comet_ecs/src/entity.rs new file mode 100644 index 0000000..809b288 --- /dev/null +++ b/crates/comet_ecs/src/entity.rs @@ -0,0 +1,35 @@ +use bit_set::BitSet; +use crate::ComponentSet; + +#[derive(Debug, Clone, PartialEq)] +pub struct Entity { + id: u32, + components: BitSet +} + +impl Entity { + pub fn new(id: u32) -> Self { + let mut components = BitSet::new(); + components.insert(0); + Self { + id, + components + } + } + + pub fn id(&self) -> &u32 { + &self.id + } + + pub(crate) fn add_component(&mut self, component_index: usize) { + self.components.insert(component_index); + } + + pub(crate) fn remove_component(&mut self, component_index: usize) { + self.components.remove(component_index); + } + + pub(crate) fn get_components(&self) -> &BitSet { + &self.components + } +} \ No newline at end of file diff --git a/crates/comet_ecs/src/id.rs b/crates/comet_ecs/src/id.rs new file mode 100644 index 0000000..b09b52b --- /dev/null +++ b/crates/comet_ecs/src/id.rs @@ -0,0 +1,40 @@ +#[derive(Debug, Clone)] +pub struct IdQueue { + queue: Vec +} + +impl IdQueue { + pub fn new() -> Self { + Self { queue: Vec::new() } + } + + pub fn from_vec(queue: Vec) -> Self { + Self { queue } + } + + pub fn front(&self) -> Option { + Some(self.queue[0]) + } + + pub fn enqueue(&mut self, id: u32) { + self.queue.push(id) + } + + pub fn sorted_enqueue(&mut self, id: u32) { + self.enqueue(id); + self.queue.sort(); + } + + pub fn dequeue(&mut self) -> Option { + Some(self.queue.remove(0)) + + } + + pub fn is_empty(&self) -> bool { + self.queue.len() == 0 + } + + pub fn size(&self) -> u32 { + self.queue.len() as u32 + } +} diff --git a/crates/comet_ecs/src/lib.rs b/crates/comet_ecs/src/lib.rs new file mode 100644 index 0000000..46d1fdc --- /dev/null +++ b/crates/comet_ecs/src/lib.rs @@ -0,0 +1,13 @@ +pub use storage::*; +pub use entity::*; +pub use component::*; +pub use world::*; +pub use id::*; +pub use component_derive::*; +pub use comet_math as math; + +mod storage; +mod entity; +mod component; +mod world; +mod id; \ No newline at end of file diff --git a/crates/comet_ecs/src/storage.rs b/crates/comet_ecs/src/storage.rs new file mode 100644 index 0000000..cbf06e7 --- /dev/null +++ b/crates/comet_ecs/src/storage.rs @@ -0,0 +1,577 @@ +use crate::{Component}; +use std::{ + alloc::{ + handle_alloc_error, + Layout + }, + any::TypeId, + collections::{ + HashMap, + HashSet + }, + hash::{ + DefaultHasher, + Hash, + Hasher + }, + mem::MaybeUninit, + ptr::NonNull +}; +use std::ptr; + +#[derive(Debug, Clone)] +pub struct BlobVec { + item_layout: Layout, + capacity: usize, + len: usize, + data: NonNull, + swap_scratch: NonNull, + drop: unsafe fn(*mut u8) +} + + +impl BlobVec { + pub fn new(item_layout: Layout, drop: unsafe fn(*mut u8), capacity: usize) -> Self { + if item_layout.size() == 0 { + BlobVec { + swap_scratch: NonNull::dangling(), + data: NonNull::dangling(), + capacity: usize:: MAX, + len: 0, + item_layout, + drop, + } + } + else { + let swap_scratch = NonNull::new(unsafe { std::alloc::alloc(item_layout) }) + .unwrap_or_else(|| handle_alloc_error(item_layout)); + + let mut blob_vec = BlobVec { + swap_scratch, + data: NonNull::dangling(), + capacity: 0, + len: 0, + item_layout, + drop, + }; + blob_vec.reserve_exact(capacity); + blob_vec + } + } + + pub fn reserve_exact(&mut self, additional: usize) { + let available_space = self.capacity - self.len; + if available_space < additional { + self.grow_exact(additional - available_space); + } + } + + fn grow_exact(&mut self, increment: usize) { + debug_assert!(self.item_layout.size() != 0); + + let new_capacity = self.capacity + increment; + let new_layout = + array_layout(&self.item_layout, new_capacity).expect("array layout should be valid"); + unsafe { + let new_data = if self.capacity == 0 { + std::alloc::alloc(new_layout) + } else { + std::alloc::realloc( + self.get_ptr().as_ptr(), + array_layout(&self.item_layout, self.capacity) + .expect("array layout should be valid"), + new_layout.size(), + ) + }; + + self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout)); + } + self.capacity = new_capacity; + } + + + #[inline] + pub fn len(&self) -> usize { + self.len + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + #[inline] + pub fn capacity(&self) -> usize { + self.capacity + } + + + #[inline] + pub unsafe fn get_ptr(&self) -> NonNull { + self.data + } + + #[inline] + pub unsafe fn push_uninit(&mut self) -> usize { + self.reserve_exact(1); + let index = self.len; + self.len += 1; + index + } + + #[inline] + pub unsafe fn get_unchecked(&self, index: usize) -> *mut u8 { + debug_assert!(index < self.len()); + self.get_ptr().as_ptr().add(index * self.item_layout.size()) + } + + #[inline] + pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> *mut u8 { + debug_assert!(index < self.len()); + self.get_ptr().as_ptr().add(index * self.item_layout.size()) + } + + pub unsafe fn push_element(&mut self, element: T) { + let index = self.push_uninit(); + let ptr = self.get_unchecked(index) as *mut T; + ptr::write(ptr,element); + } + + pub fn clear(&mut self) { + let len = self.len; + // We set len to 0 _before_ dropping elements for unwind safety. This ensures we don't + // accidentally drop elements twice in the event of a drop impl panicking. + self.len = 0; + for i in 0..len { + unsafe { + // NOTE: this doesn't use self.get_unchecked(i) because the debug_assert on index + // will panic here due to self.len being set to 0 + let ptr = self.get_ptr().as_ptr().add(i * self.item_layout.size()); + (self.drop)(ptr); + } + } + } + + #[inline] + pub unsafe fn swap_remove_and_forget_unchecked(&mut self, index: usize) -> *mut u8 { + debug_assert!(index < self.len()); + let last = self.len - 1; + let swap_scratch = self.swap_scratch.as_ptr(); + ptr::copy_nonoverlapping( + self.get_unchecked(index), + swap_scratch, + self.item_layout.size(), + ); + ptr::copy( + self.get_unchecked(last), + self.get_unchecked(index), + self.item_layout.size(), + ); + self.len -= 1; + swap_scratch + } + + #[inline] + pub unsafe fn initialize_unchecked(&mut self, index: usize, value: *mut u8) { + debug_assert!(index < self.len()); + let ptr = self.get_unchecked(index); + ptr::copy_nonoverlapping(value, ptr, self.item_layout.size()); + } +} + +impl Drop for BlobVec { + fn drop(&mut self) { + self.clear(); + let array_layout = + array_layout(&self.item_layout, self.capacity).expect("array layout should be valid"); + if array_layout.size() > 0 { + unsafe { + std::alloc::dealloc(self.get_ptr().as_ptr(), array_layout); + std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout); + } + } + } +} + +fn array_layout(layout: &Layout, n: usize) -> Option { + let (array_layout, offset) = repeat_layout(layout, n)?; + debug_assert_eq!(layout.size(), offset); + Some(array_layout) +} + +fn repeat_layout(layout: &Layout, n: usize) -> Option<(Layout, usize)> { + let padded_size = layout.size() + padding_needed_for(layout, layout.align()); + let alloc_size = padded_size.checked_mul(n)?; + + unsafe { + Some(( + Layout::from_size_align_unchecked(alloc_size, layout.align()), + padded_size, + )) + } +} + +const fn padding_needed_for(layout: &Layout, align: usize) -> usize { + let len = layout.size(); + let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1); + len_rounded_up.wrapping_sub(len) +} + +#[derive(Debug, Clone)] +pub struct Column { + pub data: BlobVec +} + +impl Column { + pub fn new(capacity: usize) -> Self { + let layout = Layout::new::(); + let drop_fn = |ptr: *mut u8| unsafe { + ptr::drop_in_place(ptr as *mut T); + }; + Self { + data: BlobVec::new(layout, drop_fn, capacity), + } + } + + pub fn data(&self) -> BlobVec { + self.data.clone() + } + + pub fn push(&mut self, item: T) { + assert_eq!(TypeId::of::(), TypeId::of::(), "Type mismatch"); + unsafe { + let index = self.data.push_uninit(); + let ptr = self.data.get_unchecked(index); + ptr::write(ptr as *mut T, item); + } + } + + pub fn get(&self, index: usize) -> Option<&T> { + assert_eq!(TypeId::of::(), TypeId::of::(), "Type mismatch"); + if index >= self.data.len() { + return None; + } + unsafe { + let ptr = self.data.get_unchecked(index); + Some(&*(ptr as *const T)) + } + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + assert_eq!(TypeId::of::(), TypeId::of::(), "Type mismatch"); + + if index >= self.data.len() { + return None; + } + + // Access the element at the given index + unsafe { + let ptr = self.data.get_unchecked(index); + // Convert the pointer to a mutable reference and return it + Some(&mut *(ptr as *mut T)) + } + } + + pub fn remove(&mut self, index: usize) -> Option { + assert_eq!(TypeId::of::(), TypeId::of::(), "Type mismatch"); + if index >= self.data.len() { + return None; + } + unsafe { + let ptr = self.data.swap_remove_and_forget_unchecked(index); + Some(ptr::read(ptr as *const T)) + } + } + + fn swap(&mut self, index1: usize, index2: usize) { + assert!(index1 < self.data.len() && index2 < self.data.len(), "Index out of bounds"); + + unsafe { + let ptr1 = self.data.get_unchecked(index1); + let ptr2 = self.data.get_unchecked(index2); + + let mut temp = MaybeUninit::::uninit(); + + // Swap the elements at index1 and index2 + ptr::copy_nonoverlapping(ptr1, temp.as_mut_ptr(), self.data.item_layout.size()); + ptr::copy_nonoverlapping(ptr2, ptr1, self.data.item_layout.size()); + ptr::copy_nonoverlapping(temp.as_ptr(), ptr2, self.data.item_layout.size()); + } + } +} + +pub struct IterMut<'a, K, V> { + keys_iter: std::slice::IterMut<'a, K>, + values_iter: std::slice::IterMut<'a, V>, +} + +impl<'a, K, V> Iterator for IterMut<'a, K, V> { + type Item = (&'a mut K, &'a mut V); + + fn next(&mut self) -> Option { + match (self.keys_iter.next(), self.values_iter.next()) { + (Some(key), Some(value)) => Some((key, value)), + _ => None, + } + } +} + +#[derive(Debug, Clone)] +pub struct SparseSet { + sparse: Vec>, + dense: Column, + len: usize +} + +impl SparseSet { + pub fn new(capacity: usize) -> Self { + Self { + sparse: Vec::with_capacity(capacity), + dense: Column::new::(capacity), + len: 0 + } + } + + pub fn set(&mut self, index: usize, element: T) { + if index >= self.sparse.len() { + self.sparse.resize_with(index + 1, || None); + } + + if let Some(column_index) = self.sparse[index] { + // Explicitly drop the existing component before replacing it + unsafe { + let existing_ptr = self.dense.data.get_unchecked_mut(column_index) as *mut T; + ptr::drop_in_place(existing_ptr); + ptr::write(existing_ptr, element); + } + } else { + let column_index = unsafe { self.dense.data.push_uninit() }; + unsafe { + self.dense.data.initialize_unchecked(column_index, &element as *const T as *mut u8); + } + self.sparse[index] = Some(column_index); + self.len += 1; + } + } + + pub fn remove(&mut self, index: usize) -> Option { + if index >= self.sparse.len() || self.sparse[index] == None { + return None; + } + + let column_index = self.sparse[index]; + let element = unsafe { + self.dense.data.swap_remove_and_forget_unchecked(column_index.unwrap()) + }; + + self.sparse[index] = None; + self.len -= 1; + + Some(unsafe { ptr::read(element as *const T) }) + } + + pub fn get(&self, index: usize) -> Option<&T> { + if index >= self.sparse.len() || self.sparse[index] == None { + return None; + } + + self.dense.get::(index) + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + if index >= self.sparse.len() || self.sparse[index] == None { + return None; + } + + self.dense.get_mut::(index) + } +} + +#[derive(Debug, Clone)] +pub struct ComponentStorage { + index_map: HashMap, + pub(crate) keys: Vec, + components: Vec +} + +impl ComponentStorage { + pub fn new() -> Self { + Self { + index_map: HashMap::new(), + keys: Vec::new(), + components: Vec::new() + } + } + + pub fn keys(&self) -> &Vec { + &self.keys + } + + pub fn contains_component(&self, type_id: &TypeId) -> bool { + self.keys.contains(type_id) + } + + pub fn get(&self) -> Option<&SparseSet> { + self.components.get(*self.index_map.get(&T::type_id()).unwrap()) + } + + pub fn set(&mut self, sparse_set: SparseSet) { + let _ = self.components.get_mut(*self.index_map.get(&T::type_id()).unwrap()); + } + + pub fn register_component(&mut self, capacity: usize) { + //self.storage.insert(T::type_id(), SparseSet::new::(capacity)); + assert!(!self.keys.contains(&T::type_id()), "This component ({}) is already registered!", T::type_name()); + self.keys.push(T::type_id()); + self.index_map.insert(T::type_id(), self.keys.len()-1); + self.components.push(SparseSet::new::(capacity)); + } + + pub fn get_component(&self, entity_id: usize) -> Option<&T> { + //self.storage.get(&T::type_id()).unwrap().get::(*entity.id() as usize) + self.components.get(*self.index_map.get(&T::type_id()).unwrap()).unwrap().get::(entity_id) + } + + pub fn get_component_mut(&mut self, entity_id: usize) -> Option<&mut T> { + self.components.get_mut(*self.index_map.get(&T::type_id()).unwrap()).unwrap().get_mut::(entity_id) + } + + pub fn set_component(&mut self, entity_id: usize, component: T) { + let index = *self.index_map.get(&T::type_id()).unwrap(); + let sparse_set = self.components.get_mut(index).unwrap(); + + // Check if a component already exists for this entity + if let Some(existing_component) = sparse_set.get_mut::(entity_id) { + // Explicitly drop the existing component before overwriting it + std::mem::drop(existing_component); + } + + // Set the new component + sparse_set.set(entity_id, component); + } + + pub fn deregister_component(&mut self) { + let type_id = T::type_id(); + if let Some(&index) = self.index_map.get(&type_id) { + // Before removing the SparseSet, ensure all elements are properly dropped + let sparse_set = self.components.get_mut(index).unwrap(); + for i in 0..sparse_set.sparse.len() { + if sparse_set.sparse[i].is_some() { + sparse_set.remove::(i); + } + } + + self.components.remove(index); + self.index_map.remove(&type_id); + self.keys.retain(|&k| k != type_id); + } + } + + pub fn remove_component(&mut self, entity_id: usize) { + if let Some(index) = self.index_map.get(&T::type_id()) { + let sparse_set = self.components.get_mut(*index).unwrap(); + sparse_set.remove::(entity_id); + } + } + + pub(crate) fn get_dense_list_as_vec(&self) -> Option> { + let mut resulting_vec: Vec = Vec::new(); + + if let Some(sparse_set) = self.components.get(*self.index_map.get(&T::type_id()).unwrap()) { + for i in 0..sparse_set.dense.data.len() { + let item: T = sparse_set.dense.get::(i)?.clone(); + resulting_vec.push(item); + } + Some(resulting_vec) + } else { + None + } + } + + pub fn iter_mut(&mut self) -> IterMut<'_, TypeId, SparseSet> { + IterMut { + keys_iter: self.keys.iter_mut(), + values_iter: self.components.iter_mut(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ComponentSet { + set: HashSet +} + +impl ComponentSet { + pub fn new() -> Self { + Self { + set: HashSet::new() + } + } + + pub fn from_ids(ids: Vec) -> Self { + Self { + set: ids.into_iter().collect() + } + } + + pub fn is_subset(&self, other: &ComponentSet) -> bool { + self.set.is_subset(&other.set) + } +} + +impl Hash for ComponentSet { + fn hash(&self, state: &mut H) { + let mut types: Vec = self.set.iter().cloned().collect(); + types.sort(); + types.hash(state); + } +} + +#[derive(Debug)] +pub struct Archetypes { + archetypes: HashMap> +} + +impl Archetypes { + pub fn new() -> Self { + Self { + archetypes: HashMap::new() + } + } + + pub fn component_sets(&self) -> Vec { + self.archetypes.keys().cloned().collect() + } + + pub fn create_archetype(&mut self, components: ComponentSet) { + self.archetypes.insert(components, Vec::new()); + } + + pub fn get_archetype(&self, components: &ComponentSet) -> Option<&Vec> { + self.archetypes.get(components) + } + + pub fn get_archetype_mut(&mut self, components: &ComponentSet) -> Option<&mut Vec> { + self.archetypes.get_mut(components) + } + + pub fn add_entity_to_archetype(&mut self, components: &ComponentSet, entity: u32) { + if let Some(archetype) = self.archetypes.get_mut(components) { + archetype.push(entity); + } + } + + pub fn remove_entity_from_archetype(&mut self, components: &ComponentSet, entity: u32) { + if let Some(archetype) = self.archetypes.get_mut(components) { + archetype.retain(|&id| id != entity); + } + } + + pub fn remove_archetype(&mut self, components: &ComponentSet) { + self.archetypes.remove(components); + } + + pub fn contains_archetype(&self, components: &ComponentSet) -> bool { + self.archetypes.contains_key(components) + } +} diff --git a/crates/comet_ecs/src/world.rs b/crates/comet_ecs/src/world.rs new file mode 100644 index 0000000..4da1a5e --- /dev/null +++ b/crates/comet_ecs/src/world.rs @@ -0,0 +1,209 @@ +use std::any::TypeId; +use bit_set::BitSet; +use crate::{ + Entity, + Component, + Transform2D, + Transform3D, + ComponentStorage, + SparseSet, + IdQueue, + Archetypes, + ComponentSet +}; +use comet_log::*; + +pub struct World { + dimension: String, + id_queue: IdQueue, + next_id: u32, + entities: Vec>, + components: ComponentStorage, + archetypes: Archetypes +} + +impl World { + pub fn new(dimension: &str) -> Self { + let mut component_storage = ComponentStorage::new(); + match dimension { + "2D" => component_storage.register_component::(0), + "3D" => component_storage.register_component::(0), + _ => {} + } + + Self { + dimension: dimension.to_string(), + id_queue: IdQueue::new(), + next_id: 0, + entities: Vec::new(), + components: component_storage, + archetypes: Archetypes::new() + } + } + + pub fn active_entities(&self) -> u32 { + self.entities.len() as u32 - self.id_queue.size() + } + + fn get_next_id(&mut self) { + if self.id_queue.is_empty() { + self.next_id = self.entities.len() as u32; + return; + } + if self.next_id > self.id_queue.front().unwrap() || self.entities[self.next_id as usize] != None { + self.next_id = self.id_queue.dequeue().unwrap(); + } + } + + pub fn dimension(&self) -> &String { + &self.dimension + } + + pub fn id_queue(&self) -> &IdQueue { + &self.id_queue + } + + pub fn entities(&self) -> &Vec> { + &self.entities + } + + pub fn components(&self) -> &ComponentStorage { + &self.components + } + + pub fn components_mut(&mut self) -> &mut ComponentStorage { + &mut self.components + } + + pub fn new_entity(&mut self) -> u32 { + let id = self.next_id; + if (self.next_id as usize) >= self.entities.len() { + self.entities.push(Some(Entity::new(self.next_id))); + match self.dimension.as_str() { + "2D" => self.add_component::(self.next_id as usize, Transform2D::new()), + "3D" => self.add_component::(self.next_id as usize, Transform3D::new()), + _ => {} + } + self.get_next_id(); + return id; + } + self.entities[self.next_id as usize] = Some(Entity::new(self.next_id)); + println!("{:?}", self.dimension); + match self.dimension.as_str() { + "2D" => self.add_component::(self.next_id as usize, Transform2D::new()), + "3D" => self.add_component::(self.next_id as usize, Transform3D::new()), + _ => {} + } + self.get_next_id(); + id + } + + pub fn get_entity(&self, entity_id: usize) -> &Entity { + assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); + self.entities.get(entity_id).unwrap().as_ref().unwrap() + } + + pub fn get_entity_mut(&mut self, entity_id: usize) -> &mut Entity { + assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); + self.entities.get_mut(entity_id).unwrap().as_mut().unwrap() + //self.entities.get_mut(id).unwrap() + } + + pub fn delete_entity(&mut self, entity_id: usize) { + self.entities[entity_id] = None; + //self.get_entity(id); + for (key, value) in self.components.iter_mut() { + value.remove::(entity_id); + } + self.id_queue.sorted_enqueue(entity_id as u32); + self.get_next_id(); + self.remove_entity_from_archetype_subsets(entity_id as u32, self.get_component_set(entity_id)); + info!(format!("Deleted entity! ID: {}", entity_id)); + } + + fn create_archetype(&mut self, components: ComponentSet) { + self.archetypes.create_archetype(components); + } + + fn add_entity_to_archetype(&mut self, entity_id: u32, components: ComponentSet) { + self.archetypes.add_entity_to_archetype(&components, entity_id); + } + + fn remove_entity_from_archetype(&mut self, entity_id: u32, components: ComponentSet) { + self.archetypes.remove_entity_from_archetype(&components, entity_id); + } + + fn remove_entity_from_archetype_subsets(&mut self, entity_id: u32, components: ComponentSet) { + let component_sets = self.archetypes.component_sets(); + let keys: Vec = component_sets.iter() + .enumerate() + .filter_map(|(i, &ref elem)| if elem.is_subset(&components) { Some(i) } else { None }) + .collect::>() + .iter() + .map(|index| component_sets[*index].clone()) + .collect::>(); + + for key in keys { + self.remove_entity_from_archetype(entity_id, key.clone()); + if self.archetypes.get_archetype(&key).unwrap().len() == 0 { + self.archetypes.remove_archetype(&key); + } + } + } + + fn get_component_set(&self, entity_id: usize) -> ComponentSet { + let components = self.entities.get(entity_id).unwrap().as_ref().unwrap().get_components().iter().collect::>(); + let type_ids = components.iter().map(|index| self.components.keys[*index]).collect::>(); + ComponentSet::from_ids(type_ids) + } + + pub fn register_component(&mut self) { + self.components.register_component::(self.entities.len()); + info!(format!("Registered component: {}", T::type_name())); + } + + pub fn deregister_component(&mut self) { + self.components.deregister_component::(); + info!(format!("Deregistered component: {}", T::type_name())); + } + + pub fn add_component(&mut self, entity_id: usize, component: T) { + assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); + self.components.set_component(entity_id, component); + let component_index = self.components.keys.iter_mut().position(|x| *x == T::type_id()).unwrap(); + + self.get_entity_mut(entity_id).add_component(component_index); + + if !self.archetypes.contains_archetype(&self.get_component_set(entity_id)) { + self.create_archetype(self.get_component_set(entity_id)); + } + self.add_entity_to_archetype(entity_id as u32, self.get_component_set(entity_id)); + + info!(format!("Added component {} to entity {}", T::type_name(), entity_id)); + debug!(format!("{:?}", self.archetypes)); + } + + pub fn remove_component(&mut self, entity_id: usize) { + self.components.remove_component::(entity_id); + self.remove_entity_from_archetype_subsets(entity_id as u32, self.get_component_set(entity_id)); + info!(format!("Removed component {} from entity {}", T::type_name(), entity_id)); + } + + pub fn get_component(&self, entity_id: usize) -> &T { + assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); + assert!(self.components.get_component::(entity_id) != None, "There is no component {} bound to the entity {} in the world!", T::type_name(), entity_id); + self.components.get_component::(entity_id).unwrap() + } + + pub fn get_component_mut(&mut self, entity_id: usize) -> &mut T { + assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); + assert!(self.components.get_component::(entity_id) != None, "There is no component {} bound to the entity {} in the world!", T::type_name(), entity_id); + self.components.get_component_mut::(entity_id).unwrap() + } + + pub fn get_entities_with(&self, components: ComponentSet) -> Vec { + assert!(self.archetypes.contains_archetype(&components), "The given components {:?} are not registered in the world!", components); + info!(format!("Querying entities with components: {:?}", components)); + self.archetypes.get_archetype(&components).unwrap().clone() + } +} diff --git a/crates/comet_input/Cargo.toml b/crates/comet_input/Cargo.toml new file mode 100644 index 0000000..4e273f4 --- /dev/null +++ b/crates/comet_input/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comet_input" +version = "0.1.0" +edition = "2021" + +[dependencies] +winit = { version = "0.29", features = ["rwh_05"] } \ No newline at end of file diff --git a/crates/comet_input/src/keyboard.rs b/crates/comet_input/src/keyboard.rs new file mode 100644 index 0000000..cb0b36d --- /dev/null +++ b/crates/comet_input/src/keyboard.rs @@ -0,0 +1,32 @@ +use winit::event::{ElementState, KeyEvent, WindowEvent}; +use winit::keyboard::{ KeyCode, PhysicalKey}; + +pub type Key = KeyCode; + +pub fn key_pressed(event: &WindowEvent, key_code: Key) -> bool { + match event { + WindowEvent::KeyboardInput { + event: KeyEvent { + state: ElementState::Pressed, + physical_key: PhysicalKey::Code(code), + .. + }, + .. + } => *code == key_code, + _ => false, + } +} + +pub fn key_released(event: &WindowEvent, key_code: Key) -> bool { + match event { + WindowEvent::KeyboardInput { + event: KeyEvent { + state: ElementState::Released, + physical_key: PhysicalKey::Code(code), + .. + }, + .. + } => *code == key_code, + _ => false, + } +} \ No newline at end of file diff --git a/crates/comet_input/src/lib.rs b/crates/comet_input/src/lib.rs new file mode 100644 index 0000000..73cdf76 --- /dev/null +++ b/crates/comet_input/src/lib.rs @@ -0,0 +1,2 @@ +pub mod keyboard; +pub mod mouse; diff --git a/crates/comet_input/src/mouse.rs b/crates/comet_input/src/mouse.rs new file mode 100644 index 0000000..708978f --- /dev/null +++ b/crates/comet_input/src/mouse.rs @@ -0,0 +1,89 @@ +use winit::event::{ + ElementState, + WindowEvent, + MouseButton, + MouseScrollDelta +}; + +pub type Button = MouseButton; + +pub fn mouse_pressed(event: &WindowEvent, button: Button) -> bool { + match event { + WindowEvent::MouseInput { + button: button_pressed, + state: ElementState::Pressed, + .. + } => *button_pressed == button, + _ => false, + } +} + +pub fn mouse_released(event: &WindowEvent, button: Button) -> bool { + match event { + WindowEvent::MouseInput { + button: button_released, + state: ElementState::Released, + .. + } => *button_released == button, + _ => false, + } +} + +pub fn mouse_wheel_vertical(event: &WindowEvent) -> f32 { + match event { + WindowEvent::MouseWheel { + delta: MouseScrollDelta::LineDelta(_, y), + .. + } => *y, + WindowEvent::MouseWheel { + delta: MouseScrollDelta::PixelDelta(p), + .. + } => p.y as f32, + _ => 0.0, + } +} + +pub fn mouse_wheel_horizontal(event: &WindowEvent) -> f32 { + match event { + WindowEvent::MouseWheel { + delta: MouseScrollDelta::LineDelta(x, _), + .. + } => *x, + WindowEvent::MouseWheel { + delta: MouseScrollDelta::PixelDelta(p), + .. + } => p.x as f32, + _ => 0.0, + } +} + +pub fn mouse_moved(event: &WindowEvent) -> (f64, f64) { + match event { + WindowEvent::CursorMoved { + position, + .. + } => (position.x, position.y), + _ => (0.0, 0.0), + } +} + +pub fn mouse_entered(event: &WindowEvent) -> bool { + match event { + WindowEvent::CursorEntered { .. } => true, + _ => false, + } +} + +pub fn mouse_exited(event: &WindowEvent) -> bool { + match event { + WindowEvent::CursorLeft { .. } => true, + _ => false, + } +} + +pub fn mouse_dragged(event: &WindowEvent) -> bool { + match event { + WindowEvent::CursorMoved { .. } => true, + _ => false, + } +} \ No newline at end of file diff --git a/crates/comet_log/Cargo.toml b/crates/comet_log/Cargo.toml new file mode 100644 index 0000000..cf8714d --- /dev/null +++ b/crates/comet_log/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "comet_log" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = "0.4" +colored = "2.0" diff --git a/crates/comet_log/src/lib.rs b/crates/comet_log/src/lib.rs new file mode 100644 index 0000000..605a50c --- /dev/null +++ b/crates/comet_log/src/lib.rs @@ -0,0 +1,47 @@ +#[macro_export] +macro_rules! info { + ($msg:expr) => { + println!( + "{} {} : {}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + "\x1b[32m\x1b[1mINFO\x1b[0m", + $msg + ); + }; +} + +#[macro_export] +macro_rules! debug { + ($msg:expr) => { + println!( + "{} {} : {}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + "\x1b[34m\x1b[1mDEBUG\x1b[0m", + $msg + ); + }; +} + +#[macro_export] +macro_rules! warn { + ($msg:expr) => { + println!( + "{} {} : {}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + "\x1b[33m\x1b[1mWARNING\x1b[0m", + $msg + ); + }; +} + +#[macro_export] +macro_rules! error { + ($msg:expr) => { + println!( + "{} {} : {}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + "\x1b[31m\x1b[1mERROR\x1b[0m", + $msg + ); + }; +} \ No newline at end of file diff --git a/crates/comet_math/Cargo.toml b/crates/comet_math/Cargo.toml new file mode 100644 index 0000000..8ecdd68 --- /dev/null +++ b/crates/comet_math/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comet_math" +version = "0.1.0" +edition = "2021" + +[dependencies] +num-traits = "0.2.19" diff --git a/crates/comet_math/src/bezier.rs b/crates/comet_math/src/bezier.rs new file mode 100644 index 0000000..68ae091 --- /dev/null +++ b/crates/comet_math/src/bezier.rs @@ -0,0 +1,145 @@ +use num_traits::Pow; + +use crate::point::{Point2, Point3}; +use crate::vector::{Vec2, Vec3}; +use crate::utilities::{lerp2, lerp3, fac}; + +pub trait ParameterCurve2 { + //fn arcLen(&self) -> f32; + fn getPoint(&self, t: f32) -> Point2; +} + +pub trait ParameterCurve3 { + //fn arcLen(&self) -> f32; + fn getPoint(&self, t: f32) -> Point3; +} + +/// A general Bézier Curve in 2D +/// WORK IN PROGRESS: DOES NOT WORK (use cBezier2 instead) +#[repr(C)] +pub struct Bezier2 { + points: Vec, + degree: u8 +} + +impl Bezier2 { + pub fn new(points: Vec) -> Self { + let n = points.len() as u8; + Self { points: points, degree: n } + } +} + + +/// A general Bézier Curve in 3D +/// WORK IN PROGRESS: DOES NOT WORK (use cBezier3 instead) +#[repr(C)] +pub struct Bezier3 { + points: Vec, + degree: u8 +} + +impl Bezier3 { + pub fn new(points: Vec) -> Self { + let n = points.len() as u8; + Self { points: points, degree: n } + } +} + +/// A cubic Bézier Curve in 2D +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct cBezier2 { + p0: Point2, + p1: Point2, + p2: Point2, + p3: Point2 +} + +impl cBezier2 { + pub fn new(p0: Point2, p1: Point2, p2: Point2, p3: Point2) -> Self { + Self { p0, p1, p2, p3 } + } +} + +impl ParameterCurve2 for cBezier2 { + fn getPoint(&self, t: f32) -> Point2 { + let tSquared = t * t; + let tCubed = tSquared * t; + let vP0 = Vec2::from_point(self.p0); + let vP1 = Vec2::from_point(self.p1); + let vP2 = Vec2::from_point(self.p2); + let vP3 = Vec2::from_point(self.p3); + + Point2::from_vec( + vP0 * (-tCubed + 3.0 * tSquared - 3.0 * t + 1.0 ) + + vP1 * (3.0 * tCubed - 6.0 * tSquared + 3.0 * t ) + + vP2 * (-3.0 * tCubed + 3.0 * tSquared ) + + vP3 * tCubed + ) + } +} + +/// A cubic Bézier Curve in 3D +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct cBezier3 { + p0: Point3, + p1: Point3, + p2: Point3, + p3: Point3 +} + +impl cBezier3 { + pub fn new(p0: Point3, p1: Point3, p2: Point3, p3: Point3) -> Self { + Self { p0, p1, p2, p3 } + } +} + +impl ParameterCurve2 for Bezier2 { + fn getPoint(&self, t: f32) -> Point2 { + let n = self.points.len(); + let mut points = self.points.clone(); + + for k in 1..n { + for i in 0..n - k { + points[i] = Point2::from_vec(lerp2(Vec2::from_point(points[i]), Vec2::from_point(points[i + 1]), t)); + } + } + + points[0] + } +} + +impl ParameterCurve3 for Bezier3 { + fn getPoint(&self, t: f32) -> Point3 { + let n = self.points.len(); + let mut points = self.points.clone(); + + for k in 1..n { + for i in 0..n - k { + points[i] = Point3::from_vec(lerp3(Vec3::from_point(points[i]), Vec3::from_point(points[i + 1]), t)); + } + } + + points[0] + } +} + +impl ParameterCurve3 for cBezier3 { + fn getPoint(&self, t: f32) -> Point3 { + let tSquared = t * t; + let tCubed = tSquared * t; + let vP0 = Vec3::from_point(self.p0); + let vP1 = Vec3::from_point(self.p1); + let vP2 = Vec3::from_point(self.p2); + let vP3 = Vec3::from_point(self.p3); + + Point3::from_vec( + vP0 * (-tCubed + 3.0 * tSquared - 3.0 * t + 1.0 ) + + vP1 * (3.0 * tCubed - 6.0 * tSquared + 3.0 * t ) + + vP2 * (-3.0 * tCubed + 3.0 * tSquared ) + + vP3 * tCubed + ) + } + +} diff --git a/crates/comet_math/src/easings.rs b/crates/comet_math/src/easings.rs new file mode 100644 index 0000000..d94dbf4 --- /dev/null +++ b/crates/comet_math/src/easings.rs @@ -0,0 +1,132 @@ +use crate::utilities::{PI, sin, cos, sqrt}; + +pub fn ease_in_sine(x: f32) -> f32 { + 1.0 - cos((x * PI) / 2.0) +} + +pub fn ease_out_sine(x: f32) -> f32 { + sin((x * PI) / 2.0) +} + +pub fn ease_in_out_sine(x: f32) -> f32 { + -(cos(PI * x) - 1.0) / 2.0 +} + +pub fn ease_in_quad(x: f32) -> f32 { + x * x +} + +pub fn ease_out_quad(x: f32) -> f32 { + 1.0 - (1.0 - x) * (1.0 - x) +} + +pub fn ease_in_out_quad(x: f32) -> f32 { + if x < 0.5 { 2.0 * x * x } else { 1.0 - (-2.0 * x + 2.0).powf(2.0) / 2.0 } +} + +pub fn ease_in_cubic(x: f32) -> f32 { + x * x * x +} + +pub fn ease_out_cubic(x: f32) -> f32 { + 1.0 - (1.0 - x).powf(3.0) +} + +pub fn ease_in_out_cubic(x: f32) -> f32 { + if x < 0.5 { 4.0 * x * x * x } else { 1.0 - (-2.0 * x + 2.0).powf(3.0) / 2.0 } +} + +pub fn ease_in_quart(x: f32) -> f32 { + x * x * x * x +} + +pub fn ease_out_quart(x: f32) -> f32 { + 1.0 - (1.0 - x).powf(4.0) +} + +pub fn ease_in_out_quart(x: f32) -> f32 { + if x < 0.5 { 8.0 * x * x * x * x } else { 1.0 - (-2.0 * x + 2.0).powf(4.0) / 2.0 } +} + +pub fn ease_in_quint(x: f32) -> f32 { + x * x * x * x * x +} + +pub fn ease_out_quint(x: f32) -> f32 { + 1.0 - (1.0 - x).powf(5.0) +} + +pub fn ease_in_out_quint(x: f32) -> f32 { + if x < 0.5 { 16.0 * x * x * x * x * x } else { 1.0 - (-2.0 * x + 2.0).powf(5.0) / 2.0 } +} + +pub fn ease_in_expo(x: f32) -> f32 { + if x == 0.0 { 0.0 } else { 2.0_f32.powf(10.0 * x - 10.0) } +} + +pub fn ease_out_expo(x: f32) -> f32 { + if x == 1.0 { 1.0 } else { 1.0 - 2.0_f32.powf(-10.0 * x) } +} + +pub fn ease_in_out_expo(x: f32) -> f32 { + if x == 0.0 { 0.0 } else if x == 1.0 { 1.0 } else if x < 0.5 { 2.0_f32.powf(20.0 * x - 10.0) / 2.0 } else { (2.0 - 2.0_f32.powf(-20.0 * x + 10.0)) / 2.0 } +} + +pub fn ease_in_circ(x: f32) -> f32 { + 1.0 - sqrt(1.0 - x * x) +} + +pub fn ease_out_circ(x: f32) -> f32 { + sqrt(1.0 - (x - 1.0).powf(2.0)) +} + +pub fn ease_in_out_circ(x: f32) -> f32 { + if x < 0.5 { sqrt(1.0 - (1.0 - 2.0 * x).powf(2.0)) / 2.0 } else { (sqrt(1.0 - (-2.0 * x + 2.0).powf(2.0)) + 1.0) / 2.0 } +} + +pub fn ease_in_back(x: f32) -> f32 { + let c1 = 1.70158; + let c3 = c1 + 1.0; + c3 * x * x * x - c1 * x * x +} + +pub fn ease_out_back(x: f32) -> f32 { + let c1 = 1.70158; + let c3 = c1 + 1.0; + 1.0 + c3 * (x - 1.0).powf(3.0) + c1 * (x - 1.0).powf(2.0) +} + +pub fn ease_in_out_back(x: f32) -> f32 { + let c1 = 1.70158; + let c2 = c1 * 1.525; + if x < 0.5 { (2.0 * x).powf(2.0) * ((c2 + 1.0) * 2.0 * x - c2) / 2.0 } else { ((2.0 * x - 2.0).powf(2.0) * ((c2 + 1.0) * (2.0 * x - 2.0) + c2) + 2.0) / 2.0 } +} + +pub fn ease_in_elastic(x: f32) -> f32 { + let c4 = (2.0 * PI) / 3.0; + if x == 0.0 { 0.0 } else if x == 1.0 { 1.0 } else { -2.0_f32.powf(10.0 * x - 10.0) * sin((x * 10.0 - 10.75) * c4) } +} + +pub fn ease_out_elastic(x: f32) -> f32 { + let c4 = (2.0 * PI) / 3.0; + if x == 0.0 { 0.0 } else if x == 1.0 { 1.0 } else { 2.0_f32.powf(-10.0 * x) * sin((x * 10.0 - 0.75) * c4) + 1.0 } +} + +pub fn ease_in_out_elastic(x: f32) -> f32 { + let c5 = (2.0 * PI) / 4.5; + if x == 0.0 { 0.0 } else if x == 1.0 { 1.0 } else if x < 0.5 { -(2.0_f32.powf(20.0 * x - 10.0) * sin((20.0 * x - 11.125) * c5)) / 2.0 } else { (2.0_f32.powf(-20.0 * x + 10.0) * sin((20.0 * x - 11.125) * c5)) / 2.0 + 1.0 } +} + +pub fn ease_in_bounce(x: f32) -> f32 { + 1.0 - ease_out_bounce(1.0 - x) +} + +pub fn ease_out_bounce(x: f32) -> f32 { + let n1 = 7.5625; + let d1 = 2.75; + if x < 1.0 / d1 { n1 * x * x } else if x < 2.0 / d1 { n1 * (x - 1.5 / d1) * (x - 1.5 / d1) + 0.75 } else if x < 2.5 / d1 { n1 * (x - 2.25 / d1) * (x - 2.25 / d1) + 0.9375 } else { n1 * (x - 2.625 / d1) * (x - 2.625 / d1) + 0.984375 } +} + +pub fn ease_in_out_bounce(x: f32) -> f32 { + if x < 0.5 { (1.0 - ease_out_bounce(1.0 - 2.0 * x)) / 2.0 } else { (1.0 + ease_out_bounce(2.0 * x - 1.0)) / 2.0 } +} diff --git a/crates/comet_math/src/lib.rs b/crates/comet_math/src/lib.rs new file mode 100644 index 0000000..94e748c --- /dev/null +++ b/crates/comet_math/src/lib.rs @@ -0,0 +1,14 @@ +pub(crate) use utilities::*; +pub use point::*; +pub use vector::*; +pub use matrix::*; +pub use bezier::*; +pub use easings::*; + +mod utilities; +pub mod point; +pub mod vector; +pub mod matrix; +pub mod quaternion; +pub mod bezier; +pub mod easings; \ No newline at end of file diff --git a/crates/comet_math/src/matrix.rs b/crates/comet_math/src/matrix.rs new file mode 100644 index 0000000..be3fd89 --- /dev/null +++ b/crates/comet_math/src/matrix.rs @@ -0,0 +1,732 @@ +use std::ops::{Add, Sub, Mul, Div}; +use crate::{cross, dot, Point3}; +use crate::vector::{Vec2, Vec3, Vec4}; + +trait LinearTransformation { + fn det(&self) -> f32; +} + +// ################################################## +// # MATRIX 2D # +// ################################################## + + +/// Representation of a 2x2 Matrix in row major +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, PartialEq)] +pub struct Mat2 { + x00: f32, + x01: f32, + x10: f32, + x11: f32, +} + +impl Mat2 { + pub const ZERO: Mat2 = Mat2 { + x00: 0.0, x01: 0.0, + x10: 0.0, x11: 0.0 + }; + + pub const IDENTITY: Mat2 = Mat2 { + x00: 1.0, x01: 0.0, + x10: 0.0, x11: 1.0 + }; + pub const fn new(x00: f32, x01: f32, x10: f32, x11: f32) -> Self { + Self { + x00, x01, + x10, x11 + } + } + + pub fn from_vec(row1: Vec2, row2: Vec2) -> Self { + Self { + x00: row1.x(), x01: row1.y(), + x10: row2.x(), x11: row2.y() + } + } + + pub fn get(&self, row: usize, col: usize) -> Option { + assert!(row <= 1, "This row ({}) is out of bounds! Bounds: 0..1", row); + assert!(col <= 1, "This row ({}) is out of bounds! Bounds: 0..1", col); + + match (row, col) { + (0, 0) => Some(self.x00), + (0, 1) => Some(self.x01), + (1, 0) => Some(self.x10), + (1, 1) => Some(self.x11), + _ => None, + } + } + + pub fn get_row(&self, row: usize) -> Option { + assert!(row <= 1, "This row ({}) is out of bounds! Bounds: 0..1", row); + match row { + 0 => Some(Vec2::new(self.x00, self.x01)), + 1 => Some(Vec2::new(self.x10, self.x11)), + _ => None + } + } + + pub fn set(&mut self, row: usize, col: usize, element: f32) { + assert!(row <= 1, "This row ({}) is out of bounds! Bounds: 0..1", row); + assert!(col <= 1, "This row ({}) is out of bounds! Bounds: 0..1", col); + + match (row, col) { + (0,0) => self.x00 = element, + (0,1) => self.x01 = element, + (1,0) => self.x10 = element, + (1,1) => self.x11 = element, + _ => {} + } + } + + pub fn set_row(&mut self, row: usize, row_content: Vec2) { + assert!(row <= 1, "This row ({}) is out of bounds! Bounds: 0..1", row); + + match row { + 0 => { self.x00 = row_content.x(); self.x01 = row_content.y(); }, + 1 => { self.x10 = row_content.x(); self.x11 = row_content.y(); }, + _ => {} + } + } + + pub fn det(&self) -> f32 { + self.x00 * self.x11 + - self.x01 * self.x10 + } + + pub fn transpose(&self) -> Self { + Self { + x00: self.x00, x01: self.x10, + x10: self.x01, x11: self.x11 + } + } + + pub fn swap_rows(&mut self, row1: usize, row2: usize) { + let tmp = self.get_row(row1).expect(format!("This row ({}) is out of bounds! Bounds: 0..1", row1).as_str()); + self.set_row(row1, self.get_row(row2).expect(format!("This row ({}) is out of bounds! Bounds: 0..1", row2).as_str())); + self.set_row(row2, tmp); + } +} + +impl Add for Mat2 { + type Output = Self; + + fn add(self, other: Mat2) -> Self { + Self { + x00: self.x00 + other.x00, x01: self.x01 + other.x01, + x10: self.x10 + other.x10, x11: self.x11 + other.x11 + } + } +} + +impl Sub for Mat2 { + type Output = Self; + + fn sub(self, other: Mat2) -> Self { + Self { + x00: self.x00 - other.x00, x01: self.x01 - other.x01, + x10: self.x10 - other.x10, x11: self.x11 - other.x11 + } + } +} + +impl Mul for Mat2 { + type Output = Self; + + fn mul(self, other: f32) -> Self { + Self { + x00: self.x00 * other, x01: self.x01 * other, + x10: self.x10 * other, x11: self.x11 * other + } + } +} + +impl Mul for f32 { + type Output = Mat2; + + fn mul(self, other: Mat2) -> Mat2 { + Mat2 { + x00: self * other.x00, x01: self * other.x01, + x10: self * other.x10, x11: self * other.x11 + } + } +} + +impl Mul for Mat2 { + type Output = Self; + + fn mul(self, other: Mat2) -> Self { + Self { + x00: self.x00 * other.x00 + self.x01 * other.x10, + x01: self.x00 * other.x01 + self.x01 * other.x11, + x10: self.x10 * other.x00 + self.x11 * other.x10, + x11: self.x10 * other.x01 + self.x11 * other.x11 + } + } +} + +impl Mul for Mat2 { + type Output = Vec2; + + fn mul(self, other: Vec2) -> Vec2 { + Vec2::new( + self.x00 * other.x() + self.x01 * other.y(), + self.x10 * other.x() + self.x11 * other.y() + ) + } +} + +impl Div for Mat2 { + type Output = Self; + + fn div(self, other: f32) -> Self { + let inv = 1.0 / other; + inv * self + } +} + +impl Into<[[f32; 2]; 2]> for Mat2 { + fn into(self) -> [[f32; 2]; 2] { + [ + [self.x00, self.x01], + [self.x10, self.x11], + ] + } +} + +// ################################################## +// # MATRIX 3D # +// ################################################## + +/// Representation of a 3x3 Matrix +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, PartialEq)] +pub struct Mat3 { + x00: f32, + x01: f32, + x02: f32, + x10: f32, + x11: f32, + x12: f32, + x20: f32, + x21: f32, + x22: f32 +} + +impl Mat3 { + pub const ZERO: Mat3 = Mat3 { + x00: 0.0, x01: 0.0, x02: 0.0, + x10: 0.0, x11: 0.0, x12: 0.0, + x20: 0.0, x21: 0.0, x22: 0.0 + }; + pub const IDENTITY: Mat3 = Mat3 { + x00: 1.0, x01: 0.0, x02: 0.0, + x10: 0.0, x11: 1.0, x12: 0.0, + x20: 0.0, x21: 0.0, x22: 1.0 + }; + pub const fn new(x00: f32, x01: f32, x02: f32, x10: f32, x11: f32, x12: f32, x20: f32, x21: f32, x22: f32) -> Self { + Self { + x00, x01, x02, + x10, x11, x12, + x20, x21, x22 + } + } + + pub fn from_vec(row1: Vec3, row2: Vec3, row3: Vec3) -> Self { + Self { + x00: row1.x(), x01: row1.y(), x02: row1.z(), + x10: row2.x(), x11: row2.y(), x12: row2.z(), + x20: row3.x(), x21: row3.y(), x22: row3.z() + } + } + + pub fn get(&self, row: usize, col: usize) -> Option { + assert!(row <= 2, "This row ({}) is out of bounds! Bounds: 0..2", row); + assert!(col <= 2, "This row ({}) is out of bounds! Bounds: 0..2", col); + match (row, col) { + (0, 0) => Some(self.x00), + (0, 1) => Some(self.x01), + (0, 2) => Some(self.x02), + (1, 0) => Some(self.x10), + (1, 1) => Some(self.x11), + (1, 2) => Some(self.x12), + (2, 0) => Some(self.x20), + (2, 1) => Some(self.x21), + (2, 2) => Some(self.x22), + _ => None, + } + } + + pub fn get_row(&self, row: usize) -> Option { + assert!(row <= 2, "This row ({}) is out of bounds! Bounds: 0..2", row); + match row { + 0 => Some(Vec3::new(self.x00, self.x01, self.x02)), + 1 => Some(Vec3::new(self.x10, self.x11, self.x12)), + 2 => Some(Vec3::new(self.x20, self.x21, self.x22)), + _ => None + } + } + + pub fn set(&mut self, row: usize, col: usize, element: f32) { + assert!(row <= 2, "This row ({}) is out of bounds! Bounds: 0..2", row); + assert!(col <= 2, "This row ({}) is out of bounds! Bounds: 0..2", col); + + match (row, col) { + (0,0) => self.x00 = element, + (0,1) => self.x01 = element, + (0,2) => self.x02 = element, + (1,0) => self.x10 = element, + (1,1) => self.x11 = element, + (1,2) => self.x12 = element, + (2,0) => self.x20 = element, + (2,1) => self.x21 = element, + (2,2) => self.x22 = element, + _ => {} + } + } + + pub fn set_row(&mut self, row: usize, row_content: Vec3) { + assert!(row <= 2, "This row ({}) is out of bounds! Bounds: 0..2", row); + match row { + 0 => { self.x00 = row_content.x; self.x01 = row_content.y; self.x02 = row_content.z; }, + 1 => { self.x10 = row_content.x; self.x11 = row_content.y; self.x12 = row_content.z; }, + 2 => { self.x20 = row_content.x; self.x21 = row_content.y; self.x22 = row_content.z; } + _ => {} + } + } + + pub fn det(&self) -> f32 { + self.x00 * self.x11 * self.x22 + + self.x01 * self.x12 * self.x20 + + self.x02 * self.x10 * self.x21 + - self.x02 * self.x11 * self.x20 + - self.x01 * self.x10 * self.x22 + - self.x00 * self.x12 * self.x21 + } + + pub fn transpose(&self) -> Self { + Self { + x00: self.x00, x01: self.x10, x02: self.x20, + x10: self.x01, x11: self.x11, x12: self.x21, + x20: self.x02, x21: self.x12, x22: self.x22 + } + } + + pub fn swap_rows(&mut self, row1: usize, row2: usize) { + let tmp = self.get_row(row1).expect(format!("This row ({}) is out of bounds! Bounds: 0..2", row1).as_str()); + self.set_row(row1, self.get_row(row2).expect(format!("This row ({}) is out of bounds! Bounds: 0..2", row2).as_str())); + self.set_row(row2, tmp); + } +} + +impl Add for Mat3 { + type Output = Self; + + fn add(self, other: Mat3) -> Self { + Self { + x00: self.x00 + other.x00, x01: self.x01 + other.x01, x02: self.x02 + other.x02, + x10: self.x10 + other.x10, x11: self.x11 + other.x11, x12: self.x12 + other.x12, + x20: self.x20 + other.x20, x21: self.x21 + other.x21, x22: self.x22 + other.x22 + } + } +} + +impl Sub for Mat3 { + type Output = Self; + + fn sub(self, other: Mat3) -> Self { + Self { + x00: self.x00 - other.x00, x01: self.x01 - other.x01, x02: self.x02 - other.x02, + x10: self.x10 - other.x10, x11: self.x11 - other.x11, x12: self.x12 - other.x12, + x20: self.x20 - other.x20, x21: self.x21 - other.x21, x22: self.x22 - other.x22 + } + } +} + +impl Mul for Mat3 { + type Output = Self; + + fn mul(self, other: f32) -> Self { + Self { + x00: self.x00 * other, x01: self.x01 * other, x02: self.x02 * other, + x10: self.x10 * other, x11: self.x11 * other, x12: self.x12 * other, + x20: self.x20 * other, x21: self.x21 * other, x22: self.x22 * other + } + } +} + +impl Mul for f32 { + type Output = Mat3; + + fn mul(self, other: Mat3) -> Mat3 { + Mat3 { + x00: self * other.x00, x01: self * other.x01, x02: self * other.x02, + x10: self * other.x10, x11: self * other.x11, x12: self * other.x12, + x20: self * other.x20, x21: self * other.x21, x22: self * other.x22 + } + } +} + +impl Mul for Mat3 { + type Output = Self; + + fn mul(self, other: Mat3) -> Self { + Self { + x00: self.x00 * other.x00 + self.x01 * other.x10 + self.x02 * other.x20, + x01: self.x00 * other.x01 + self.x01 * other.x11 + self.x02 * other.x21, + x02: self.x00 * other.x02 + self.x01 * other.x12 + self.x02 * other.x22, + x10: self.x10 * other.x00 + self.x11 * other.x10 + self.x12 * other.x20, + x11: self.x10 * other.x01 + self.x11 * other.x11 + self.x12 * other.x21, + x12: self.x10 * other.x02 + self.x11 * other.x12 + self.x12 * other.x22, + x20: self.x20 * other.x00 + self.x21 * other.x10 + self.x22 * other.x20, + x21: self.x20 * other.x01 + self.x21 * other.x11 + self.x22 * other.x21, + x22: self.x20 * other.x02 + self.x21 * other.x12 + self.x22 * other.x22 + } + } +} + +impl Mul for Mat3 { + type Output = Vec3; + + fn mul(self, other: Vec3) -> Vec3 { + Vec3::new( + self.x00 * other.x() + self.x01 * other.y() + self.x02 * other.z(), + self.x10 * other.x() + self.x11 * other.y() + self.x12 * other.z(), + self.x20 * other.x() + self.x21 * other.y() + self.x22 * other.z() + ) + } +} + +impl Div for Mat3 { + type Output = Self; + + fn div(self, other: f32) -> Self { + let inv = 1.0 / other; + inv * self + } +} + +impl Into<[[f32; 3]; 3]> for Mat3 { + fn into(self) -> [[f32; 3]; 3] { + [ + [self.x00, self.x01, self.x02], + [self.x10, self.x11, self.x12], + [self.x20, self.x21, self.x22], + ] + } +} + +// ################################################## +// # MATRIX 4D # +// ################################################## + +/// Representation of a 4x4 Matrix +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct Mat4 { + x00: f32, + x01: f32, + x02: f32, + x03: f32, + x10: f32, + x11: f32, + x12: f32, + x13: f32, + x20: f32, + x21: f32, + x22: f32, + x23: f32, + x30: f32, + x31: f32, + x32: f32, + x33: f32 +} + +impl Mat4 { + pub const ZERO: Mat4 = Mat4 { + x00: 0.0, x01: 0.0, x02: 0.0, x03: 0.0, + x10: 0.0, x11: 0.0, x12: 0.0, x13: 0.0, + x20: 0.0, x21: 0.0, x22: 0.0, x23: 0.0, + x30: 0.0, x31: 0.0, x32: 0.0, x33: 0.0 + }; + + pub const IDENTITY: Mat4 = Mat4 { + x00: 1.0, x01: 0.0, x02: 0.0, x03: 0.0, + x10: 0.0, x11: 1.0, x12: 0.0, x13: 0.0, + x20: 0.0, x21: 0.0, x22: 1.0, x23: 0.0, + x30: 0.0, x31: 0.0, x32: 0.0, x33: 1.0 + }; + + pub const fn new(x00: f32, x01: f32,x02: f32,x03: f32,x10: f32,x11: f32,x12: f32,x13: f32,x20: f32,x21: f32,x22: f32,x23: f32,x30: f32, x31: f32, x32: f32,x33: f32) -> Self { + Self { + x00, x01, x02, x03, + x10, x11, x12, x13, + x20, x21, x22, x23, + x30, x31, x32, x33 + } + } + + pub fn from_vec(row1: Vec4, row2: Vec4, row3: Vec4, row4: Vec4) -> Self { + Self { + x00: row1.x(), x01: row1.y(), x02: row1.z(), x03: row1.w(), + x10: row2.x(), x11: row2.y(), x12: row2.z(), x13: row2.w(), + x20: row3.x(), x21: row3.y(), x22: row3.z(), x23: row3.w(), + x30: row4.x(), x31: row4.y(), x32: row4.z(), x33: row4.w() + } + } + + pub fn rh_look_to(camera: Point3, dir: Vec3, up: Vec3) -> Self { + let f = dir.normalize(); + let s = cross(f, up).normalize(); + let u = cross(s,f); + let cam = camera.to_vec(); + + + /*Mat4::new( + s.x().clone(), u.x().clone(), -f.x().clone(), 0.0, + s.y().clone(), u.y().clone(), -f.y().clone(), 0.0, + s.z().clone(), u.z().clone(), -f.z().clone(), 0.0, + -dot(&cam, &s), -dot(&cam, &u), dot(&cam, &f),1.0 + )*/ + + Mat4::new( + s.x().clone(), s.y().clone(), s.z().clone(), 0.0, + u.x().clone(), u.y().clone(), u.z().clone(), 0.0, + -f.x().clone(), -f.y().clone(), -f.z().clone(), 0.0, + -dot(&cam, &s), -dot(&cam, &u), dot(&cam, &f), 1.0 + ) + + } + + pub fn lh_look_to(camera: Point3, dir: Vec3, up: Vec3) -> Self { + Self::lh_look_to(camera, dir * -1.0, up) + } + + pub fn look_at_rh(camera: Point3, center: Point3, up: Vec3) -> Mat4 { + Self::rh_look_to(camera, (center.to_vec() - camera.to_vec()), up) + } + + pub fn look_at_lh(camera: Point3, center: Point3, up: Vec3) -> Self { + Self::lh_look_to(camera, (center.to_vec() - camera.to_vec()), up) + } + + pub fn perspective_matrix(fovy: f32, aspect: f32, near: f32, far: f32) -> Self { + let angle = fovy * 0.5; + let ymax = near * angle.tan(); + let xmax = ymax * aspect; + + let left = -xmax; + let right = xmax; + let bottom = -ymax; + let top = ymax; + + /*Mat4::new( + (2.0 * near) / (right - left), 0.0, (right + left) / (right - left), 0.0, + 0.0, (2.0 * near) / (top - bottom), (top + bottom) / (top - bottom), 0.0, + 0.0, 0.0, -(far + near) / (far - near), -(2.0 * far * near) / (far - near), + 0.0, 0.0, -1.0, 0.0 + )*/ + + Mat4::new( + (2.0 * near) / (right - left), 0.0, 0.0, 0.0, + 0.0, (2.0 * near) / (top - bottom), 0.0, 0.0, + (right + left) / (right - left), (top + bottom) / (top - bottom), -(far + near) / (far - near), -1.0, + 0.0, 0.0, -(2.0 * far * near) / (far - near), 0.0 + ) + + } + + pub fn get(&self, row: usize, col: usize) -> Option { + assert!(row <= 3, "This row ({}) is out of bounds! Bounds: 0..3", row); + assert!(col <= 3, "This row ({}) is out of bounds! Bounds: 0..3", col); + match (row, col) { + (0, 0) => Some(self.x00), + (0, 1) => Some(self.x01), + (0, 2) => Some(self.x02), + (0, 3) => Some(self.x03), + (1, 0) => Some(self.x10), + (1, 1) => Some(self.x11), + (1, 2) => Some(self.x12), + (1, 3) => Some(self.x13), + (2, 0) => Some(self.x20), + (2, 1) => Some(self.x21), + (2, 2) => Some(self.x22), + (2, 3) => Some(self.x23), + (3, 0) => Some(self.x30), + (3, 1) => Some(self.x31), + (3, 2) => Some(self.x32), + (3, 3) => Some(self.x33), + _ => None, + } + } + + pub fn get_row(&self, row: usize) -> Option { + assert!(row <= 3, "This row ({}) is out of bounds! Bounds: 0..3", row); + match row { + 0 => Some(Vec4::new(self.x00, self.x01, self.x02, self.x03)), + 1 => Some(Vec4::new(self.x10, self.x11, self.x12, self.x13)), + 2 => Some(Vec4::new(self.x20, self.x21, self.x22, self.x23)), + 3 => Some(Vec4::new(self.x30, self.x31, self.x32, self.x33)), + _ => None + } + } + + pub fn set(&mut self, row: usize, col: usize, element: f32) { + assert!(row <= 3, "The given row ({}) is out of bounds! Bounds: 0..3", row); + assert!(col <= 3, "The given column ({}) is out of bounds! Bounds: 0..3", col); + match (row, col) { + (0,0) => self.x00 = element, + (0,1) => self.x01 = element, + (0,2) => self.x02 = element, + (0,3) => self.x03 = element, + (1,0) => self.x10 = element, + (1,1) => self.x11 = element, + (1,2) => self.x12 = element, + (1,3) => self.x13 = element, + (2,0) => self.x20 = element, + (2,1) => self.x21 = element, + (2,2) => self.x22 = element, + (2,3) => self.x23 = element, + (3,0) => self.x30 = element, + (3,1) => self.x31 = element, + (3,2) => self.x32 = element, + (3,3) => self.x33 = element, + _ => {} + } + } + + pub fn set_row(&mut self, row: usize, row_content: Vec4) { + assert!(row <= 3, "This row ({}) is out of bounds: Bounds: 0..3", row); + match row { + 0 => { self.x00 = row_content.x(); self.x01 = row_content.y(); self.x02 = row_content.z(); self.x03 = row_content.w(); }, + 1 => { self.x10 = row_content.x(); self.x11 = row_content.y(); self.x12 = row_content.z(); self.x13 = row_content.w(); }, + 2 => { self.x20 = row_content.x(); self.x21 = row_content.y(); self.x22 = row_content.z(); self.x23 = row_content.w(); }, + 3 => { self.x30 = row_content.x(); self.x31 = row_content.y(); self.x32 = row_content.z(); self.x33 = row_content.w(); } + _ => {} + } + } + + pub fn det(&self) -> f32 { + self.x00 * (self.x11 * (self.x22* self.x33 - self.x23 * self.x32) + - self.x21 * (self.x12 * self.x33 - self.x13 * self.x32) + + self.x31 * (self.x12 * self.x23 - self.x13 * self.x22)) + - self.x10 * (self.x01 * (self.x22* self.x33 - self.x23 * self.x32) + - self.x21 * (self.x02 * self.x33 - self.x32 * self.x03) + + self.x31 * (self.x02 * self.x23 - self.x22 * self.x03)) + + self.x20 * (self.x01 * (self.x12 * self.x33 - self.x13 * self.x32) + - self.x11 * (self.x02 * self.x33 - self.x03 * self.x32) + + self.x31 * (self.x02 * self.x13 - self.x03 * self.x12)) + - self.x30 * (self.x01 * (self.x12 * self.x23 - self.x22 * self.x13) + - self.x11 * (self.x02 * self.x23 - self.x22 * self.x03) + + self.x21 * (self.x02 * self.x13 - self.x03 * self.x12)) + } + + pub fn transpose(&self) -> Self { + Self { + x00: self.x00, x01: self.x10, x02: self.x20, x03: self.x30, + x10: self.x01, x11: self.x11, x12: self.x21, x13: self.x31, + x20: self.x02, x21: self.x12, x22: self.x22, x23: self.x32, + x30: self.x03, x31: self.x13, x32: self.x23, x33: self.x33 + } + } +} + +impl Add for Mat4 { + type Output = Self; + + fn add(self, other: Mat4) -> Self { + Self { + x00: self.x00 + other.x00, x01: self.x01 + other.x01, x02: self.x02 + other.x02, x03: self.x03 + other.x03, + x10: self.x10 + other.x10, x11: self.x11 + other.x11, x12: self.x12 + other.x12, x13: self.x13 + other.x13, + x20: self.x20 + other.x20, x21: self.x21 + other.x21, x22: self.x22 + other.x22, x23: self.x23 + other.x23, + x30: self.x30 + other.x30, x31: self.x31 + other.x31, x32: self.x32 + other.x32, x33: self.x33 + other.x33 + } + } +} + +impl Sub for Mat4 { + type Output = Self; + + fn sub(self, other: Mat4) -> Self { + Self { + x00: self.x00 - other.x00, x01: self.x01 - other.x01, x02: self.x02 - other.x02, x03: self.x03 - other.x03, + x10: self.x10 - other.x10, x11: self.x11 - other.x11, x12: self.x12 - other.x12, x13: self.x13 - other.x13, + x20: self.x20 - other.x20, x21: self.x21 - other.x21, x22: self.x22 - other.x22, x23: self.x23 - other.x23, + x30: self.x30 - other.x30, x31: self.x31 - other.x31, x32: self.x32 - other.x32, x33: self.x33 - other.x33 + } + } +} + +impl Mul for Mat4 { + type Output = Self; + + fn mul(self, other: f32) -> Self { + Self { + x00: self.x00 * other, x01: self.x01 * other, x02: self.x02 * other, x03: self.x03 * other, + x10: self.x10 * other, x11: self.x11 * other, x12: self.x12 * other, x13: self.x13 * other, + x20: self.x20 * other, x21: self.x21 * other, x22: self.x22 * other, x23: self.x23 * other, + x30: self.x30 * other, x31: self.x31 * other, x32: self.x32 * other, x33: self.x33 * other + } + } +} + +impl Div for Mat4 { + type Output = Self; + + fn div(self, other: f32) -> Self { + Self { + x00: self.x00 / other, x01: self.x01 / other, x02: self.x02 / other, x03: self.x03 / other, + x10: self.x10 / other, x11: self.x11 / other, x12: self.x12 / other, x13: self.x13 / other, + x20: self.x20 / other, x21: self.x21 / other, x22: self.x22 / other, x23: self.x23 / other, + x30: self.x30 / other, x31: self.x31 / other, x32: self.x32 / other, x33: self.x33 / other + } + } +} + +impl Mul for Mat4 { + type Output = Self; + + fn mul(self, other: Mat4) -> Self { + Self { + x00: self.x00 * other.x00 + self.x01 * other.x10 + self.x02 * other.x20 + self.x03 * other.x30, + x01: self.x00 * other.x01 + self.x01 * other.x11 + self.x02 * other.x21 + self.x03 * other.x31, + x02: self.x00 * other.x02 + self.x01 * other.x12 + self.x02 * other.x22 + self.x03 * other.x32, + x03: self.x00 * other.x03 + self.x01 * other.x13 + self.x02 * other.x23 + self.x03 * other.x33, + x10: self.x10 * other.x00 + self.x11 * other.x10 + self.x12 * other.x20 + self.x13 * other.x30, + x11: self.x10 * other.x01 + self.x11 * other.x11 + self.x12 * other.x21 + self.x13 * other.x31, + x12: self.x10 * other.x02 + self.x11 * other.x12 + self.x12 * other.x22 + self.x13 * other.x32, + x13: self.x10 * other.x03 + self.x11 * other.x13 + self.x12 * other.x23 + self.x13 * other.x33, + x20: self.x20 * other.x00 + self.x21 * other.x10 + self.x22 * other.x20 + self.x23 * other.x30, + x21: self.x20 * other.x01 + self.x21 * other.x11 + self.x22 * other.x21 + self.x23 * other.x31, + x22: self.x20 * other.x02 + self.x21 * other.x12 + self.x22 * other.x22 + self.x23 * other.x32, + x23: self.x20 * other.x03 + self.x21 * other.x13 + self.x22 * other.x23 + self.x23 * other.x33, + x30: self.x30 * other.x00 + self.x31 * other.x10 + self.x32 * other.x20 + self.x33 * other.x30, + x31: self.x30 * other.x01 + self.x31 * other.x11 + self.x32 * other.x21 + self.x33 * other.x31, + x32: self.x30 * other.x02 + self.x31 * other.x12 + self.x32 * other.x22 + self.x33 * other.x32, + x33: self.x30 * other.x03 + self.x31 * other.x13 + self.x32 * other.x23 + self.x33 * other.x33 + } + } +} + +impl Into<[[f32; 4]; 4]> for Mat4 { + fn into(self) -> [[f32; 4]; 4] { + [ + [self.x00, self.x01, self.x02, self.x03], + [self.x10, self.x11, self.x12, self.x13], + [self.x20, self.x21, self.x22, self.x23], + [self.x30, self.x31, self.x32, self.x33], + ] + } +} + +// ################################################## +// # MATRIX FUNCTIONS # +// ################################################## + +pub fn det(mat: &T) -> f32 { + mat.det() +} diff --git a/crates/comet_math/src/point.rs b/crates/comet_math/src/point.rs new file mode 100644 index 0000000..a11840e --- /dev/null +++ b/crates/comet_math/src/point.rs @@ -0,0 +1,62 @@ +use crate::vector::{Vec2, Vec3}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Point2 { + x: f32, + y: f32 +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Point3 { + x: f32, + y: f32, + z: f32 +} + +impl Point2 { + pub fn new(x: f32, y: f32) -> Self { + Point2 { x, y } + } + + pub fn from_vec(v: Vec2) -> Self { + Self { x: v.x(), y: v.y() } + } + + pub fn to_vec(&self) -> Vec2 { + Vec2::new(self.x, self.y) + } + + pub fn x(&self) -> f32 { + self.x + } + + pub fn y(&self) -> f32 { + self.y + } +} + +impl Point3 { + pub fn new(x: f32, y: f32, z: f32) -> Self { + Point3 { x, y, z } + } + + pub fn from_vec(v: Vec3) -> Self { + Self { x: v.x(), y: v.y(), z: v.z() } + } + + pub fn to_vec(&self) -> Vec3 { + Vec3::new(self.x, self.y, self.z) + } + + pub fn x(&self) -> f32 { + self.x + } + + pub fn y(&self) -> f32 { + self.y + } + + pub fn z(&self) -> f32 { + self.z + } +} diff --git a/crates/comet_math/src/quaternion.rs b/crates/comet_math/src/quaternion.rs new file mode 100644 index 0000000..5af3ae2 --- /dev/null +++ b/crates/comet_math/src/quaternion.rs @@ -0,0 +1,60 @@ +use std::ops::Mul; + +use crate::vector::Vec3; + +/// Representation of a quaternion in scalar/vector form +pub struct Quat { + pub s: f32, + pub v: Vec3, +} + +impl Quat { + pub const fn zero() -> Self { + Self { + s: 0.0, + v: Vec3 { + x: 0.0, + y: 0.0, + z: 0.0, + }, + } + } + pub const fn new(s: f32, v: Vec3) -> Self { + Self { s, v } + } + + pub fn conjugate(&self) -> Self { + Self { + s: self.s, + v: self.v * (-1.0), + } + } + + pub fn normalize(&self) -> Self { + let inverse_squareroot = 1.0/(self.s*self.s + self.v.x*self.v.x + self.v.y*self.v.y + self.v.z*self.v.z).sqrt(); + Self::new(self.s*inverse_squareroot, self.v*inverse_squareroot) + } + + pub fn into_vec(&self) -> Vec3 { + Vec3 { + x: self.v.x, + y: self.v.y, + z: self.v.z, + } + } +} + +impl Mul for Quat { + type Output = Quat; + + fn mul(self, other: Quat) -> Quat { + Quat { + s: self.s*other.s - self.v.x*other.v.x - self.v.y*other.v.y - self.v.z*other.v.z, + v: Vec3 { + x: self.s*other.v.x + self.v.x*other.s + self.v.y*other.v.z - self.v.z*other.v.y, + y: self.s*other.v.y + self.v.y*other.s + self.v.z*other.v.x - self.v.x*other.v.z, + z: self.s*other.v.z + self.v.z*other.s + self.v.x*other.v.y - self.v.y*other.v.x, + } + } + } +} diff --git a/crates/comet_math/src/utilities.rs b/crates/comet_math/src/utilities.rs new file mode 100644 index 0000000..3f85e10 --- /dev/null +++ b/crates/comet_math/src/utilities.rs @@ -0,0 +1,177 @@ +use crate::point::{Point2, Point3}; +use crate::vector::{Vec2, Vec3, Vec4, InnerSpace}; + +// ################################################## +// # CONSTANTS # +// ################################################## + +static FAC: [i64; 21] = [ + 1,1,2,6,24,120,720,5040,40320,362880,3628800, + 39916800,479001600,6227020800,87178291200,1307674368000, + 20922789888000,355687428096000,6402373705728000, + 121645100408832000,2432902008176640000 +]; + +static iFAC: [f32; 6] = [ + 1.0,1.0,0.5,0.1666666666666666667,0.04166666666666666667,0.00833333333333333334 +]; + +pub static PI: f32 = std::f32::consts::PI; + +// ################################################## +// # GENERAL PURPOSE # +// ################################################## + +pub fn fac(n: i64) -> i64 { + match n { + _ if n <= 21 => { FAC[n as usize] } + _ => n * fac(n-1) + } +} + +pub fn sqrt(x: f32) -> f32 { + x.sqrt() +} + +pub fn ln(x: f32) -> f32 { + x.ln() +} + +pub fn log(x: f32) -> f32 { + ln(x)/2.30258509299 +} + +pub fn log2(x: f32) -> f32 { + ln(x)/0.69314718056 +} + +pub fn sin(x: f32) -> f32 { + x.sin() +} + +pub fn asin(x: f32) -> f32 { + x.asin() +} + +pub fn cos(x: f32) -> f32 { + x.cos() +} + +pub fn acos(x: f32) -> f32 { + x.acos() +} + +pub fn tan(x: f32) -> f32 { + x.tan() +} + +pub fn atan(x: f32) -> f32 { + x.atan() +} + +pub fn atan2(p: Point2) -> f32 { + p.y().atan2(p.x()) +} + +pub fn sinh(x: f32) -> f32 { + x.sinh() +} + +pub fn cosh(x: f32) -> f32 { + x.cosh() +} + +pub fn tanh(x: f32) -> f32 { + x.tanh() +} + +pub fn clamp(start: f32, end: f32, value: f32) -> f32 { + match value { + _ if value > end => end, + _ if start > value => start, + _ => value + } +} + +pub fn pointDerivative(func: fn(f32) -> f32, x: f32, h: f32) -> f32 { + (func(x+h) - func(x-h))/(2.0 * h) +} + +// ################################################## +// # INTERPOLATION # +// ################################################## + +pub fn lerp(a: f32, b: f32, t: f32) -> f32 { + (1.0 - t) * a + t * b +} + +pub fn invLerp(a: f32, b:f32, value: f32) -> f32 { + (value - a) / (b - a) +} + +pub fn lerp2(a: Vec2, b: Vec2, t: f32) -> Vec2 { + a * (1.0 - t) + b * t +} + +pub fn invLerp2(a: Vec2, b: Vec2, value: Vec2) -> Option { + let tx = (value.x() - a.x()) / (b.x() - a.x()); + let ty = (value.y() - a.y()) / (b.y() - a.y()); + + if tx == ty { + return Some(tx); + } + None +} + +pub fn lerp3(a: Vec3, b: Vec3, t: f32) -> Vec3 { + a * (1.0 - t) + b * t +} + +pub fn invLerp3(a: Vec3, b: Vec3, value: Vec3) -> Option { + let tx = (value.x() - a.x())/(b.x() - a.x()); + let ty = (value.y() - a.y())/(b.y() - a.y()); + let tz = (value.z() - a.z())/(b.z() - a.z()); + + if (tx == ty) && (ty == tz) { + return Some(tx); + } + None +} + +// ################################################## +// # BEZIER CURVES # +// ################################################## + +/// Cubic Bézier Curve in R² +pub fn bezier2(p0: Point2, p1: Point2, p2: Point2, p3: Point2, t: f32) -> Point2 { + let tSquared = t * t; + let tCubed = tSquared * t; + let vP0 = Vec2::from_point(p0); + let vP1 = Vec2::from_point(p1); + let vP2 = Vec2::from_point(p2); + let vP3 = Vec2::from_point(p3); + + Point2::from_vec(vP0 * (-tCubed + 3.0 * tSquared - 3.0 * t + 1.0 ) + + vP1 * (3.0 * tCubed - 6.0 * tSquared + 3.0 * t ) + + vP2 * (-3.0 * tCubed + 3.0 * tSquared ) + + vP3 * tCubed) +} + +/// Cubic Bézier Curve in R³ +pub fn bezier3(p0: Point3, p1: Point3, p2: Point3, p3: Point3, t: f32) -> Point3 { + let tSquared = t * t; + let tCubed = tSquared * t; + let vP0 = Vec3::from_point(p0); + let vP1 = Vec3::from_point(p1); + let vP2 = Vec3::from_point(p2); + let vP3 = Vec3::from_point(p3); + + Point3::from_vec(vP0 * (-tCubed + 3.0 * tSquared - 3.0 * t + 1.0 ) + + vP1 * (3.0 * tCubed - 6.0 * tSquared + 3.0 * t ) + + vP2 * (-3.0 * tCubed + 3.0 * tSquared ) + + vP3 * tCubed) +} + +// ################################################## +// # SPLINES # +// ################################################## diff --git a/crates/comet_math/src/vector.rs b/crates/comet_math/src/vector.rs new file mode 100644 index 0000000..68073c3 --- /dev/null +++ b/crates/comet_math/src/vector.rs @@ -0,0 +1,2640 @@ +use crate::point::{Point2, Point3}; +use crate::quaternion::Quat; +use crate::utilities::acos; +use std::ops::{Add, Div, Mul, Sub}; + +pub trait InnerSpace { + fn dot(&self, other: &Self) -> f32; + fn dist(&self, other: &Self) -> f32; + fn vAngle(&self, other: &Self) -> f32; +} + +// ################################################## +// # VECTOR 2D # +// ################################################## + +/// Representation of a 2D Vector +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Vec2 { + x: f32, + y: f32, +} + +impl Vec2 { + pub const X: Vec2 = Vec2 { x: 1.0, y: 0.0 }; + pub const Y: Vec2 = Vec2 { x: 0.0, y: 1.0 }; + pub const ZERO: Vec2 = Vec2 { x: 0.0, y: 0.0 }; + + pub const fn new(x: f32, y: f32) -> Self { + Vec2 { x, y } + } + + pub fn from_point(p: Point2) -> Self { + Self { x: p.x(), y: p.y() } + } + + pub fn x(&self) -> f32 { + self.x + } + + pub fn y(&self) -> f32 { + self.y + } + + pub fn length(&self) -> f32 { + (self.x * self.x + self.y * self.y).sqrt() + } + + pub fn normalize(&self) -> Self { + let factor = 1.0 / self.length(); + Vec2 { + x: factor * self.x, + y: factor * self.y, + } + } + + pub fn xx(&self) -> Vec2 { + Vec2 { + x: self.x, + y: self.x, + } + } + + pub fn xy(&self) -> Vec2 { + Vec2 { + x: self.x, + y: self.y, + } + } + + pub fn yx(&self) -> Vec2 { + Vec2 { + x: self.y, + y: self.x, + } + } + + pub fn yy(&self) -> Vec2 { + Vec2 { + x: self.y, + y: self.y, + } + } +} + +impl Add for Vec2 { + type Output = Vec2; + + fn add(self, other: Vec2) -> Vec2 { + Vec2 { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +impl Sub for Vec2 { + type Output = Vec2; + + fn sub(self, other: Vec2) -> Vec2 { + Vec2 { + x: self.x - other.x, + y: self.y - other.y, + } + } +} + +impl Mul for Vec2 { + type Output = Vec2; + + fn mul(self, other: f32) -> Vec2 { + Vec2 { + x: self.x * other, + y: self.y * other, + } + } +} + +// ################################################## +// # VECTOR 3D # +// ################################################## + +/// Representation of a 3D Vector +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Vec3 { + pub x: f32, + pub y: f32, + pub z: f32, +} + +impl Vec3 { + pub const X: Vec3 = Vec3 { x: 1.0, y: 0.0, z: 0.0 }; + pub const Y: Vec3 = Vec3 { x: 0.0, y: 1.0, z: 0.0 }; + pub const Z: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 1.0 }; + pub const ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 }; + + pub const fn new(x: f32, y: f32, z: f32) -> Self { + Vec3 { x, y, z } + } + + pub fn from_point(p: Point3) -> Self { + Self { + x: p.x(), + y: p.y(), + z: p.z(), + } + } + + pub fn x(&self) -> f32 { + self.x + } + + pub fn y(&self) -> f32 { + self.y + } + + pub fn z(&self) -> f32 { + self.z + } + + pub fn into_quaternion(&self) -> Quat { + Quat { + s: 0.0, + v: Vec3 { + x: self.x, + y: self.y, + z: self.z, + } + } + } + + pub fn length(&self) -> f32 { + (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() + } + + pub fn normalize(&self) -> Self { + let factor = 1.0 / self.length(); + Vec3 { + x: factor * self.x, + y: factor * self.y, + z: factor * self.z, + } + } + + pub fn xxx(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.x, + z: self.x, + } + } + pub fn xxy(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.x, + z: self.y, + } + } + pub fn xxz(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.x, + z: self.z, + } + } + pub fn xyx(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.y, + z: self.x, + } + } + pub fn xyy(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.y, + z: self.y, + } + } + pub fn xyz(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.y, + z: self.z, + } + } + pub fn xzx(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.z, + z: self.x, + } + } + pub fn xzy(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.z, + z: self.y, + } + } + pub fn xzz(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.z, + z: self.z, + } + } + pub fn yxx(&self) -> Vec3 { + Vec3 { + x: self.y, + y: self.x, + z: self.x, + } + } + pub fn yxy(&self) -> Vec3 { + Vec3 { + x: self.y, + y: self.x, + z: self.y, + } + } + pub fn yxz(&self) -> Vec3 { + Vec3 { + x: self.y, + y: self.x, + z: self.z, + } + } + pub fn yyx(&self) -> Vec3 { + Vec3 { + x: self.y, + y: self.y, + z: self.x, + } + } + pub fn yyy(&self) -> Vec3 { + Vec3 { + x: self.y, + y: self.y, + z: self.y, + } + } + pub fn yyz(&self) -> Vec3 { + Vec3 { + x: self.y, + y: self.y, + z: self.z, + } + } + pub fn yzx(&self) -> Vec3 { + Vec3 { + x: self.y, + y: self.z, + z: self.x, + } + } + pub fn yzy(&self) -> Vec3 { + Vec3 { + x: self.y, + y: self.z, + z: self.y, + } + } + pub fn yzz(&self) -> Vec3 { + Vec3 { + x: self.y, + y: self.z, + z: self.z, + } + } + pub fn zxx(&self) -> Vec3 { + Vec3 { + x: self.z, + y: self.x, + z: self.x, + } + } + pub fn zxy(&self) -> Vec3 { + Vec3 { + x: self.z, + y: self.x, + z: self.y, + } + } + pub fn zxz(&self) -> Vec3 { + Vec3 { + x: self.z, + y: self.x, + z: self.z, + } + } + pub fn zyx(&self) -> Vec3 { + Vec3 { + x: self.z, + y: self.y, + z: self.x, + } + } + pub fn zyy(&self) -> Vec3 { + Vec3 { + x: self.z, + y: self.y, + z: self.y, + } + } + pub fn zyz(&self) -> Vec3 { + Vec3 { + x: self.z, + y: self.y, + z: self.z, + } + } + pub fn zzx(&self) -> Vec3 { + Vec3 { + x: self.z, + y: self.z, + z: self.x, + } + } + pub fn zzy(&self) -> Vec3 { + Vec3 { + x: self.z, + y: self.z, + z: self.y, + } + } + pub fn zzz(&self) -> Vec3 { + Vec3 { + x: self.z, + y: self.z, + z: self.z, + } + } +} + +impl Add for Vec3 { + type Output = Vec3; + + fn add(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x + other.x, + y: self.y + other.y, + z: self.z + other.z, + } + } +} + +impl Sub for Vec3 { + type Output = Vec3; + + fn sub(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x - other.x, + y: self.y - other.y, + z: self.z - other.z, + } + } +} + +impl Mul for Vec3 { + type Output = Vec3; + + fn mul(self, other: f32) -> Vec3 { + Vec3 { + x: self.x * other, + y: self.y * other, + z: self.z * other, + } + } +} + +// ################################################## +// # VECTOR 4D # +// ################################################## + +/// Representation of a 4D Vector +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Vec4 { + x: f32, + y: f32, + z: f32, + w: f32, +} + +impl Vec4 { + pub const X: Vec4 = Vec4 { x: 1.0, y: 0.0, z: 0.0, w: 0.0 }; + pub const Y: Vec4 = Vec4 { x: 0.0, y: 1.0, z: 0.0, w: 0.0 }; + pub const Z: Vec4 = Vec4 { x: 0.0, y: 0.0, z: 1.0, w: 0.0 }; + pub const W: Vec4 = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }; + + pub const ZERO: Vec4 = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 }; + + pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + Vec4 { x, y, z, w } + } + + pub fn length(&self) -> f32 { + (self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w).sqrt() + } + + pub fn normalize(&self) -> Self { + let factor = 1.0 / self.length(); + Vec4 { + x: factor * self.x, + y: factor * self.y, + z: factor * self.z, + w: factor * self.w, + } + } + + pub fn x(&self) -> f32 { + self.x + } + + pub fn y(&self) -> f32 { + self.y + } + + pub fn z(&self) -> f32 { + self.z + } + + pub fn w(&self) -> f32 { + self.w + } + + pub fn xxxx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.x, + w: self.x, + } + } + pub fn xxxy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.x, + w: self.y, + } + } + pub fn xxxz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.x, + w: self.z, + } + } + pub fn xxxw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.x, + w: self.w, + } + } + pub fn xxyx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.y, + w: self.x, + } + } + pub fn xxyy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.y, + w: self.y, + } + } + pub fn xxyz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.y, + w: self.z, + } + } + pub fn xxyw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.y, + w: self.w, + } + } + pub fn xxzx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.z, + w: self.x, + } + } + pub fn xxzy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.z, + w: self.y, + } + } + pub fn xxzz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.z, + w: self.z, + } + } + pub fn xxzw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.z, + w: self.w, + } + } + pub fn xxwx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.w, + w: self.x, + } + } + pub fn xxwy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.w, + w: self.y, + } + } + pub fn xxwz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.w, + w: self.z, + } + } + pub fn xxww(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.x, + z: self.w, + w: self.w, + } + } + pub fn xyxx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.x, + w: self.x, + } + } + pub fn xyxy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.x, + w: self.y, + } + } + pub fn xyxz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.x, + w: self.z, + } + } + pub fn xyxw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.x, + w: self.w, + } + } + pub fn xyyx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.y, + w: self.x, + } + } + pub fn xyyy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.y, + w: self.y, + } + } + pub fn xyyz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.y, + w: self.z, + } + } + pub fn xyyw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.y, + w: self.w, + } + } + pub fn xyzx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.z, + w: self.x, + } + } + pub fn xyzy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.z, + w: self.y, + } + } + pub fn xyzz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.z, + w: self.z, + } + } + pub fn xyzw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.z, + w: self.w, + } + } + pub fn xywx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.w, + w: self.x, + } + } + pub fn xywy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.w, + w: self.y, + } + } + pub fn xywz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.w, + w: self.z, + } + } + pub fn xyww(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.y, + z: self.w, + w: self.w, + } + } + pub fn xzxx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.x, + w: self.x, + } + } + pub fn xzxy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.x, + w: self.y, + } + } + pub fn xzxz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.x, + w: self.z, + } + } + pub fn xzxw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.x, + w: self.w, + } + } + pub fn xzyx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.y, + w: self.x, + } + } + pub fn xzyy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.y, + w: self.y, + } + } + pub fn xzyz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.y, + w: self.z, + } + } + pub fn xzyw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.y, + w: self.w, + } + } + pub fn xzzx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.z, + w: self.x, + } + } + pub fn xzzy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.z, + w: self.y, + } + } + pub fn xzzz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.z, + w: self.z, + } + } + pub fn xzzw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.z, + w: self.w, + } + } + pub fn xzwx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.w, + w: self.x, + } + } + pub fn xzwy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.w, + w: self.y, + } + } + pub fn xzwz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.w, + w: self.z, + } + } + pub fn xzww(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.z, + z: self.w, + w: self.w, + } + } + pub fn xwxx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.x, + w: self.x, + } + } + pub fn xwxy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.x, + w: self.y, + } + } + pub fn xwxz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.x, + w: self.z, + } + } + pub fn xwxw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.x, + w: self.w, + } + } + pub fn xwyx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.y, + w: self.x, + } + } + pub fn xwyy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.y, + w: self.y, + } + } + pub fn xwyz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.y, + w: self.z, + } + } + pub fn xwyw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.y, + w: self.w, + } + } + pub fn xwzx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.z, + w: self.x, + } + } + pub fn xwzy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.z, + w: self.y, + } + } + pub fn xwzz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.z, + w: self.z, + } + } + pub fn xwzw(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.z, + w: self.w, + } + } + pub fn xwwx(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.w, + w: self.x, + } + } + pub fn xwwy(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.w, + w: self.y, + } + } + pub fn xwwz(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.w, + w: self.z, + } + } + pub fn xwww(&self) -> Vec4 { + Vec4 { + x: self.x, + y: self.w, + z: self.w, + w: self.w, + } + } + pub fn yxxx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.x, + w: self.x, + } + } + pub fn yxxy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.x, + w: self.y, + } + } + pub fn yxxz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.x, + w: self.z, + } + } + pub fn yxxw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.x, + w: self.w, + } + } + pub fn yxyx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.y, + w: self.x, + } + } + pub fn yxyy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.y, + w: self.y, + } + } + pub fn yxyz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.y, + w: self.z, + } + } + pub fn yxyw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.y, + w: self.w, + } + } + pub fn yxzx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.z, + w: self.x, + } + } + pub fn yxzy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.z, + w: self.y, + } + } + pub fn yxzz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.z, + w: self.z, + } + } + pub fn yxzw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.z, + w: self.w, + } + } + pub fn yxwx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.w, + w: self.x, + } + } + pub fn yxwy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.w, + w: self.y, + } + } + pub fn yxwz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.w, + w: self.z, + } + } + pub fn yxww(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.x, + z: self.w, + w: self.w, + } + } + pub fn yyxx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.x, + w: self.x, + } + } + pub fn yyxy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.x, + w: self.y, + } + } + pub fn yyxz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.x, + w: self.z, + } + } + pub fn yyxw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.x, + w: self.w, + } + } + pub fn yyyx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.y, + w: self.x, + } + } + pub fn yyyy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.y, + w: self.y, + } + } + pub fn yyyz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.y, + w: self.z, + } + } + pub fn yyyw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.y, + w: self.w, + } + } + pub fn yyzx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.z, + w: self.x, + } + } + pub fn yyzy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.z, + w: self.y, + } + } + pub fn yyzz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.z, + w: self.z, + } + } + pub fn yyzw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.z, + w: self.w, + } + } + pub fn yywx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.w, + w: self.x, + } + } + pub fn yywy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.w, + w: self.y, + } + } + pub fn yywz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.w, + w: self.z, + } + } + pub fn yyww(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.y, + z: self.w, + w: self.w, + } + } + pub fn yzxx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.x, + w: self.x, + } + } + pub fn yzxy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.x, + w: self.y, + } + } + pub fn yzxz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.x, + w: self.z, + } + } + pub fn yzxw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.x, + w: self.w, + } + } + pub fn yzyx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.y, + w: self.x, + } + } + pub fn yzyy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.y, + w: self.y, + } + } + pub fn yzyz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.y, + w: self.z, + } + } + pub fn yzyw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.y, + w: self.w, + } + } + pub fn yzzx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.z, + w: self.x, + } + } + pub fn yzzy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.z, + w: self.y, + } + } + pub fn yzzz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.z, + w: self.z, + } + } + pub fn yzzw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.z, + w: self.w, + } + } + pub fn yzwx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.w, + w: self.x, + } + } + pub fn yzwy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.w, + w: self.y, + } + } + pub fn yzwz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.w, + w: self.z, + } + } + pub fn yzww(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.z, + z: self.w, + w: self.w, + } + } + pub fn ywxx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.x, + w: self.x, + } + } + pub fn ywxy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.x, + w: self.y, + } + } + pub fn ywxz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.x, + w: self.z, + } + } + pub fn ywxw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.x, + w: self.w, + } + } + pub fn ywyx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.y, + w: self.x, + } + } + pub fn ywyy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.y, + w: self.y, + } + } + pub fn ywyz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.y, + w: self.z, + } + } + pub fn ywyw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.y, + w: self.w, + } + } + pub fn ywzx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.z, + w: self.x, + } + } + pub fn ywzy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.z, + w: self.y, + } + } + pub fn ywzz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.z, + w: self.z, + } + } + pub fn ywzw(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.z, + w: self.w, + } + } + pub fn ywwx(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.w, + w: self.x, + } + } + pub fn ywwy(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.w, + w: self.y, + } + } + pub fn ywwz(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.w, + w: self.z, + } + } + pub fn ywww(&self) -> Vec4 { + Vec4 { + x: self.y, + y: self.w, + z: self.w, + w: self.w, + } + } + pub fn zxxx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.x, + w: self.x, + } + } + pub fn zxxy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.x, + w: self.y, + } + } + pub fn zxxz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.x, + w: self.z, + } + } + pub fn zxxw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.x, + w: self.w, + } + } + pub fn zxyx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.y, + w: self.x, + } + } + pub fn zxyy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.y, + w: self.y, + } + } + pub fn zxyz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.y, + w: self.z, + } + } + pub fn zxyw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.y, + w: self.w, + } + } + pub fn zxzx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.z, + w: self.x, + } + } + pub fn zxzy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.z, + w: self.y, + } + } + pub fn zxzz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.z, + w: self.z, + } + } + pub fn zxzw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.z, + w: self.w, + } + } + pub fn zxwx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.w, + w: self.x, + } + } + pub fn zxwy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.w, + w: self.y, + } + } + pub fn zxwz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.w, + w: self.z, + } + } + pub fn zxww(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.x, + z: self.w, + w: self.w, + } + } + pub fn zyxx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.x, + w: self.x, + } + } + pub fn zyxy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.x, + w: self.y, + } + } + pub fn zyxz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.x, + w: self.z, + } + } + pub fn zyxw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.x, + w: self.w, + } + } + pub fn zyyx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.y, + w: self.x, + } + } + pub fn zyyy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.y, + w: self.y, + } + } + pub fn zyyz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.y, + w: self.z, + } + } + pub fn zyyw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.y, + w: self.w, + } + } + pub fn zyzx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.z, + w: self.x, + } + } + pub fn zyzy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.z, + w: self.y, + } + } + pub fn zyzz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.z, + w: self.z, + } + } + pub fn zyzw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.z, + w: self.w, + } + } + pub fn zywx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.w, + w: self.x, + } + } + pub fn zywy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.w, + w: self.y, + } + } + pub fn zywz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.w, + w: self.z, + } + } + pub fn zyww(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.y, + z: self.w, + w: self.w, + } + } + pub fn zzxx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.x, + w: self.x, + } + } + pub fn zzxy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.x, + w: self.y, + } + } + pub fn zzxz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.x, + w: self.z, + } + } + pub fn zzxw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.x, + w: self.w, + } + } + pub fn zzyx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.y, + w: self.x, + } + } + pub fn zzyy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.y, + w: self.y, + } + } + pub fn zzyz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.y, + w: self.z, + } + } + pub fn zzyw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.y, + w: self.w, + } + } + pub fn zzzx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.z, + w: self.x, + } + } + pub fn zzzy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.z, + w: self.y, + } + } + pub fn zzzz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.z, + w: self.z, + } + } + pub fn zzzw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.z, + w: self.w, + } + } + pub fn zzwx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.w, + w: self.x, + } + } + pub fn zzwy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.w, + w: self.y, + } + } + pub fn zzwz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.w, + w: self.z, + } + } + pub fn zzww(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.z, + z: self.w, + w: self.w, + } + } + pub fn zwxx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.x, + w: self.x, + } + } + pub fn zwxy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.x, + w: self.y, + } + } + pub fn zwxz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.x, + w: self.z, + } + } + pub fn zwxw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.x, + w: self.w, + } + } + pub fn zwyx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.y, + w: self.x, + } + } + pub fn zwyy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.y, + w: self.y, + } + } + pub fn zwyz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.y, + w: self.z, + } + } + pub fn zwyw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.y, + w: self.w, + } + } + pub fn zwzx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.z, + w: self.x, + } + } + pub fn zwzy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.z, + w: self.y, + } + } + pub fn zwzz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.z, + w: self.z, + } + } + pub fn zwzw(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.z, + w: self.w, + } + } + pub fn zwwx(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.w, + w: self.x, + } + } + pub fn zwwy(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.w, + w: self.y, + } + } + pub fn zwwz(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.w, + w: self.z, + } + } + pub fn zwww(&self) -> Vec4 { + Vec4 { + x: self.z, + y: self.w, + z: self.w, + w: self.w, + } + } + pub fn wxxx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.x, + w: self.x, + } + } + pub fn wxxy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.x, + w: self.y, + } + } + pub fn wxxz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.x, + w: self.z, + } + } + pub fn wxxw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.x, + w: self.w, + } + } + pub fn wxyx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.y, + w: self.x, + } + } + pub fn wxyy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.y, + w: self.y, + } + } + pub fn wxyz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.y, + w: self.z, + } + } + pub fn wxyw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.y, + w: self.w, + } + } + pub fn wxzx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.z, + w: self.x, + } + } + pub fn wxzy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.z, + w: self.y, + } + } + pub fn wxzz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.z, + w: self.z, + } + } + pub fn wxzw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.z, + w: self.w, + } + } + pub fn wxwx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.w, + w: self.x, + } + } + pub fn wxwy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.w, + w: self.y, + } + } + pub fn wxwz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.w, + w: self.z, + } + } + pub fn wxww(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.x, + z: self.w, + w: self.w, + } + } + pub fn wyxx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.x, + w: self.x, + } + } + pub fn wyxy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.x, + w: self.y, + } + } + pub fn wyxz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.x, + w: self.z, + } + } + pub fn wyxw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.x, + w: self.w, + } + } + pub fn wyyx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.y, + w: self.x, + } + } + pub fn wyyy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.y, + w: self.y, + } + } + pub fn wyyz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.y, + w: self.z, + } + } + pub fn wyyw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.y, + w: self.w, + } + } + pub fn wyzx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.z, + w: self.x, + } + } + pub fn wyzy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.z, + w: self.y, + } + } + pub fn wyzz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.z, + w: self.z, + } + } + pub fn wyzw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.z, + w: self.w, + } + } + pub fn wywx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.w, + w: self.x, + } + } + pub fn wywy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.w, + w: self.y, + } + } + pub fn wywz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.w, + w: self.z, + } + } + pub fn wyww(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.y, + z: self.w, + w: self.w, + } + } + pub fn wzxx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.x, + w: self.x, + } + } + pub fn wzxy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.x, + w: self.y, + } + } + pub fn wzxz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.x, + w: self.z, + } + } + pub fn wzxw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.x, + w: self.w, + } + } + pub fn wzyx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.y, + w: self.x, + } + } + pub fn wzyy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.y, + w: self.y, + } + } + pub fn wzyz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.y, + w: self.z, + } + } + pub fn wzyw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.y, + w: self.w, + } + } + pub fn wzzx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.z, + w: self.x, + } + } + pub fn wzzy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.z, + w: self.y, + } + } + pub fn wzzz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.z, + w: self.z, + } + } + pub fn wzzw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.z, + w: self.w, + } + } + pub fn wzwx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.w, + w: self.x, + } + } + pub fn wzwy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.w, + w: self.y, + } + } + pub fn wzwz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.w, + w: self.z, + } + } + pub fn wzww(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.z, + z: self.w, + w: self.w, + } + } + pub fn wwxx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.x, + w: self.x, + } + } + pub fn wwxy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.x, + w: self.y, + } + } + pub fn wwxz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.x, + w: self.z, + } + } + pub fn wwxw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.x, + w: self.w, + } + } + pub fn wwyx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.y, + w: self.x, + } + } + pub fn wwyy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.y, + w: self.y, + } + } + pub fn wwyz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.y, + w: self.z, + } + } + pub fn wwyw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.y, + w: self.w, + } + } + pub fn wwzx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.z, + w: self.x, + } + } + pub fn wwzy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.z, + w: self.y, + } + } + pub fn wwzz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.z, + w: self.z, + } + } + pub fn wwzw(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.z, + w: self.w, + } + } + pub fn wwwx(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.w, + w: self.x, + } + } + pub fn wwwy(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.w, + w: self.y, + } + } + pub fn wwwz(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.w, + w: self.z, + } + } + pub fn wwww(&self) -> Vec4 { + Vec4 { + x: self.w, + y: self.w, + z: self.w, + w: self.w, + } + } +} + +impl Add for Vec4 { + type Output = Vec4; + + fn add(self, other: Vec4) -> Vec4 { + Vec4 { + x: self.x + other.x, + y: self.y + other.y, + z: self.z + other.z, + w: self.w + other.w, + } + } +} + +impl Sub for Vec4 { + type Output = Vec4; + + fn sub(self, other: Vec4) -> Vec4 { + Vec4 { + x: self.x - other.x, + y: self.y - other.y, + z: self.z - other.z, + w: self.w - other.w, + } + } +} + +impl Mul for Vec4 { + type Output = Vec4; + + fn mul(self, other: f32) -> Vec4 { + Vec4 { + x: self.x * other, + y: self.y * other, + z: self.z * other, + w: self.w * other, + } + } +} + +impl InnerSpace for Vec2 { + fn dot(&self, other: &Self) -> f32 { + self.x * other.x + self.y * other.y + } + + fn dist(&self, other: &Self) -> f32 { + Vec2 { + x: other.x - self.x, + y: other.y - self.y, + } + .length() + } + + fn vAngle(&self, other: &Self) -> f32 { + acos(dot(self, other) / (self.length() * other.length())) + } +} + +impl InnerSpace for Vec3 { + fn dot(&self, other: &Self) -> f32 { + self.x * other.x + self.y * other.y + self.z * other.z + } + + fn dist(&self, other: &Self) -> f32 { + Vec3 { + x: other.x - self.x, + y: other.y - self.y, + z: other.z - self.z, + } + .length() + } + + fn vAngle(&self, other: &Self) -> f32 { + acos(dot(self, other) / (self.length() * other.length())) + } +} + +impl InnerSpace for Vec4 { + fn dot(&self, other: &Self) -> f32 { + self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w + } + + fn dist(&self, other: &Self) -> f32 { + Vec4 { + x: other.x - self.x, + y: other.y - self.y, + z: other.z - self.z, + w: other.w - self.w, + } + .length() + } + + fn vAngle(&self, other: &Self) -> f32 { + acos(dot(self, other) / (self.length() * other.length())) + } +} + +// ################################################## +// # VECTOR FUNCTIONS # +// ################################################## + +pub fn dot(v1: &T, v2: &T) -> f32 { + v1.dot(v2) +} + +pub fn cross(v1: Vec3, v2: Vec3) -> Vec3 { + Vec3 { + x: v1.y * v2.z - v1.z * v2.y, + y: v1.z * v2.x - v1.x * v2.z, + z: v1.x * v2.y - v2.y * v1.x, + } +} + +pub fn v_dist(v1: &T, v2: &T) -> f32 { + v1.dist(v2) +} + +pub fn v_angle(v1: &T, v2: &T) -> f32 { + v1.vAngle(v2) +} diff --git a/crates/comet_renderer/Cargo.toml b/crates/comet_renderer/Cargo.toml new file mode 100644 index 0000000..8d7c855 --- /dev/null +++ b/crates/comet_renderer/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "comet_renderer" +version = "0.1.0" +edition = "2021" + +[dependencies] +comet_math = { path = "../comet_math" } +comet_resources = { path = "../comet_resources" } +comet_colors = { path = "../comet_colors" } +comet_log = { path = "../comet_log" } + +cfg-if = "1" +anyhow = "1.0" +bytemuck = { version = "1.16", features = [ "derive" ] } +cgmath = "0.18" +env_logger = "0.10" +pollster = "0.3" +log = "0.4" +tobj = { version = "3.2", default-features = false, features = ["async"]} +wgpu = { version = "22.0"} +winit = { version = "0.29", features = ["rwh_05"] } +instant = "0.1" + +[dependencies.image] +version = "0.24" +default-features = false +features = ["png", "jpeg", "hdr"] \ No newline at end of file diff --git a/crates/comet_renderer/src/camera.rs b/crates/comet_renderer/src/camera.rs new file mode 100644 index 0000000..c5109a9 --- /dev/null +++ b/crates/comet_renderer/src/camera.rs @@ -0,0 +1,321 @@ +use comet_math::Point3; + +#[rustfmt::skip] +pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0, +); + +const SAFE_FRAC_PI_2: f32 = std::f32::consts::FRAC_PI_2 - 0.0001; + +pub struct Camera { + eye: cgmath::Point3, + target: cgmath::Point3, + up: cgmath::Vector3, + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32, +} + +impl Camera { + pub fn new( + eye: cgmath::Point3, + target: cgmath::Point3, + up: cgmath::Vector3, + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32, + ) -> Self { + Self { + eye, + target, + up, + aspect, + fovy, + znear, + zfar, + } + } + + pub fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { + // 1. + let view = cgmath::Matrix4::look_at_rh(self.eye, self.target, self.up); + // 2. + let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); + + // 3. + return OPENGL_TO_WGPU_MATRIX * proj * view; + } +} + + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct CameraUniform { + view_proj: [[f32; 4]; 4], +} + +impl CameraUniform { + pub fn new() -> Self { + use cgmath::SquareMatrix; + Self { + view_proj: cgmath::Matrix4::identity().into(), + } + } + + pub fn update_view_proj(&mut self, camera: &Camera) { + self.view_proj = camera.build_view_projection_matrix().into(); + } +} +/*use comet_math::{Mat4, Point3, Vec3}; + +#[rustfmt::skip] +pub const OPENGL_TO_WGPU_MATRIX: Mat4 = Mat4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.0, 0.0, 0.5, 1.0, +); + +pub struct Camera { + eye: Point3, + target: Point3, + up: Vec3, + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32, +} + +impl Camera { + pub fn new(eye: Point3, target: Point3, up: Vec3, aspect: f32, fovy: f32, znear: f32, zfar: f32) -> Self { + Self { + eye, + target, + up, + aspect, + fovy, + znear, + zfar, + } + } + + pub fn build_view_projection_matrix(&self) -> Mat4 { + let view = Mat4::look_at_rh(self.eye, self.target, self.up); + let proj = Mat4::perspective_matrix(self.fovy, self.aspect, self.znear, self.zfar); + + (OPENGL_TO_WGPU_MATRIX * proj * view).transpose() + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct CameraUniform { + view_proj: [[f32; 4]; 4], +} + +impl CameraUniform { + pub fn new() -> Self { + Self { + view_proj: Mat4::IDENTITY.into(), + } + } + + pub fn update_view_proj(&mut self, camera: &Camera) { + self.view_proj = camera.build_view_projection_matrix().into(); + } +}*/ + +/*use std::f32::consts::FRAC_PI_2; +use std::time::Duration; +use winit::dpi::PhysicalPosition; +use winit::event::*; +use winit::keyboard::KeyCode; + +const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001; + +#[derive(Debug)] +pub struct Camera3D { + pub position: Point3, + yaw: f32, + pitch: f32, +} + +impl Camera3D { + pub fn new( + position: Point3, + yaw: f32, + pitch: f32, + ) -> Self { + Self { + position: position.into(), + yaw: yaw.into(), + pitch: pitch.into(), + } + } + + pub fn calc_matrix(&self) -> Mat4 { + let (sin_pitch, cos_pitch) = self.pitch.0.sin_cos(); + let (sin_yaw, cos_yaw) = self.yaw.0.sin_cos(); + + Mat4::look_to_rh( + self.position, + Vec3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(), + Vec3::unit_y(), + ) + } +} + +pub struct Projection { + aspect: f32, + fovy: Rad, + znear: f32, + zfar: f32, +} + +impl Projection { + pub fn new>>(width: u32, height: u32, fovy: F, znear: f32, zfar: f32) -> Self { + Self { + aspect: width as f32 / height as f32, + fovy: fovy.into(), + znear, + zfar, + } + } + + pub fn resize(&mut self, width: u32, height: u32) { + self.aspect = width as f32 / height as f32; + } + + pub fn calc_matrix(&self) -> Matrix4 { + // UDPATE + perspective(self.fovy, self.aspect, self.znear, self.zfar) + } +} + +#[derive(Debug)] +pub struct CameraController { + amount_left: f32, + amount_right: f32, + amount_forward: f32, + amount_backward: f32, + amount_up: f32, + amount_down: f32, + rotate_horizontal: f32, + rotate_vertical: f32, + scroll: f32, + speed: f32, + sensitivity: f32, +} + +impl CameraController { + pub fn new(speed: f32, sensitivity: f32) -> Self { + Self { + amount_left: 0.0, + amount_right: 0.0, + amount_forward: 0.0, + amount_backward: 0.0, + amount_up: 0.0, + amount_down: 0.0, + rotate_horizontal: 0.0, + rotate_vertical: 0.0, + scroll: 0.0, + speed, + sensitivity, + } + } + + pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool { + let amount = if state == ElementState::Pressed { + 1.0 + } else { + 0.0 + }; + match key { + KeyCode::KeyW | KeyCode::ArrowUp => { + self.amount_forward = amount; + true + } + KeyCode::KeyS | KeyCode::ArrowDown => { + self.amount_backward = amount; + true + } + KeyCode::KeyA | KeyCode::ArrowLeft => { + self.amount_left = amount; + true + } + KeyCode::KeyD | KeyCode::ArrowRight => { + self.amount_right = amount; + true + } + KeyCode::Space => { + self.amount_up = amount; + true + } + KeyCode::ShiftLeft => { + self.amount_down = amount; + true + } + _ => false, + } + } + + pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) { + self.rotate_horizontal = mouse_dx as f32; + self.rotate_vertical = mouse_dy as f32; + } + + pub fn process_scroll(&mut self, delta: &MouseScrollDelta) { + self.scroll = match delta { + // I'm assuming a line is about 100 pixels + MouseScrollDelta::LineDelta(_, scroll) => -scroll * 0.5, + MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => -*scroll as f32, + }; + } + + pub fn update_camera(&mut self, camera: &mut Camera, dt: Duration) { + let dt = dt.as_secs_f32(); + + // Move forward/backward and left/right + let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos(); + let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize(); + let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize(); + camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt; + camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt; + + // Move in/out (aka. "zoom") + // Note: this isn't an actual zoom. The camera's position + // changes when zooming. I've added this to make it easier + // to get closer to an object you want to focus on. + let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos(); + let scrollward = + Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize(); + camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt; + self.scroll = 0.0; + + // Move up/down. Since we don't use roll, we can just + // modify the y coordinate directly. + camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt; + + // Rotate + camera.yaw += Rad(self.rotate_horizontal) * self.sensitivity * dt; + camera.pitch += Rad(-self.rotate_vertical) * self.sensitivity * dt; + + // If process_mouse isn't called every frame, these values + // will not get set to zero, and the camera will rotate + // when moving in a non cardinal direction. + self.rotate_horizontal = 0.0; + self.rotate_vertical = 0.0; + + // Keep the camera's angle from going too high/low. + if camera.pitch < -Rad(SAFE_FRAC_PI_2) { + camera.pitch = -Rad(SAFE_FRAC_PI_2); + } else if camera.pitch > Rad(SAFE_FRAC_PI_2) { + camera.pitch = Rad(SAFE_FRAC_PI_2); + } + } +}*/ \ No newline at end of file diff --git a/crates/comet_renderer/src/lib.rs b/crates/comet_renderer/src/lib.rs new file mode 100644 index 0000000..0696fe5 --- /dev/null +++ b/crates/comet_renderer/src/lib.rs @@ -0,0 +1,671 @@ +mod camera; + +use core::default::Default; +use std::iter; +use std::sync::Arc; +use std::time::Instant; +use cgmath::num_traits::FloatConst; +use image::GenericImageView; +use log::info; +use wgpu::Color; +use wgpu::util::DeviceExt; +use winit::{ + dpi::PhysicalSize, + window::Window +}; +use comet_colors::LinearRgba; +use comet_math; +use comet_math::{Mat4, Point3, Vec3}; +use comet_resources::{ResourceManager, texture, Vertex, Texture}; +use crate::camera::{Camera, CameraUniform}; +// RAINBOW TRIANGLE +/*const VERTICES: &[Vertex] = &[ + Vertex :: new ( [0.0, 0.5, 0.0], [1.0, 0.0, 0.0] ), + Vertex :: new ( [-0.5, -0.5, 0.0], [0.0, 1.0, 0.0] ), + Vertex :: new ( [0.5, -0.5, 0.0], [0.0, 0.0, 1.0] ), +];*/ + +// RAW PENTAGON +/*const VERTICES: &[Vertex] = &[ + Vertex :: new ( [-0.0868241, 0.49240386, 0.0], [0.5, 0.0, 0.5] ), // A + Vertex :: new ( [-0.49513406, 0.06958647, 0.0], [0.5, 0.0, 0.5] ), // B + Vertex :: new ( [-0.21918549, -0.44939706, 0.0], [0.5, 0.0, 0.5] ), // C + Vertex :: new ( [0.35966998, -0.3473291, 0.0], [0.5, 0.0, 0.5] ), // D + Vertex :: new ( [0.44147372, 0.2347359, 0.0], [0.5, 0.0, 0.5] ), // E +];*/ + +// RAINBOW PENTAGON +/*const VERTICES: &[Vertex] = &[ + Vertex :: new ( [-0.0868241, 0.49240386, 0.0], [1.0, 0.0, 0.0] ), // A + Vertex :: new ( [-0.49513406, 0.06958647, 0.0], [0.0, 0.35, 1.0] ), // B + Vertex :: new ( [-0.21918549, -0.44939706, 0.0], [0.2, 1.0, 0.2] ), // C + Vertex :: new ( [0.35966998, -0.3473291, 0.0], [1.0, 0.85, 0.2] ), // D + Vertex :: new ( [0.44147372, 0.2347359, 0.0], [1.0, 0.2, 0.6] ), // E +]; + +const INDICES: &[u16] = &[ + 0, 1, 4, + 1, 2, 4, + 2, 3, 4, +];*/ + +// RAW QUAD +/*const VERTICES: &[Vertex] = &[ + Vertex :: new ( [-0.5, 0.5, 0.0], [0.0, 0.0, 1.0] ), + Vertex :: new ( [-0.5, -0.5, 0.0], [0.0, 1.0, 0.0] ), + Vertex :: new ( [0.5, -0.5, 0.0], [1.0, 0.0, 0.0] ), + Vertex :: new ( [0.5, 0.5, 0.0], [1.0, 0.0, 1.0] ) +]; + +const INDICES: &[u16] = &[ + 0, 1, 3, + 1, 2, 3 +];*/ + + +/* +vec![ + Vertex :: new ( [-0.1, 0.1, 0.0], [0.0, 0.0] ), + Vertex :: new ( [-0.1, -0.1, 0.0], [0.0, 1.0] ), + Vertex :: new ( [0.1, -0.1, 0.0], [1.0, 1.0] ), + Vertex :: new ( [0.1, 0.1, 0.0], [1.0, 0.0] ), + ], vec![ + 0, 1, 3, + 1, 2, 3 + ] +*/ + +pub struct Projection { + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32 +} + +impl Projection { + pub fn new(width: u32, height: u32, fovy: f32, znear: f32, zfar: f32) -> Self { + Self { + aspect: width as f32 / height as f32, + fovy, + znear, + zfar + } + } + + pub fn resize(&mut self, width: u32, height: u32) { self.aspect = width as f32 / height as f32; } + + pub fn calc_matrix(&self) -> Mat4 { + Mat4::perspective_matrix(self.fovy, self.aspect, self.znear, self.zfar) + } +} + +pub struct Renderer<'a> { + window: &'a Window, + surface: wgpu::Surface<'a>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + //projection: Projection, + render_pipeline: wgpu::RenderPipeline, + last_frame_time: Instant, + deltatime: f32, + vertex_buffer: wgpu::Buffer, + vertex_data: Vec, + index_buffer: wgpu::Buffer, + index_data: Vec, + num_indices: u32, + clear_color: Color, + diffuse_texture: texture::Texture, + diffuse_bind_group: wgpu::BindGroup, + resource_manager: ResourceManager, + /*camera: Camera, + camera_uniform: CameraUniform, + camera_buffer: wgpu::Buffer, + camera_bind_group: wgpu::BindGroup,*/ +} + +impl<'a> Renderer<'a> { + pub async fn new(window: &'a Window, clear_color: Option) -> anyhow::Result> { + let vertex_data: Vec = vec![]; + let index_data: Vec = vec![]; + + let size = PhysicalSize::::new(1920, 1080); //window.inner_size(); + + // The instance is a handle to our GPU + // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + + let surface = instance.create_surface(window).unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + // WebGL doesn't support all of wgpu's features, so if + // we're building for the web we'll have to disable some. + required_limits: wgpu::Limits::default(), + memory_hints: Default::default(), + }, + None, // Trace path + ) + .await + .unwrap(); + + let surface_caps = surface.get_capabilities(&adapter); + // Shader code in this tutorial assumes an Srgb surface texture. Using a different + // one will result all the colors comming out darker. If you want to support non + // Srgb surfaces, you'll need to account for that when drawing to the frame. + let surface_format = surface_caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertex_data), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&index_data), + usage: wgpu::BufferUsages::INDEX + }); + + let num_indices = index_data.len() as u32; + + let resource_manager = ResourceManager::new(); + + let diffuse_bytes = include_bytes!(r"../../../resources/textures/comet_icon.png"); + let diffuse_texture = + texture::Texture::from_bytes(&device, &queue, diffuse_bytes, "comet_icon.png", false).unwrap(); + + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + // This should match the filterable field of the + // corresponding Texture entry above. + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + ], + label: Some("diffuse_bind_group"), + }); + + /*let camera = Camera::new( + // position the camera 1 unit up and 2 units back + // +z is out of the screen + (0.0, 1.0, 2.0).into(), + // have it look at the origin + (0.0, 0.0, 0.0).into(), + // which way is "up" + cgmath::Vector3::unit_y(), + config.width as f32 / config.height as f32, + 45.0, + 0.1, + 100.0, + ); + + let mut camera_uniform = CameraUniform::new(); + camera_uniform.update_view_proj(&camera); + + let camera_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Camera Buffer"), + contents: bytemuck::cast_slice(&[camera_uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + } + ); + + let camera_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + } + ], + label: Some("camera_bind_group_layout"), + }); + + let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &camera_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: camera_buffer.as_entire_binding(), + } + ], + label: Some("camera_bind_group"), + });*/ + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[ + &texture_bind_group_layout, + //&camera_bind_group_layout, + ], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE + // or Features::POLYGON_MODE_POINT + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + // If the pipeline will be used with a multiview render pass, this + // indicates how many array layers the attachments will have. + multiview: None, + // Useful for optimizing shader compilation on Android + cache: None, + }); + + let clear_color = match clear_color { + Some(color) => color.to_wgpu(), + None => wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + } + }; + + Ok(Self { + window, + surface, + device, + queue, + config, + size, + //projection, + render_pipeline, + last_frame_time: Instant::now(), + deltatime: 0.0, + vertex_buffer, + vertex_data, + index_buffer, + index_data, + num_indices, + clear_color, + diffuse_texture, + diffuse_bind_group, + resource_manager, + /*camera, + camera_uniform, + camera_buffer, + camera_bind_group,*/ + }) + } + + pub fn dt(&self) -> f32 { + self.deltatime + } + + fn vertex_data_mut(&mut self) -> &mut Vec { + &mut self.vertex_data + } + + fn index_data_mut(&mut self) -> &mut Vec { + &mut self.index_data + } + + fn create_rectangle(&self, width: f32, height: f32) -> Vec { + let (bound_x, bound_y) = + ((width/ self.config.width as f32) * 0.5, (height/ self.config.height as f32) * 0.5); + + vec![ + Vertex :: new ( [-bound_x, bound_y, 0.0], [0.0, 0.0] ), + Vertex :: new ( [-bound_x, -bound_y, 0.0], [0.0, 1.0] ), + Vertex :: new ( [ bound_x, -bound_y, 0.0], [1.0, 1.0]) , + Vertex :: new ( [ bound_x, bound_y, 0.0], [1.0, 0.0] ) + ] + } + + pub fn display_atlas(&mut self) { + let atlas = vec![ + r"C:\Users\lisk77\Code Sharing\comet-engine\resources\textures\comet-128.png".to_string(), + r"C:\Users\lisk77\Code Sharing\comet-engine\resources\textures\comet-256.png".to_string(), + ]; + + //self.diffuse_texture = Texture::from_image(&self.device, &self.queue, atlas.atlas(), None, false).unwrap(); + + self.set_texture_atlas(atlas); + + let (bound_x, bound_y) = + ((self.diffuse_texture.size.width as f32/ self.config.width as f32) * 0.5, (self.diffuse_texture.size.height as f32/ self.config.height as f32) * 0.5); + + let vertices: Vec = vec![ + Vertex :: new ( [-bound_x, bound_y, 0.0], [0.0, 0.0] ), + Vertex :: new ( [-bound_x, -bound_y, 0.0], [0.0, 1.0] ), + Vertex :: new ( [ bound_x, -bound_y, 0.0], [1.0, 1.0]) , + Vertex :: new ( [ bound_x, bound_y, 0.0], [1.0, 0.0] ) + ]; + + /*let vertices: Vec = vec![ + Vertex :: new ( [-1.0, 1.0, 0.0], [0.0, 0.0] ), + Vertex :: new ( [-1.0, -1.0, 0.0], [0.0, 1.0] ), + Vertex :: new ( [ 1.0, -1.0, 0.0], [1.0, 1.0]) , + Vertex :: new ( [ 1.0, 1.0, 0.0], [1.0, 0.0] ) + ];*/ + + let indices: Vec = vec![ + 0, 1, 3, + 1, 2, 3 + ]; + + self.set_buffers(vertices, indices) + } + + pub fn set_texture_atlas(&mut self, paths: Vec) { + self.resource_manager.create_texture_atlas(paths); + self.diffuse_texture = Texture::from_image(&self.device, &self.queue, self.resource_manager.texture_atlas().atlas(), None, false).unwrap(); + + let texture_bind_group_layout = + self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + // This should match the filterable field of the + // corresponding Texture entry above. + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + let diffuse_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&self.diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&self.diffuse_texture.sampler), + }, + ], + label: Some("diffuse_bind_group"), + }); + + self.diffuse_bind_group = diffuse_bind_group; + } + + pub(crate) fn set_buffers(&mut self, new_vertex_buffer: Vec, new_index_buffer: Vec) { + match new_vertex_buffer == self.vertex_data { + true => return, + false => { + self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Vertex Buffer"), + contents: bytemuck::cast_slice(&new_vertex_buffer), + usage: wgpu::BufferUsages::VERTEX, + }); + self.vertex_data = new_vertex_buffer; + } + } + + match new_index_buffer == self.index_data { + true => return, + false => { + self.index_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Index Buffer"), + contents: bytemuck::cast_slice(&new_index_buffer), + usage: wgpu::BufferUsages::INDEX, + }); + self.index_data = new_index_buffer.clone(); + self.num_indices = new_index_buffer.len() as u32; + } + } + } + + pub(crate) fn push_to_buffers(&mut self, new_vertex_buffer: &mut Vec, new_index_buffer: &mut Vec) { + self.vertex_data.append(new_vertex_buffer); + self.index_data.append(new_index_buffer); + + self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Vertex Buffer"), + contents: bytemuck::cast_slice(&self.vertex_data), + usage: wgpu::BufferUsages::VERTEX, + }); + + self.index_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Index Buffer"), + contents: bytemuck::cast_slice(&self.index_data), + usage: wgpu::BufferUsages::INDEX, + }); + + self.num_indices = self.index_data.len() as u32; + } + + pub fn clear_buffers(&mut self) { + self.vertex_data = vec![]; + self.index_data = vec![]; + + self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Vertex Buffer"), + contents: bytemuck::cast_slice(&self.vertex_data), + usage: wgpu::BufferUsages::VERTEX, + }); + + self.index_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Index Buffer"), + contents: bytemuck::cast_slice(&self.index_data), + usage: wgpu::BufferUsages::INDEX, + }); + + self.num_indices = self.index_data.len() as u32; + } + + pub fn draw_texture_at(&mut self, texture_path: &str, position: Point3) { + let region = self.resource_manager.texture_locations().get(texture_path).unwrap(); + let (dim_x, dim_y) = region.dimensions(); + + let (bound_x, bound_y) = + ((dim_x as f32/ self.config.width as f32) * 0.5, (dim_y as f32/ self.config.height as f32) * 0.5); + /*let bound_x = dim_x as f32/ self.config.width as f32 * 0.5; + let bound_y = bound_x;*/ + + let vertices: &mut Vec = &mut vec![ + Vertex :: new ( [-bound_x + position.x(), bound_y + position.y(), 0.0 + position.z()], [region.x0(), region.y0()] ), + Vertex :: new ( [-bound_x + position.x(), -bound_y + position.y(), 0.0 + position.z()], [region.x0(), region.y1()] ), + Vertex :: new ( [ bound_x + position.x(), -bound_y + position.y(), 0.0 + position.z()], [region.x1(), region.y1()] ) , + Vertex :: new ( [ bound_x + position.x(), bound_y + position.y(), 0.0 + position.z()], [region.x1(), region.y0()] ) + ]; + + let buffer_size = self.vertex_data.len() as u16; + + let indices: &mut Vec = &mut vec![ + 0 + buffer_size, 1 + buffer_size, 3 + buffer_size, + 1 + buffer_size, 2 + buffer_size, 3 + buffer_size + ]; + + self.push_to_buffers(vertices, indices) + } + + pub fn window(&self) -> &Window { + &self.window + } + + pub fn size(&self) -> PhysicalSize { + self.size + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + //self.projection.resize(new_size.width, new_size.height); + self.size = new_size; + self.config.width = new_size.width; + self.config.height = new_size.height; + self.surface.configure(&self.device, &self.config); + } + } + + pub fn update(&mut self) { + let now = Instant::now(); + self.deltatime = now.duration_since(self.last_frame_time).as_secs_f32(); // Time delta in seconds + self.last_frame_time = now; + } + + pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + let output = self.surface.get_current_texture()?; + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(self.clear_color), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + //render_pass.set_bind_group(1, &self.camera_bind_group, &[]); + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw_indexed(0..self.num_indices, 0, 0..1); + } + + self.queue.submit(iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} \ No newline at end of file diff --git a/crates/comet_renderer/src/model.rs b/crates/comet_renderer/src/model.rs new file mode 100644 index 0000000..1f8cd76 --- /dev/null +++ b/crates/comet_renderer/src/model.rs @@ -0,0 +1,134 @@ +use std::ops::Range; + +use crate::texture; + +pub trait Vertex { + fn desc() -> wgpu::VertexBufferLayout<'static>; +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct ModelVertex { + pub position: [f32; 3], + pub tex_coords: [f32; 2], + pub normal: [f32; 3], +} + +impl Vertex for ModelVertex { + fn desc() -> wgpu::VertexBufferLayout<'static> { + use std::mem; + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, + shader_location: 2, + format: wgpu::VertexFormat::Float32x3, + }, + ], + } + } +} + +pub struct Material { + #[allow(unused)] + pub name: String, + #[allow(unused)] + pub diffuse_texture: texture::Texture, + pub bind_group: wgpu::BindGroup, +} + +pub struct Mesh { + #[allow(unused)] + pub name: String, + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, + pub num_elements: u32, + pub material: usize, +} + +pub struct Model { + pub meshes: Vec, + pub materials: Vec, +} + +pub trait DrawModel<'a> { + #[allow(unused)] + fn draw_mesh( + &mut self, + mesh: &'a Mesh, + material: &'a Material, + camera_bind_group: &'a wgpu::BindGroup, + ); + fn draw_mesh_instanced( + &mut self, + mesh: &'a Mesh, + material: &'a Material, + instances: Range, + camera_bind_group: &'a wgpu::BindGroup, + ); + + #[allow(unused)] + fn draw_model(&mut self, model: &'a Model, camera_bind_group: &'a wgpu::BindGroup); + fn draw_model_instanced( + &mut self, + model: &'a Model, + instances: Range, + camera_bind_group: &'a wgpu::BindGroup, + ); +} + +impl<'a, 'b> DrawModel<'b> for wgpu::RenderPass<'a> +where + 'b: 'a, +{ + fn draw_mesh( + &mut self, + mesh: &'b Mesh, + material: &'b Material, + camera_bind_group: &'b wgpu::BindGroup, + ) { + self.draw_mesh_instanced(mesh, material, 0..1, camera_bind_group); + } + + fn draw_mesh_instanced( + &mut self, + mesh: &'b Mesh, + material: &'b Material, + instances: Range, + camera_bind_group: &'b wgpu::BindGroup, + ) { + self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); + self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32); + self.set_bind_group(0, &material.bind_group, &[]); + self.set_bind_group(1, camera_bind_group, &[]); + self.draw_indexed(0..mesh.num_elements, 0, instances); + } + + fn draw_model(&mut self, model: &'b Model, camera_bind_group: &'b wgpu::BindGroup) { + self.draw_model_instanced(model, 0..1, camera_bind_group); + } + + fn draw_model_instanced( + &mut self, + model: &'b Model, + instances: Range, + camera_bind_group: &'b wgpu::BindGroup, + ) { + for mesh in &model.meshes { + let material = &model.materials[mesh.material]; + self.draw_mesh_instanced(mesh, material, instances.clone(), camera_bind_group); + } + } +} \ No newline at end of file diff --git a/crates/comet_renderer/src/shader.wgsl b/crates/comet_renderer/src/shader.wgsl new file mode 100644 index 0000000..6c5b01c --- /dev/null +++ b/crates/comet_renderer/src/shader.wgsl @@ -0,0 +1,38 @@ +// Vertex shader +/*struct CameraUniform { + view_proj: mat4x4, +}; +@group(1) @binding(0) // 1. +var camera: CameraUniform;*/ + +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, +} + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coords = model.tex_coords; + out.clip_position = /*camera.view_proj **/ vec4(model.position, 1.0); + return out; +} + +// Fragment shader + +@group(0) @binding(0) +var t_diffuse: texture_2d; +@group(0) @binding(1) +var s_diffuse: sampler; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return textureSample(t_diffuse, s_diffuse, in.tex_coords); +} \ No newline at end of file diff --git a/crates/comet_renderer/src/texture.rs b/crates/comet_renderer/src/texture.rs new file mode 100644 index 0000000..4042061 --- /dev/null +++ b/crates/comet_renderer/src/texture.rs @@ -0,0 +1,254 @@ +use anyhow::*; +use image::GenericImageView; + +pub struct Texture { + #[allow(unused)] + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, + pub sampler: wgpu::Sampler, + pub size: wgpu::Extent3d, +} + +impl Texture { + pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + + pub fn create_depth_texture( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + label: &str, + ) -> Self { + let size = wgpu::Extent3d { + width: config.width.max(1), + height: config.height.max(1), + depth_or_array_layers: 1, + }; + let desc = wgpu::TextureDescriptor { + label: Some(label), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[Self::DEPTH_FORMAT], + }; + let texture = device.create_texture(&desc); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + ..Default::default() + }); + + Self { + texture, + view, + sampler, + size, // NEW! + } + } + + #[allow(dead_code)] + pub fn from_bytes( + device: &wgpu::Device, + queue: &wgpu::Queue, + bytes: &[u8], + label: &str, + is_normal_map: bool, + ) -> Result { + let img = image::load_from_memory(bytes)?; + Self::from_image(device, queue, &img, Some(label), is_normal_map) + } + + pub fn from_image( + device: &wgpu::Device, + queue: &wgpu::Queue, + img: &image::DynamicImage, + label: Option<&str>, + is_normal_map: bool, + ) -> Result { + let dimensions = img.dimensions(); + let rgba = img.to_rgba8(); + + let format = if is_normal_map { + wgpu::TextureFormat::Rgba8Unorm + } else { + wgpu::TextureFormat::Rgba8UnormSrgb + }; + let usage = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST; + let size = wgpu::Extent3d { + width: img.width(), + height: img.height(), + depth_or_array_layers: 1, + }; + let texture = Self::create_2d_texture( + device, + size.width, + size.height, + format, + usage, + wgpu::FilterMode::Linear, + label, + ); + + queue.write_texture( + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &texture.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &rgba, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * dimensions.0), + rows_per_image: Some(dimensions.1), + }, + size, + ); + + Ok(texture) + } + + pub(crate) fn create_2d_texture( + device: &wgpu::Device, + width: u32, + height: u32, + format: wgpu::TextureFormat, + usage: wgpu::TextureUsages, + mag_filter: wgpu::FilterMode, + label: Option<&str>, + ) -> Self { + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + Self::create_texture( + device, + label, + size, + format, + usage, + wgpu::TextureDimension::D2, + mag_filter, + ) + } + + pub fn create_texture( + device: &wgpu::Device, + label: Option<&str>, + size: wgpu::Extent3d, + format: wgpu::TextureFormat, + usage: wgpu::TextureUsages, + dimension: wgpu::TextureDimension, + mag_filter: wgpu::FilterMode, + ) -> Self { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size, + mip_level_count: 1, + sample_count: 1, + dimension, + format, + usage, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + Self { + texture, + view, + sampler, + size, // NEW! + } + } +} + +pub struct CubeTexture { + texture: wgpu::Texture, + sampler: wgpu::Sampler, + view: wgpu::TextureView, +} + +impl CubeTexture { + pub fn create_2d( + device: &wgpu::Device, + width: u32, + height: u32, + format: wgpu::TextureFormat, + mip_level_count: u32, + usage: wgpu::TextureUsages, + mag_filter: wgpu::FilterMode, + label: Option<&str>, + ) -> Self { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size: wgpu::Extent3d { + width, + height, + // A cube has 6 sides, so we need 6 layers + depth_or_array_layers: 6, + }, + mip_level_count, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label, + dimension: Some(wgpu::TextureViewDimension::Cube), + array_layer_count: Some(6), + ..Default::default() + }); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + Self { + texture, + sampler, + view, + } + } + + pub fn texture(&self) -> &wgpu::Texture { + &self.texture + } + + pub fn view(&self) -> &wgpu::TextureView { + &self.view + } + + pub fn sampler(&self) -> &wgpu::Sampler { + &self.sampler + } +} \ No newline at end of file diff --git a/crates/comet_resources/Cargo.toml b/crates/comet_resources/Cargo.toml new file mode 100644 index 0000000..71deea7 --- /dev/null +++ b/crates/comet_resources/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "comet_resources" +version = "0.1.0" +edition = "2021" + +[dependencies] +comet_log = { path = "../comet_log" } +chrono = "0.4.38" + +wgpu = { version = "22.0"} +anyhow = "1.0" +tobj = { version = "3.2", default-features = false, features = ["async"]} +bytemuck = { version = "1.16", features = [ "derive" ] } +log = "0.4.22" + +[dependencies.image] +version = "0.24" +default-features = false +features = ["png", "jpeg", "hdr"] diff --git a/crates/comet_resources/src/lib.rs b/crates/comet_resources/src/lib.rs new file mode 100644 index 0000000..07b7aca --- /dev/null +++ b/crates/comet_resources/src/lib.rs @@ -0,0 +1,146 @@ +pub use resources::*; +pub use texture::*; +pub use vertex::*; + +pub mod resources; +pub mod texture; +pub mod vertex; +pub mod texture_atlas; +/*use std::io::{BufReader, Cursor}; +use wgpu::util::DeviceExt; + +use crate::{model, texture}; + +pub async fn load_string(file_name: &str) -> anyhow::Result { + let path = std::path::Path::new(env!("OUT_DIR")) + .join("res") + .join(file_name); + let txt = std::fs::read_to_string(path)?; + + Ok(txt) +} + +pub async fn load_binary(file_name: &str) -> anyhow::Result> { + let path = std::path::Path::new(env!("OUT_DIR")) + .join("res") + .join(file_name); + let data = std::fs::read(path)?; + + Ok(data) +} + +pub async fn load_texture( + file_name: &str, + device: &wgpu::Device, + queue: &wgpu::Queue, +) -> anyhow::Result { + let data = load_binary(file_name).await?; + texture::Texture::from_bytes(device, queue, &data, file_name) +} + +pub async fn load_model( + file_name: &str, + device: &wgpu::Device, + queue: &wgpu::Queue, + layout: &wgpu::BindGroupLayout, +) -> anyhow::Result { + let obj_text = load_string(file_name).await?; + let obj_cursor = Cursor::new(obj_text); + let mut obj_reader = BufReader::new(obj_cursor); + + let (models, obj_materials) = tobj::load_obj_buf_async( + &mut obj_reader, + &tobj::LoadOptions { + triangulate: true, + single_index: true, + ..Default::default() + }, + |p| async move { + let mat_text = load_string(&p).await.unwrap(); + tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text))) + }, + ) + .await?; + + let mut materials = Vec::new(); + for m in obj_materials? { + let diffuse_texture = load_texture(&m.diffuse_texture, device, queue).await?; + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + ], + label: None, + }); + + materials.push(model::Material { + name: m.name, + diffuse_texture, + bind_group, + }) + } + + let meshes = models + .into_iter() + .map(|m| { + let vertices = (0..m.mesh.positions.len() / 3) + .map(|i| { + if m.mesh.normals.is_empty(){ + model::ModelVertex { + position: [ + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ], + tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], + normal: [0.0, 0.0, 0.0], + } + }else{ + model::ModelVertex { + position: [ + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ], + tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], + normal: [ + m.mesh.normals[i * 3], + m.mesh.normals[i * 3 + 1], + m.mesh.normals[i * 3 + 2], + ], + } + } + }) + .collect::>(); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Vertex Buffer", file_name)), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Index Buffer", file_name)), + contents: bytemuck::cast_slice(&m.mesh.indices), + usage: wgpu::BufferUsages::INDEX, + }); + + log::info!("Mesh: {}", m.name); + model::Mesh { + name: file_name.to_string(), + vertex_buffer, + index_buffer, + num_elements: m.mesh.indices.len() as u32, + material: m.mesh.material_id.unwrap_or(0), + } + }) + .collect::>(); + + Ok(model::Model { meshes, materials }) +}*/ \ No newline at end of file diff --git a/crates/comet_resources/src/resources.rs b/crates/comet_resources/src/resources.rs new file mode 100644 index 0000000..efb9aad --- /dev/null +++ b/crates/comet_resources/src/resources.rs @@ -0,0 +1,182 @@ +use std::{ + collections::HashMap, path::Path +}; + +use wgpu::{Device, FilterMode, Queue, TextureFormat, TextureUsages}; +use crate::{texture, Texture}; +use crate::texture_atlas::{TextureAtlas, TextureRegion}; + +pub struct ResourceManager { + texture_atlas: TextureAtlas, + data_files: HashMap +} + +impl ResourceManager { + pub fn new() -> Self { + Self { + texture_atlas: TextureAtlas::empty(), + data_files: HashMap::new() + } + } + + pub fn texture_atlas(&self) -> &TextureAtlas { + &self.texture_atlas + } + + pub fn texture_locations(&self) -> &HashMap { + &self.texture_atlas.textures() + } + + pub fn data_files(&self) -> &HashMap { + &self.data_files + } + + pub fn set_texture_atlas(&mut self, texture_atlas: TextureAtlas) { + self.texture_atlas = texture_atlas; + + // This is just for testing purposes + //self.texture_locations.insert("normal_comet.png".to_string(), ([0,0], [15,15])); + //self.texture_locations.insert("green_comet.png".to_string(), ([0,15], [15,31])); + } + + pub fn create_texture_atlas(&mut self, paths: Vec) { + self.texture_atlas = TextureAtlas::from_texture_paths(paths) + } + + pub async fn load_string(&self, file_name: &str) -> anyhow::Result { + let path = Path::new(std::env::var("OUT_DIR")?.as_str()) + .join("res") + .join(file_name); + let txt = std::fs::read_to_string(path)?; + + Ok(txt) + } + + pub async fn load_binary(&self, file_name: &str) -> anyhow::Result> { + let path = Path::new(std::env::var("OUT_DIR").unwrap().as_str()) + .join("res") + .join(file_name); + let data = std::fs::read(path)?; + + Ok(data) + } + + pub async fn load_texture( + &self, + file_name: &str, + is_normal_map: bool, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> anyhow::Result { + let data = self.load_binary(file_name).await?; + texture::Texture::from_bytes(device, queue, &data, file_name, is_normal_map) + } + + /*pub async fn load_model( + &self, + file_name: &str, + device: &wgpu::Device, + queue: &wgpu::Queue, + layout: &wgpu::BindGroupLayout, + ) -> anyhow::Result { + let obj_text = self.load_string(file_name).await?; + let obj_cursor = Cursor::new(obj_text); + let mut obj_reader = BufReader::new(obj_cursor); + + let (models, obj_materials) = tobj::load_obj_buf_async( + &mut obj_reader, + &tobj::LoadOptions { + triangulate: true, + single_index: true, + ..Default::default() + }, + |p| async move { + let mat_text = self.load_string(&p).await.unwrap(); + tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text))) + }, + ) + .await?; + + let mut materials = Vec::new(); + for m in obj_materials? { + let diffuse_texture = self.load_texture(&m.diffuse_texture, false, device, queue).await?; + let normal_texture = self.load_texture(&m.normal_texture, true, device, queue).await?; + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + ], + label: None, + }); + + materials.push(model::Material { + name: m.name, + diffuse_texture, + bind_group, + }); + } + + let meshes = models + .into_iter() + .map(|m| { + let vertices = (0..m.mesh.positions.len() / 3) + .map(|i| { + if m.mesh.normals.is_empty() { + model::ModelVertex { + position: [ + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ], + tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], + normal: [0.0, 0.0, 0.0], + } + } else { + model::ModelVertex { + position: [ + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ], + tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], + normal: [ + m.mesh.normals[i * 3], + m.mesh.normals[i * 3 + 1], + m.mesh.normals[i * 3 + 2], + ], + } + } + }) + .collect::>(); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Vertex Buffer", file_name)), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Index Buffer", file_name)), + contents: bytemuck::cast_slice(&m.mesh.indices), + usage: wgpu::BufferUsages::INDEX, + }); + + model::Mesh { + name: file_name.to_string(), + vertex_buffer, + index_buffer, + num_elements: m.mesh.indices.len() as u32, + material: m.mesh.material_id.unwrap_or(0), + } + }) + .collect::>(); + + Ok(model::Model { meshes, materials }) + }*/ +} diff --git a/crates/comet_resources/src/texture.rs b/crates/comet_resources/src/texture.rs new file mode 100644 index 0000000..ea75f1b --- /dev/null +++ b/crates/comet_resources/src/texture.rs @@ -0,0 +1,325 @@ +use anyhow::*; +use image::{DynamicImage, GenericImageView, RgbaImage}; +use wgpu::{Device, Queue}; + +#[derive(Debug)] +pub struct Texture { + #[allow(unused)] + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, + pub sampler: wgpu::Sampler, + pub size: wgpu::Extent3d, +} + +impl Texture { + pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + + pub fn create_depth_texture( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + label: &str, + ) -> Self { + let size = wgpu::Extent3d { + width: config.width.max(1), + height: config.height.max(1), + depth_or_array_layers: 1, + }; + let desc = wgpu::TextureDescriptor { + label: Some(label), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[Self::DEPTH_FORMAT], + }; + let texture = device.create_texture(&desc); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + ..Default::default() + }); + + Self { + texture, + view, + sampler, + size, // NEW! + } + } + + #[allow(dead_code)] + pub fn from_bytes( + device: &wgpu::Device, + queue: &wgpu::Queue, + bytes: &[u8], + label: &str, + is_normal_map: bool, + ) -> Result { + let img = image::load_from_memory(bytes)?; + Self::from_image(device, queue, &img, Some(label), is_normal_map) + } + + pub fn from_image( + device: &wgpu::Device, + queue: &wgpu::Queue, + img: &image::DynamicImage, + label: Option<&str>, + is_normal_map: bool, + ) -> Result { + let dimensions = img.dimensions(); + let rgba = img.to_rgba8(); + + let format = if is_normal_map { + wgpu::TextureFormat::Rgba8Unorm + } else { + wgpu::TextureFormat::Rgba8UnormSrgb + }; + let usage = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST; + let size = wgpu::Extent3d { + width: img.width(), + height: img.height(), + depth_or_array_layers: 1, + }; + let texture = Self::create_2d_texture( + device, + size.width, + size.height, + format, + usage, + wgpu::FilterMode::Nearest, + label, + ); + + queue.write_texture( + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &texture.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &rgba, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * dimensions.0), + rows_per_image: Some(dimensions.1), + }, + size, + ); + + Ok(texture) + } + + pub(crate) fn create_2d_texture( + device: &wgpu::Device, + width: u32, + height: u32, + format: wgpu::TextureFormat, + usage: wgpu::TextureUsages, + mag_filter: wgpu::FilterMode, + label: Option<&str>, + ) -> Self { + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + Self::create_texture( + device, + label, + size, + format, + usage, + wgpu::TextureDimension::D2, + mag_filter, + ) + } + + pub fn create_texture( + device: &wgpu::Device, + label: Option<&str>, + size: wgpu::Extent3d, + format: wgpu::TextureFormat, + usage: wgpu::TextureUsages, + dimension: wgpu::TextureDimension, + mag_filter: wgpu::FilterMode, + ) -> Self { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size, + mip_level_count: 1, + sample_count: 1, + dimension, + format, + usage, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + Self { + texture, + view, + sampler, + size, // NEW! + } + } + + pub fn to_image( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> Result { + // Size of the texture + let width = self.size.width; + let height = self.size.height; + + // Calculate the size of the texture in bytes + let texture_size_bytes = (4 * width * height) as wgpu::BufferAddress; + + // Create a buffer for reading the texture data back from the GPU + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Texture Readback Buffer"), + size: texture_size_bytes, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + // Create a command encoder to copy the texture data to the buffer + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Texture to Buffer Encoder"), + }); + + // Define the copy operation from the texture to the buffer + encoder.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: &self.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * width), + rows_per_image: Some(height), + }, + }, + self.size, + ); + + // Submit the command to the queue + queue.submit(Some(encoder.finish())); + + // Wait for the GPU to finish the operation + let buffer_slice = buffer.slice(..); + buffer_slice.map_async(wgpu::MapMode::Read, |result| { + if let Err(e) = result { + eprintln!("Failed to map buffer: {:?}", e); + } + }); + + // Get the buffer data + let data = buffer_slice.get_mapped_range(); + + // Convert the raw data into an image::RgbaImage + let image = RgbaImage::from_raw(width, height, data.to_vec()) + .ok_or_else(|| anyhow!("Failed to create image from raw texture data"))?; + + // Unmap the buffer now that we're done with it + buffer.unmap(); + + // Convert the RgbaImage into a DynamicImage + Ok(DynamicImage::ImageRgba8(image)) + } +} + +pub struct CubeTexture { + texture: wgpu::Texture, + sampler: wgpu::Sampler, + view: wgpu::TextureView, +} + +impl CubeTexture { + pub fn create_2d( + device: &wgpu::Device, + width: u32, + height: u32, + format: wgpu::TextureFormat, + mip_level_count: u32, + usage: wgpu::TextureUsages, + mag_filter: wgpu::FilterMode, + label: Option<&str>, + ) -> Self { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size: wgpu::Extent3d { + width, + height, + // A cube has 6 sides, so we need 6 layers + depth_or_array_layers: 6, + }, + mip_level_count, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label, + dimension: Some(wgpu::TextureViewDimension::Cube), + array_layer_count: Some(6), + ..Default::default() + }); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + Self { + texture, + sampler, + view, + } + } + + pub fn texture(&self) -> &wgpu::Texture { + &self.texture + } + + pub fn view(&self) -> &wgpu::TextureView { + &self.view + } + + pub fn sampler(&self) -> &wgpu::Sampler { + &self.sampler + } +} \ No newline at end of file diff --git a/crates/comet_resources/src/texture_atlas.rs b/crates/comet_resources/src/texture_atlas.rs new file mode 100644 index 0000000..d07ac8b --- /dev/null +++ b/crates/comet_resources/src/texture_atlas.rs @@ -0,0 +1,186 @@ +use std::collections::HashMap; +use std::path::Path; +use chrono::Local; +use std::time::Instant; +use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat}; +use comet_log::info; +use wgpu::{Device, FilterMode, TextureFormat, TextureUsages}; +use crate::Texture; + +#[derive(Debug)] +pub struct TextureRegion { + x0: f32, + y0: f32, + x1: f32, + y1: f32, + dimensions: (u32, u32) +} + +impl TextureRegion { + pub fn new(x0: f32, y0: f32, x1: f32, y1: f32, dimensions: (u32, u32)) -> Self { + Self { + x0, + y0, + x1, + y1, + dimensions + } + } + + pub fn x0(&self) -> f32 { + self.x0 + } + + pub fn x1(&self) -> f32 { + self.x1 + } + + pub fn y0(&self) -> f32 { + self.y0 + } + + pub fn y1(&self) -> f32 { + self.y1 + } + + pub fn dimensions(&self) -> (u32, u32) { + self.dimensions + } +} + +#[derive(Debug)] +pub struct TextureAtlas { + atlas: DynamicImage, + textures: HashMap, +} + +impl TextureAtlas { + pub fn empty() -> Self { + Self { + atlas: DynamicImage::new(1,1, image::ColorType::Rgb8), + textures: HashMap::new() + } + } + + pub fn texture_paths(&self) -> Vec { + self.textures.keys().map(|k| k.to_string()).collect() + } + + fn calculate_atlas_width(textures: &Vec) -> u32 { + let mut last_height: u32 = textures.get(0).unwrap().height(); + let mut widths: Vec = Vec::new(); + let mut current_width: u32 = 0; + + for texture in textures { + if last_height != texture.height() { + widths.push(current_width); + current_width = 0; + last_height = texture.height(); + } + current_width += texture.width(); + } + + widths.push(current_width); + + *widths.iter().max().unwrap() + } + + fn calculate_atlas_height(textures: &Vec) -> u32 { + let last_height: u32 = textures.get(0).unwrap().height(); + let mut height: u32 = 0; + height += last_height; + + for texture in textures { + if last_height == texture.height() { + continue; + } + + height += texture.height(); + } + + height + } + + fn insert_texture_at(base: &mut DynamicImage, texture: &DynamicImage, x_pos: u32, y_pos: u32) { + for y in 0..texture.height() { + for x in 0..texture.width() { + let pixel = texture.get_pixel(x,y); + base.put_pixel(x + x_pos, y + y_pos, pixel); + } + } + } + + pub fn from_texture_paths( + paths: Vec, + ) -> Self { + //let t0 = Instant::now(); + + let mut textures: Vec = Vec::new(); + let mut regions: HashMap = HashMap::new(); + + info!("Loading textures..."); + + for path in &paths { + textures.push(image::open(&Path::new(path.as_str())).expect("Failed to load texture")); + } + + info!("Textures loaded!"); + info!("Sorting textures by height..."); + + let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = textures.iter().zip(paths.iter()).collect(); + texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); + let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = texture_path_pairs.into_iter().unzip(); + let sorted_textures: Vec = sorted_textures.into_iter().map(|t| t.clone()).collect(); + let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); + + let (height, width) = (Self::calculate_atlas_height(&sorted_textures), Self::calculate_atlas_width(&sorted_textures)); + let mut base = DynamicImage::new_rgba8(width,height); + + let mut previous = sorted_textures.get(0).unwrap().height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; + + info!("Creating texture atlas..."); + + for (texture, path) in sorted_textures.iter().zip(sorted_paths.iter()) { + if texture.height() != previous { + y_offset += previous; + x_offset = 0; + previous = texture.height(); + } + //base.copy_from(texture, x_offset, y_offset).expect("Nope, you propably failed the offets"); + Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); + regions.insert(path.to_string(), TextureRegion::new( + x_offset as f32 / width as f32, + y_offset as f32 / height as f32, + (x_offset + texture.width()) as f32 / width as f32, + (y_offset + texture.height()) as f32 / height as f32, + texture.dimensions() + )); + x_offset += texture.width(); + } + + // Save the image to disk as a PNG + //let output_path = Path::new(r"C:\Users\lisk77\Code Sharing\comet-engine\resources\textures\atlas.png"); + //base.save_with_format(output_path, ImageFormat::Png).expect("Failed to save texture atlas"); + + info!("Texture atlas created!"); + + /*let t1 = Instant::now(); + let delta = t1.duration_since(t0); + println!("{:?}", delta);*/ + + TextureAtlas { + atlas: base, + textures: regions + } + } + + pub fn atlas(&self) -> &DynamicImage { + &self.atlas + } + + pub fn textures(&self) -> &HashMap { + &self.textures + } +} \ No newline at end of file diff --git a/crates/comet_resources/src/vertex.rs b/crates/comet_resources/src/vertex.rs new file mode 100644 index 0000000..71573f7 --- /dev/null +++ b/crates/comet_resources/src/vertex.rs @@ -0,0 +1,44 @@ +use wgpu::Color; + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)] +pub struct Vertex { + position: [f32; 3], + tex_coords: [f32; 2], +} + +impl Vertex { + pub fn new(position: [f32; 3], tex_coords: [f32; 2]) -> Self { + Self { + position, + tex_coords + } + } + + pub fn set_position(&mut self, new_position: [f32;3]) { + self.position = new_position + } + + pub fn set_tex_coords(&mut self, new_tex_coords: [f32; 2]) { + self.tex_coords = new_tex_coords + } + + pub fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x2, + } + ] + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f4d879c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +pub use winit::keyboard as keyboard; +pub use comet_math as math; +pub use comet_renderer as renderer; +pub use comet_resources as resources; +pub use comet_ecs as ecs; +pub use comet_app as app; +pub use comet_colors as colors; +pub use comet_input as input; +pub use comet_log as log; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..eb84ee1 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,49 @@ +use comet::{ + app::{ + App, + ApplicationType::* + }, + renderer::Renderer, + ecs::World, + math::*, + input::keyboard::*, + log::* +}; +use winit::event::{WindowEvent}; +use comet_ecs::{Component, ComponentSet, Transform2D}; +use comet_input::mouse::{mouse_entered, mouse_pressed, Button}; + +#[derive(Component)] +struct TestComponent { + position: f32 +} + +fn input(event: &WindowEvent, app: &mut App, renderer: &mut Renderer) { + match event { + _ if key_pressed(event, Key::KeyI) => app.world_mut().register_component::(), + _ if key_pressed(event, Key::Escape) => app.quit(), + _ if key_pressed(event, Key::KeyE) => { + let id = app.world_mut().new_entity(); + app.world_mut().add_component::(id as usize, TestComponent::new()) + }, + _ if key_pressed(event, Key::KeyR) => { + debug!(format!("{:?}", app.world().get_entities_with(ComponentSet::from_ids(vec![Transform2D::type_id(), TestComponent::type_id()])))); + }, + _ => {} + } +} + +fn update(world: &mut World, renderer: &mut Renderer) { + if world.entities().len() == 0 { + let id = world.new_entity(); + } +} + +fn main() { + App::new(App2D) + .with_title("Comet App") + .with_icon(r"resources/textures/comet_icon.png") + .with_size(1920, 1080) + .run(input, update) + ; +} \ No newline at end of file