mirror of
https://github.com/lisk77/comet.git
synced 2025-10-23 21:38:50 +00:00
initial commit
This commit is contained in:
commit
6154c72b0e
55 changed files with 9481 additions and 0 deletions
56
Cargo.toml
Normal file
56
Cargo.toml
Normal 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
29
build.rs
Normal 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, ©_options)?;
|
||||
|
||||
Ok(())
|
||||
}
|
23
crates/comet_app/Cargo.toml
Normal file
23
crates/comet_app/Cargo.toml
Normal 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
175
crates/comet_app/src/app.rs
Normal 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));
|
||||
}
|
||||
}
|
2
crates/comet_app/src/lib.rs
Normal file
2
crates/comet_app/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub use app::*;
|
||||
mod app;
|
8
crates/comet_colors/Cargo.toml
Normal file
8
crates/comet_colors/Cargo.toml
Normal 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" }
|
101
crates/comet_colors/src/hsla.rs
Normal file
101
crates/comet_colors/src/hsla.rs
Normal 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()
|
||||
}
|
||||
}
|
99
crates/comet_colors/src/hsva.rs
Normal file
99
crates/comet_colors/src/hsva.rs
Normal 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()
|
||||
}
|
||||
}
|
157
crates/comet_colors/src/hwba.rs
Normal file
157
crates/comet_colors/src/hwba.rs
Normal 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()
|
||||
}
|
||||
|
||||
}
|
143
crates/comet_colors/src/laba.rs
Normal file
143
crates/comet_colors/src/laba.rs
Normal 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()
|
||||
}
|
||||
}
|
93
crates/comet_colors/src/lcha.rs
Normal file
93
crates/comet_colors/src/lcha.rs
Normal 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()
|
||||
}
|
||||
|
||||
}
|
22
crates/comet_colors/src/lib.rs
Normal file
22
crates/comet_colors/src/lib.rs
Normal 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;
|
135
crates/comet_colors/src/linear_rgba.rs
Normal file
135
crates/comet_colors/src/linear_rgba.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
112
crates/comet_colors/src/oklaba.rs
Normal file
112
crates/comet_colors/src/oklaba.rs
Normal 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()
|
||||
}
|
||||
}
|
91
crates/comet_colors/src/oklcha.rs
Normal file
91
crates/comet_colors/src/oklcha.rs
Normal 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()
|
||||
}
|
||||
}
|
359
crates/comet_colors/src/rgba.rs
Normal file
359
crates/comet_colors/src/rgba.rs
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
109
crates/comet_colors/src/xyza.rs
Normal file
109
crates/comet_colors/src/xyza.rs
Normal 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()
|
||||
}
|
||||
}
|
12
crates/comet_ecs/Cargo.toml
Normal file
12
crates/comet_ecs/Cargo.toml
Normal 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"
|
11
crates/comet_ecs/component_derive/Cargo.toml
Normal file
11
crates/comet_ecs/component_derive/Cargo.toml
Normal 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"
|
129
crates/comet_ecs/component_derive/src/lib.rs
Normal file
129
crates/comet_ecs/component_derive/src/lib.rs
Normal 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
|
||||
}
|
182
crates/comet_ecs/src/component.rs
Normal file
182
crates/comet_ecs/src/component.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
|
35
crates/comet_ecs/src/entity.rs
Normal file
35
crates/comet_ecs/src/entity.rs
Normal 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
|
||||
}
|
||||
}
|
40
crates/comet_ecs/src/id.rs
Normal file
40
crates/comet_ecs/src/id.rs
Normal 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
|
||||
}
|
||||
}
|
13
crates/comet_ecs/src/lib.rs
Normal file
13
crates/comet_ecs/src/lib.rs
Normal 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;
|
577
crates/comet_ecs/src/storage.rs
Normal file
577
crates/comet_ecs/src/storage.rs
Normal 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)
|
||||
}
|
||||
}
|
209
crates/comet_ecs/src/world.rs
Normal file
209
crates/comet_ecs/src/world.rs
Normal 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()
|
||||
}
|
||||
}
|
7
crates/comet_input/Cargo.toml
Normal file
7
crates/comet_input/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "comet_input"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
winit = { version = "0.29", features = ["rwh_05"] }
|
32
crates/comet_input/src/keyboard.rs
Normal file
32
crates/comet_input/src/keyboard.rs
Normal 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,
|
||||
}
|
||||
}
|
2
crates/comet_input/src/lib.rs
Normal file
2
crates/comet_input/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod keyboard;
|
||||
pub mod mouse;
|
89
crates/comet_input/src/mouse.rs
Normal file
89
crates/comet_input/src/mouse.rs
Normal 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,
|
||||
}
|
||||
}
|
8
crates/comet_log/Cargo.toml
Normal file
8
crates/comet_log/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "comet_log"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
colored = "2.0"
|
47
crates/comet_log/src/lib.rs
Normal file
47
crates/comet_log/src/lib.rs
Normal 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
|
||||
);
|
||||
};
|
||||
}
|
7
crates/comet_math/Cargo.toml
Normal file
7
crates/comet_math/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "comet_math"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
num-traits = "0.2.19"
|
145
crates/comet_math/src/bezier.rs
Normal file
145
crates/comet_math/src/bezier.rs
Normal 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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
132
crates/comet_math/src/easings.rs
Normal file
132
crates/comet_math/src/easings.rs
Normal 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 }
|
||||
}
|
14
crates/comet_math/src/lib.rs
Normal file
14
crates/comet_math/src/lib.rs
Normal 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;
|
732
crates/comet_math/src/matrix.rs
Normal file
732
crates/comet_math/src/matrix.rs
Normal 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()
|
||||
}
|
62
crates/comet_math/src/point.rs
Normal file
62
crates/comet_math/src/point.rs
Normal 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
|
||||
}
|
||||
}
|
60
crates/comet_math/src/quaternion.rs
Normal file
60
crates/comet_math/src/quaternion.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
177
crates/comet_math/src/utilities.rs
Normal file
177
crates/comet_math/src/utilities.rs
Normal 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 #
|
||||
// ##################################################
|
2640
crates/comet_math/src/vector.rs
Normal file
2640
crates/comet_math/src/vector.rs
Normal file
File diff suppressed because it is too large
Load diff
27
crates/comet_renderer/Cargo.toml
Normal file
27
crates/comet_renderer/Cargo.toml
Normal 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"]
|
321
crates/comet_renderer/src/camera.rs
Normal file
321
crates/comet_renderer/src/camera.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}*/
|
671
crates/comet_renderer/src/lib.rs
Normal file
671
crates/comet_renderer/src/lib.rs
Normal 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(())
|
||||
}
|
||||
}
|
134
crates/comet_renderer/src/model.rs
Normal file
134
crates/comet_renderer/src/model.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
38
crates/comet_renderer/src/shader.wgsl
Normal file
38
crates/comet_renderer/src/shader.wgsl
Normal 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);
|
||||
}
|
254
crates/comet_renderer/src/texture.rs
Normal file
254
crates/comet_renderer/src/texture.rs
Normal 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
|
||||
}
|
||||
}
|
19
crates/comet_resources/Cargo.toml
Normal file
19
crates/comet_resources/Cargo.toml
Normal 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"]
|
146
crates/comet_resources/src/lib.rs
Normal file
146
crates/comet_resources/src/lib.rs
Normal 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 })
|
||||
}*/
|
182
crates/comet_resources/src/resources.rs
Normal file
182
crates/comet_resources/src/resources.rs
Normal 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 })
|
||||
}*/
|
||||
}
|
325
crates/comet_resources/src/texture.rs
Normal file
325
crates/comet_resources/src/texture.rs
Normal 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
|
||||
}
|
||||
}
|
186
crates/comet_resources/src/texture_atlas.rs
Normal file
186
crates/comet_resources/src/texture_atlas.rs
Normal 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
|
||||
}
|
||||
}
|
44
crates/comet_resources/src/vertex.rs
Normal file
44
crates/comet_resources/src/vertex.rs
Normal 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
9
src/lib.rs
Normal 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
49
src/main.rs
Normal 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)
|
||||
;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue