initial commit

This commit is contained in:
lisk77 2024-10-26 02:15:26 +02:00
commit 6154c72b0e
55 changed files with 9481 additions and 0 deletions

56
Cargo.toml Normal file
View file

@ -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 }

29
build.rs Normal file
View file

@ -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, &copy_options)?;
Ok(())
}

View file

@ -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"]

175
crates/comet_app/src/app.rs Normal file
View file

@ -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<Icon>,
size: Option<LogicalSize<u32>>,
clear_color: Option<LinearRgba>,
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<Icon> {
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<Icon>, window_size: &Option<LogicalSize<u32>>, 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<F: Fn(&WindowEvent, &mut App, &mut Renderer), G: Fn(&mut World, &mut Renderer)>(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<F: Fn(&WindowEvent, &mut App, &mut Renderer), G: Fn(&mut World, &mut Renderer)>(mut self, input_manager: F, game_manager: G) {
pollster::block_on(self.run_app(input_manager, game_manager));
}
}

View file

@ -0,0 +1,2 @@
pub use app::*;
mod app;

View file

@ -0,0 +1,8 @@
[package]
name = "comet_colors"
version = "0.1.0"
edition = "2021"
[dependencies]
comet_math = { path = "../comet_math" }
wgpu = { version = "22.0" }

View file

@ -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<f32>) -> 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<f32> {
self.to_hwba().to_rgba()
}
pub fn to_rgba8(&self) -> sRgba<u8> {
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()
}
}

View file

@ -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<f32> {
self.to_hwba().to_rgba()
}
pub fn to_rgba8(&self) -> sRgba<u8> {
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()
}
}

View file

@ -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<u8>) -> Self {
let rgba = rgba.to_rbga();
self.from_rgba(rgba)
}
pub fn from_rgba(&self, rgba: sRgba<f32>) -> 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<u8> {
let rgba = self.to_rgba();
sRgba::<u8>::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<f32> {
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::<f32>::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()
}
}

View file

@ -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<f32> {
self.to_linear().to_rgba()
}
pub fn to_rgba8(&self) -> sRgba<u8> {
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()
}
}

View file

@ -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<f32> {
self.to_linear().to_rgba()
}
pub fn to_rgba8(&self) -> sRgba<u8> {
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()
}
}

View file

@ -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;

View file

@ -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<f32>) -> 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<f32> {
sRgba::<f32>::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<u8> {
let color = self.to_rgba();
sRgba::<u8>::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
}
}
}

View file

@ -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<f32> {
self.to_linear().to_rgba()
}
pub fn to_rgba8(&self) -> sRgba<u8> {
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()
}
}

View file

@ -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<f32> {
self.to_linear().to_rgba()
}
pub fn to_rgba8(&self) -> sRgba<u8> {
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()
}
}

View file

@ -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<u8>` and `sRgba<f32>`
/// 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<T> {
red: T,
green: T,
blue: T,
alpha: T,
}
impl sRgba<u8> {
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<f32>) -> 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<f32> {
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<f32> {
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<u8>) -> 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<u8> {
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
)
}
}

View file

@ -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<f32> {
self.to_linear().to_rgba()
}
pub fn to_rgba8(&self) -> sRgba<u8> {
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()
}
}

View file

@ -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"

View file

@ -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"

View file

@ -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::<Vec<_>>(),
Fields::Unnamed(fields) => fields.unnamed.iter().collect::<Vec<_>>(),
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::<Vec<_>>()
},
Fields::Unnamed(fields) => {
// Generate default values for tuple structs
fields.unnamed.iter().map(|_field| {
quote! { Default::default() }
}).collect::<Vec<_>>()
},
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::<Self>()
}
fn type_name() -> String {
std::any::type_name::<Self>().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
}

View file

@ -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::<Self>()
}
fn type_name() -> String {
std::any::type_name::<Self>().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
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,40 @@
#[derive(Debug, Clone)]
pub struct IdQueue {
queue: Vec<u32>
}
impl IdQueue {
pub fn new() -> Self {
Self { queue: Vec::new() }
}
pub fn from_vec(queue: Vec<u32>) -> Self {
Self { queue }
}
pub fn front(&self) -> Option<u32> {
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<u32> {
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
}
}

View file

@ -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;

View file

@ -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<u8>,
swap_scratch: NonNull<u8>,
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<u8> {
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<T>(&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<Layout> {
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<T: 'static>(capacity: usize) -> Self {
let layout = Layout::new::<T>();
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<T: 'static>(&mut self, item: T) {
assert_eq!(TypeId::of::<T>(), TypeId::of::<T>(), "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<T: 'static>(&self, index: usize) -> Option<&T> {
assert_eq!(TypeId::of::<T>(), TypeId::of::<T>(), "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<T: 'static>(&mut self, index: usize) -> Option<&mut T> {
assert_eq!(TypeId::of::<T>(), TypeId::of::<T>(), "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<T: 'static>(&mut self, index: usize) -> Option<T> {
assert_eq!(TypeId::of::<T>(), TypeId::of::<T>(), "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::<u8>::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<Self::Item> {
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<Option<usize>>,
dense: Column,
len: usize
}
impl SparseSet {
pub fn new<T: 'static>(capacity: usize) -> Self {
Self {
sparse: Vec::with_capacity(capacity),
dense: Column::new::<T>(capacity),
len: 0
}
}
pub fn set<T: 'static>(&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<T: 'static>(&mut self, index: usize) -> Option<T> {
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<T: 'static>(&self, index: usize) -> Option<&T> {
if index >= self.sparse.len() || self.sparse[index] == None {
return None;
}
self.dense.get::<T>(index)
}
pub fn get_mut<T: 'static>(&mut self, index: usize) -> Option<&mut T> {
if index >= self.sparse.len() || self.sparse[index] == None {
return None;
}
self.dense.get_mut::<T>(index)
}
}
#[derive(Debug, Clone)]
pub struct ComponentStorage {
index_map: HashMap<TypeId, usize>,
pub(crate) keys: Vec<TypeId>,
components: Vec<SparseSet>
}
impl ComponentStorage {
pub fn new() -> Self {
Self {
index_map: HashMap::new(),
keys: Vec::new(),
components: Vec::new()
}
}
pub fn keys(&self) -> &Vec<TypeId> {
&self.keys
}
pub fn contains_component(&self, type_id: &TypeId) -> bool {
self.keys.contains(type_id)
}
pub fn get<T: Component + 'static>(&self) -> Option<&SparseSet> {
self.components.get(*self.index_map.get(&T::type_id()).unwrap())
}
pub fn set<T: Component + 'static>(&mut self, sparse_set: SparseSet) {
let _ = self.components.get_mut(*self.index_map.get(&T::type_id()).unwrap());
}
pub fn register_component<T: Component + 'static>(&mut self, capacity: usize) {
//self.storage.insert(T::type_id(), SparseSet::new::<T>(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::<T>(capacity));
}
pub fn get_component<T: Component + 'static>(&self, entity_id: usize) -> Option<&T> {
//self.storage.get(&T::type_id()).unwrap().get::<T>(*entity.id() as usize)
self.components.get(*self.index_map.get(&T::type_id()).unwrap()).unwrap().get::<T>(entity_id)
}
pub fn get_component_mut<T: Component + 'static>(&mut self, entity_id: usize) -> Option<&mut T> {
self.components.get_mut(*self.index_map.get(&T::type_id()).unwrap()).unwrap().get_mut::<T>(entity_id)
}
pub fn set_component<T: Component + 'static>(&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::<T>(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<T: Component + 'static>(&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::<T>(i);
}
}
self.components.remove(index);
self.index_map.remove(&type_id);
self.keys.retain(|&k| k != type_id);
}
}
pub fn remove_component<T: Component + 'static>(&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::<T>(entity_id);
}
}
pub(crate) fn get_dense_list_as_vec<T: Component + Clone + 'static>(&self) -> Option<Vec<T>> {
let mut resulting_vec: Vec<T> = 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::<T>(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<TypeId>
}
impl ComponentSet {
pub fn new() -> Self {
Self {
set: HashSet::new()
}
}
pub fn from_ids(ids: Vec<TypeId>) -> 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<H: Hasher>(&self, state: &mut H) {
let mut types: Vec<TypeId> = self.set.iter().cloned().collect();
types.sort();
types.hash(state);
}
}
#[derive(Debug)]
pub struct Archetypes {
archetypes: HashMap<ComponentSet, Vec<u32>>
}
impl Archetypes {
pub fn new() -> Self {
Self {
archetypes: HashMap::new()
}
}
pub fn component_sets(&self) -> Vec<ComponentSet> {
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<u32>> {
self.archetypes.get(components)
}
pub fn get_archetype_mut(&mut self, components: &ComponentSet) -> Option<&mut Vec<u32>> {
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)
}
}

View file

@ -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<Option<Entity>>,
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::<Transform2D>(0),
"3D" => component_storage.register_component::<Transform3D>(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<Option<Entity>> {
&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::<Transform2D>(self.next_id as usize, Transform2D::new()),
"3D" => self.add_component::<Transform3D>(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::<Transform2D>(self.next_id as usize, Transform2D::new()),
"3D" => self.add_component::<Transform3D>(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::<u8>(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<ComponentSet> = component_sets.iter()
.enumerate()
.filter_map(|(i, &ref elem)| if elem.is_subset(&components) { Some(i) } else { None })
.collect::<Vec<usize>>()
.iter()
.map(|index| component_sets[*index].clone())
.collect::<Vec<ComponentSet>>();
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::<Vec<usize>>();
let type_ids = components.iter().map(|index| self.components.keys[*index]).collect::<Vec<TypeId>>();
ComponentSet::from_ids(type_ids)
}
pub fn register_component<T: Component + 'static>(&mut self) {
self.components.register_component::<T>(self.entities.len());
info!(format!("Registered component: {}", T::type_name()));
}
pub fn deregister_component<T: Component + 'static>(&mut self) {
self.components.deregister_component::<T>();
info!(format!("Deregistered component: {}", T::type_name()));
}
pub fn add_component<T: Component + 'static>(&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<T: Component + 'static>(&mut self, entity_id: usize) {
self.components.remove_component::<T>(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<T: Component + 'static>(&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::<T>(entity_id) != None, "There is no component {} bound to the entity {} in the world!", T::type_name(), entity_id);
self.components.get_component::<T>(entity_id).unwrap()
}
pub fn get_component_mut<T: Component + 'static>(&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::<T>(entity_id) != None, "There is no component {} bound to the entity {} in the world!", T::type_name(), entity_id);
self.components.get_component_mut::<T>(entity_id).unwrap()
}
pub fn get_entities_with(&self, components: ComponentSet) -> Vec<u32> {
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()
}
}

View file

@ -0,0 +1,7 @@
[package]
name = "comet_input"
version = "0.1.0"
edition = "2021"
[dependencies]
winit = { version = "0.29", features = ["rwh_05"] }

View file

@ -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,
}
}

View file

@ -0,0 +1,2 @@
pub mod keyboard;
pub mod mouse;

View file

@ -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,
}
}

View file

@ -0,0 +1,8 @@
[package]
name = "comet_log"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = "0.4"
colored = "2.0"

View file

@ -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
);
};
}

View file

@ -0,0 +1,7 @@
[package]
name = "comet_math"
version = "0.1.0"
edition = "2021"
[dependencies]
num-traits = "0.2.19"

View file

@ -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<Point2>,
degree: u8
}
impl Bezier2 {
pub fn new(points: Vec<Point2>) -> 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<Point3>,
degree: u8
}
impl Bezier3 {
pub fn new(points: Vec<Point3>) -> 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
)
}
}

View file

@ -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 }
}

View file

@ -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;

View file

@ -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<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) => 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<Vec2> {
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<Mat2> 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<Mat2> 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<f32> 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<Mat2> 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<Mat2> 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<Vec2> 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<f32> 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<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) => 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<Vec3> {
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<Mat3> 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<Mat3> 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<f32> 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<Mat3> 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<Mat3> 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<Vec3> 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<f32> 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<f32> {
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<Vec4> {
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<Mat4> 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<Mat4> 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<f32> 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<f32> 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<Mat4> 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<T: LinearTransformation>(mat: &T) -> f32 {
mat.det()
}

View file

@ -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
}
}

View file

@ -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<Quat> 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,
}
}
}
}

View file

@ -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<f32> {
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<f32> {
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 #
// ##################################################

File diff suppressed because it is too large Load diff

View file

@ -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"]

View file

@ -0,0 +1,321 @@
use comet_math::Point3;
#[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = 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<f32>,
target: cgmath::Point3<f32>,
up: cgmath::Vector3<f32>,
aspect: f32,
fovy: f32,
znear: f32,
zfar: f32,
}
impl Camera {
pub fn new(
eye: cgmath::Point3<f32>,
target: cgmath::Point3<f32>,
up: cgmath::Vector3<f32>,
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<f32> {
// 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<f32>,
znear: f32,
zfar: f32,
}
impl Projection {
pub fn new<F: Into<Rad<f32>>>(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<f32> {
// 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);
}
}
}*/

View file

@ -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<u32>,
//projection: Projection,
render_pipeline: wgpu::RenderPipeline,
last_frame_time: Instant,
deltatime: f32,
vertex_buffer: wgpu::Buffer,
vertex_data: Vec<Vertex>,
index_buffer: wgpu::Buffer,
index_data: Vec<u16>,
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<LinearRgba>) -> anyhow::Result<Renderer<'a>> {
let vertex_data: Vec<Vertex> = vec![];
let index_data: Vec<u16> = vec![];
let size = PhysicalSize::<u32>::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<Vertex> {
&mut self.vertex_data
}
fn index_data_mut(&mut self) -> &mut Vec<u16> {
&mut self.index_data
}
fn create_rectangle(&self, width: f32, height: f32) -> Vec<Vertex> {
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<Vertex> = 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<Vertex> = 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<u16> = vec![
0, 1, 3,
1, 2, 3
];
self.set_buffers(vertices, indices)
}
pub fn set_texture_atlas(&mut self, paths: Vec<String>) {
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<Vertex>, new_index_buffer: Vec<u16>) {
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<Vertex>, new_index_buffer: &mut Vec<u16>) {
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<Vertex> = &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<u16> = &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<u32> {
self.size
}
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
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(())
}
}

View file

@ -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::<ModelVertex>() 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<Mesh>,
pub materials: Vec<Material>,
}
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<u32>,
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<u32>,
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<u32>,
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<u32>,
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);
}
}
}

View file

@ -0,0 +1,38 @@
// Vertex shader
/*struct CameraUniform {
view_proj: mat4x4<f32>,
};
@group(1) @binding(0) // 1.
var<uniform> camera: CameraUniform;*/
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
}
@vertex
fn vs_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coords = model.tex_coords;
out.clip_position = /*camera.view_proj **/ vec4<f32>(model.position, 1.0);
return out;
}
// Fragment shader
@group(0) @binding(0)
var t_diffuse: texture_2d<f32>;
@group(0) @binding(1)
var s_diffuse: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
}

View file

@ -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<Self> {
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<Self> {
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
}
}

View file

@ -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"]

View file

@ -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<String> {
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<Vec<u8>> {
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<texture::Texture> {
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<model::Model> {
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::<Vec<_>>();
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::<Vec<_>>();
Ok(model::Model { meshes, materials })
}*/

View file

@ -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<String, String>
}
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<String, TextureRegion> {
&self.texture_atlas.textures()
}
pub fn data_files(&self) -> &HashMap<String, String> {
&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<String>) {
self.texture_atlas = TextureAtlas::from_texture_paths(paths)
}
pub async fn load_string(&self, file_name: &str) -> anyhow::Result<String> {
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<Vec<u8>> {
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<texture::Texture> {
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<model::Model> {
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::<Vec<_>>();
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::<Vec<_>>();
Ok(model::Model { meshes, materials })
}*/
}

View file

@ -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<Self> {
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<Self> {
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<DynamicImage> {
// 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
}
}

View file

@ -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<String, TextureRegion>,
}
impl TextureAtlas {
pub fn empty() -> Self {
Self {
atlas: DynamicImage::new(1,1, image::ColorType::Rgb8),
textures: HashMap::new()
}
}
pub fn texture_paths(&self) -> Vec<String> {
self.textures.keys().map(|k| k.to_string()).collect()
}
fn calculate_atlas_width(textures: &Vec<DynamicImage>) -> u32 {
let mut last_height: u32 = textures.get(0).unwrap().height();
let mut widths: Vec<u32> = 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<DynamicImage>) -> 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<String>,
) -> Self {
//let t0 = Instant::now();
let mut textures: Vec<DynamicImage> = Vec::new();
let mut regions: HashMap<String, TextureRegion> = 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<DynamicImage> = sorted_textures.into_iter().map(|t| t.clone()).collect();
let sorted_paths: Vec<String> = 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<String, TextureRegion> {
&self.textures
}
}

View file

@ -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::<Vertex>() 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,
}
]
}
}
}

9
src/lib.rs Normal file
View file

@ -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;

49
src/main.rs Normal file
View file

@ -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::<TestComponent>(),
_ 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::<TestComponent>(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)
;
}