From 0c2457f0c62a360b486bca230b7ea163ebfe1216 Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Sat, 22 Mar 2025 15:58:16 +0300 Subject: [PATCH 1/9] fix: #92 and some cleanups --- Cargo.lock | 25 +++++++++++--- Cargo.toml | 8 ++--- crates/blue_engine_core/Cargo.toml | 14 ++++---- crates/blue_engine_core/src/header.rs | 16 ++------- crates/blue_engine_core/src/render.rs | 22 +----------- crates/blue_engine_core/src/vector.rs | 34 +++++++++++++------ crates/blue_engine_dynamic/Cargo.toml | 4 +-- examples/shapes/square.rs | 48 ++++++++++++++------------- 8 files changed, 83 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39432ca..b09929a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,10 +309,27 @@ dependencies = [ name = "blue_engine" version = "0.6.5" dependencies = [ - "blue_engine_core", + "blue_engine_core 0.6.5", "blue_engine_dynamic", ] +[[package]] +name = "blue_engine_core" +version = "0.6.5" +dependencies = [ + "android_logger", + "bytemuck", + "downcast", + "env_logger", + "image", + "log", + "nalgebra-glm", + "pollster", + "thiserror 2.0.9", + "wgpu", + "winit", +] + [[package]] name = "blue_engine_core" version = "0.6.5" @@ -338,7 +355,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86550ddc6fb6455656ebe19545929b0e458e17a8a764d9c9a636d0c20e7f8665" dependencies = [ - "blue_engine_core", + "blue_engine_core 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2668,9 +2685,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "24.0.1" +version = "24.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47f55718f85c2fa756edffa0e7f0e0a60aba463d1362b57e23123c58f035e4b6" +checksum = "35904fb00ba2d2e0a4d002fcbbb6e1b89b574d272a50e5fc95f6e81cf281c245" dependencies = [ "arrayvec", "bitflags 2.9.0", diff --git a/Cargo.toml b/Cargo.toml index ce4125d..53a150c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ exclude = ["/examples", "/resources"] name = "blue_engine" [features] -default = ["debug", "u16", "dep:blue_engine_core"] +default = ["debug", "dep:blue_engine_core"] # Use the default engine dynamic_link = ["dep:blue_engine_dynamic"] @@ -29,14 +29,12 @@ android_game_activity = [ "blue_engine_core?/android_game_activity", "blue_engine_dynamic?/android_game_activity", ] -# using u16 for indices and others -u16 = ["blue_engine_core?/u16", "blue_engine_dynamic?/u16"] # using u32 for indices and others u32 = ["blue_engine_core?/u32", "blue_engine_dynamic?/u32"] [dependencies] -blue_engine_core = { version = "0.6.5", optional = true } -# blue_engine_core = { path = "crates/blue_engine_core", optional = true } +# blue_engine_core = { version = "0.6.5", optional = true } +blue_engine_core = { path = "crates/blue_engine_core", optional = true } # Wasm does not support dynamic linking. [target.'cfg(not(target_family = "wasm"))'.dependencies] diff --git a/crates/blue_engine_core/Cargo.toml b/crates/blue_engine_core/Cargo.toml index 9acc025..6a3e314 100644 --- a/crates/blue_engine_core/Cargo.toml +++ b/crates/blue_engine_core/Cargo.toml @@ -10,30 +10,28 @@ license = "Apache-2.0" name = "blue_engine_core" [features] -default = ["debug", "u16"] +default = ["debug"] debug = ["dep:env_logger"] android = ["dep:log", "dep:android_logger"] android_native_activity = ["winit/android-native-activity"] android_game_activity = ["winit/android-game-activity"] -# using u16 for indices and others -u16 = [] # using u32 for indices and others u32 = [] [dependencies] -image = { version = "0.25.2" } +image = { version = "0.25" } pollster = "0.4" -winit = { version = "0.30.5", features = ["rwh_06"] } -wgpu = { version = "24.0.1" } +winit = { version = "0.30", features = ["rwh_06"] } +wgpu = { version = "24.0.3" } bytemuck = { version = "1.16", features = ["derive"] } downcast = "0.11" nalgebra-glm = "0.19" +thiserror = "2.0" # debug logs env_logger = { version = "0.11", optional = true } # android log = { version = "0.4", optional = true } android_logger = { version = "0.15.0", optional = true } -thiserror = "2.0.9" [target.'cfg(target_arch = "wasm32")'.dependencies] -wgpu = { version = "24.0.1", features = ["webgl"] } +wgpu = { version = "24.0.3", features = ["webgl"] } diff --git a/crates/blue_engine_core/src/header.rs b/crates/blue_engine_core/src/header.rs index a70c721..6d6f154 100644 --- a/crates/blue_engine_core/src/header.rs +++ b/crates/blue_engine_core/src/header.rs @@ -1,9 +1,3 @@ -/* - * Blue Engine by Elham Aryanpur - * - * The license is Apache-2.0 -*/ - /// re-exports from dependencies that are useful pub mod imports; /// few commonly used uniform buffer structures @@ -11,10 +5,10 @@ pub mod uniform_buffer; pub use imports::*; pub use uniform_buffer::*; -use downcast::{downcast, Any}; +use downcast::{Any, downcast}; /// The uint type used for indices and more -#[cfg(feature = "u16")] +#[cfg(not(feature = "u32"))] pub type UnsignedIntType = u16; #[cfg(feature = "u32")] pub type UnsignedIntType = u32; @@ -682,11 +676,7 @@ pub fn pixel_to_cartesian(value: f32, max: u32) -> f32 { } else if result < max as f32 / 2.0 { } - if result > -1.0 { - result - } else { - -1.0 - } + if result > -1.0 { result } else { -1.0 } } /// A unified way to handle strings diff --git a/crates/blue_engine_core/src/render.rs b/crates/blue_engine_core/src/render.rs index 731afdb..27e002a 100644 --- a/crates/blue_engine_core/src/render.rs +++ b/crates/blue_engine_core/src/render.rs @@ -1,9 +1,3 @@ -/* - * Blue Engine by Elham Aryanpur - * - * The license is same as the one on the root. -*/ - // ? ADD VISIBILITY TAGS FOR DIFFERENT RENDER PASS TO USE AND RENDER ONLY THE OBJECTS THEY NEED use crate::{ @@ -14,10 +8,6 @@ use crate::{ impl Renderer { /// Creates a new renderer. - /// - /// # Arguments - /// * `window` - The window to create the renderer for. - /// * `power_preference` - The power preference to use. pub(crate) async fn new( size: winit::dpi::PhysicalSize, settings: crate::WindowDescriptor, @@ -176,8 +166,6 @@ impl Renderer { } /// Resize the window. - /// # Arguments - /// * `new_size` - The new window size. pub(crate) fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { // check if new_size is non-zero if new_size.width != 0 && new_size.height != 0 { @@ -196,10 +184,6 @@ impl Renderer { } /// Render the scene. Returns the command encoder, the texture view, and the surface texture. - /// - /// # Arguments - /// * `objects` - The object storage. - /// * `camera` - The camera. pub(crate) fn pre_render( &mut self, objects: &ObjectStorage, @@ -303,7 +287,7 @@ impl Renderer { render_pass.set_vertex_buffer(1, i.instance_buffer.slice(..)); render_pass.set_index_buffer( vertex_buffer.index_buffer.slice(..), - #[cfg(feature = "u16")] + #[cfg(not(feature = "u32"))] wgpu::IndexFormat::Uint16, #[cfg(feature = "u32")] wgpu::IndexFormat::Uint32, @@ -331,10 +315,6 @@ impl Renderer { } /// Render the scene. - /// - /// # Arguments - /// * `encoder` - The command encoder. - /// * `frame` - The surface texture. pub(crate) fn render(&mut self, encoder: wgpu::CommandEncoder, frame: wgpu::SurfaceTexture) { // submit will accept anything that implements IntoIter self.queue.submit(std::iter::once(encoder.finish())); diff --git a/crates/blue_engine_core/src/vector.rs b/crates/blue_engine_core/src/vector.rs index d909842..1409939 100644 --- a/crates/blue_engine_core/src/vector.rs +++ b/crates/blue_engine_core/src/vector.rs @@ -1,8 +1,8 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; use crate::{ - header::{Vector2, Vector3}, RotateAmount, RotateAxis, + header::{Vector2, Vector3}, }; use bytemuck::Pod; @@ -97,9 +97,12 @@ impl Vector3 { } /// Returns the cross product of this vector and ``with``. /// - /// This returns a vector perpendicular to both ``self`` and ``with``, which would be the normal vector of the plane defined by the two vectors. - /// As there are two such vectors, in opposite directions, this method returns the vector defined by a right-handed coordinate system. - /// If the two vectors are parallel this returns an empty vector, making it useful for testing if two vectors are parallel. + /// This returns a vector perpendicular to both ``self`` and ``with``, which + /// would be the normal vector of the plane defined by the two vectors. + /// As there are two such vectors, in opposite directions, this method + /// returns the vector defined by a right-handed coordinate system. + /// If the two vectors are parallel this returns an empty vector, making + /// it useful for testing if two vectors are parallel. pub const fn cross(&self, with: Self) -> Self { Self { x: self.y * with.z - self.z * with.y, @@ -107,13 +110,15 @@ impl Vector3 { z: self.x * with.y - self.y * with.x, } } - /// Returns the normalized vector pointing from this vector to ``to``. This is equivalent to using ``(b - a).normalized()``. + /// Returns the normalized vector pointing from this vector to ``to``. This + /// is equivalent to using ``(b - a).normalized()``. pub fn direction_to(&self, to: Self) -> Self { (to - *self).normalize() } /// Returns the squared distance between this vector and ``to``. /// - /// This method runs faster than [``Vector3::distance_to``], so prefer it if you need to compare vectors or need the squared distance for some formula. + /// This method runs faster than [``Vector3::distance_to``], so prefer it if + /// you need to compare vectors or need the squared distance for some formula. pub fn distance_squared_to(&self, to: Self) -> f32 { (*self - to).length_squared() } @@ -121,11 +126,16 @@ impl Vector3 { pub fn distance_to(&self, to: Self) -> f32 { (*self - to).length() } - /// Returns the dot product of this vector and ``with``. This can be used to compare the angle between two vectors. For example, this can be used to determine whether an enemy is facing the player. + /// Returns the dot product of this vector and ``with``. This can be used to + /// compare the angle between two vectors. For example, this can be used to + /// determine whether an enemy is facing the player. /// - /// The dot product will be ``0`` for a right angle (90 degrees), greater than 0 for angles narrower than 90 degrees and lower than 0 for angles wider than 90 degrees. + /// The dot product will be ``0`` for a right angle (90 degrees), greater than + /// 0 for angles narrower than 90 degrees and lower than 0 for angles wider than 90 degrees. /// - /// When using unit (normalized) vectors, the result will always be between ``-1.0`` (180 degree angle) when the vectors are facing opposite directions, and ``1.0`` (0 degree angle) when the vectors are aligned. + /// When using unit (normalized) vectors, the result will always be between + /// ``-1.0`` (180 degree angle) when the vectors are facing opposite directions, + /// and ``1.0`` (0 degree angle) when the vectors are aligned. /// ///> Note: ``a.dot(b)`` is equivalent to ``b.dot(a)``. pub fn dot(&self, with: Self) -> f32 { @@ -165,7 +175,8 @@ impl Vector3 { } /// Returns the squared length (squared magnitude) of this vector. /// - /// This method runs faster than [`Vector3::length`], so prefer it if you need to compare vectors or need the squared distance for some formula. + /// This method runs faster than [`Vector3::length`], so prefer it if you + /// need to compare vectors or need the squared distance for some formula. pub const fn length_squared(&self) -> f32 { self.x * self.x + self.y * self.y + self.z * self.z } @@ -252,7 +263,8 @@ impl Vector3 { z: self.z / length, } } - /// Returns a new vector with all components rounded to the nearest integer, with halfway cases rounded away from zero. + /// Returns a new vector with all components rounded to the nearest + /// integer, with halfway cases rounded away from zero. pub fn round(&self) -> Self { Self { x: self.x.round(), diff --git a/crates/blue_engine_dynamic/Cargo.toml b/crates/blue_engine_dynamic/Cargo.toml index 9aa3492..f344974 100644 --- a/crates/blue_engine_dynamic/Cargo.toml +++ b/crates/blue_engine_dynamic/Cargo.toml @@ -11,13 +11,11 @@ name = "blue_engine_dynamic" crate-type = ["dylib"] [features] -default = ["debug", "u16"] +default = ["debug"] debug = ["blue_engine_core/debug"] android = ["blue_engine_core/android"] android_native_activity = ["blue_engine_core/android_native_activity"] android_game_activity = ["blue_engine_core/android_game_activity"] -# using u16 for indices and others -u16 = ["blue_engine_core/u16"] # using u32 for indices and others u32 = ["blue_engine_core/u32"] diff --git a/examples/shapes/square.rs b/examples/shapes/square.rs index 899d0a7..a428818 100644 --- a/examples/shapes/square.rs +++ b/examples/shapes/square.rs @@ -7,35 +7,37 @@ */ use blue_engine::{ - header::{Engine, ObjectSettings, Vertex}, StringBuffer, Vector2, Vector3, + header::{Engine, ObjectSettings, Vertex}, }; pub fn square(name: impl StringBuffer, engine: &mut Engine) { + let vertices = vec![ + Vertex { + position: Vector3::new(1.0, 1.0, 0.0), + uv: Vector2::new(1.0, 1.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(1.0, -1.0, 0.0), + uv: Vector2::new(1.0, 0.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(-1.0, -1.0, 0.0), + uv: Vector2::new(0.0, 1.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(-1.0, 1.0, 0.0), + uv: Vector2::new(0.0, 0.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + ]; + engine.objects.new_object( name, - vec![ - Vertex { - position: Vector3::new(1.0, 1.0, 0.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(1.0, -1.0, 0.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(-1.0, -1.0, 0.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(-1.0, 1.0, 0.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - ], + vertices, vec![2, 1, 0, 2, 0, 3], ObjectSettings { camera_effect: None, From 2e58c8df62e4a89fc46e6936628579760f5fcab8 Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Sat, 22 Mar 2025 16:47:01 +0300 Subject: [PATCH 2/9] chore: renamed header to prelude --- Cargo.lock | 38 +++++++++ Cargo.toml | 2 +- README.md | 12 +-- crates/blue_engine_core/Cargo.toml | 2 + crates/blue_engine_core/src/definition.rs | 4 +- crates/blue_engine_core/src/lib.rs | 6 +- crates/blue_engine_core/src/objects.rs | 40 ++++----- .../src/{header.rs => prelude.rs} | 2 +- .../src/{header => prelude}/imports.rs | 0 .../src/{header => prelude}/uniform_buffer.rs | 0 .../src/primitive_shapes/two_dimensions.rs | 2 +- crates/blue_engine_core/src/render.rs | 81 +++++++------------ crates/blue_engine_core/src/utils/camera.rs | 2 +- .../src/utils/default_resources.rs | 4 +- crates/blue_engine_core/src/vector.rs | 2 +- crates/blue_engine_core/src/window.rs | 2 +- crates/docs/main.md | 2 +- examples/camera/rotate_around.rs | 2 +- examples/shapes/cube.rs | 2 +- examples/shapes/square.rs | 2 +- examples/shapes/triangle.rs | 2 +- examples/utils/instancing.rs | 2 +- examples/utils/signals.rs | 2 +- 23 files changed, 107 insertions(+), 106 deletions(-) rename crates/blue_engine_core/src/{header.rs => prelude.rs} (99%) rename crates/blue_engine_core/src/{header => prelude}/imports.rs (100%) rename crates/blue_engine_core/src/{header => prelude}/uniform_buffer.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index b09929a..46379db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,6 +319,7 @@ version = "0.6.5" dependencies = [ "android_logger", "bytemuck", + "crabtime", "downcast", "env_logger", "image", @@ -554,6 +555,28 @@ dependencies = [ "libc", ] +[[package]] +name = "crabtime" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495b2c0e1afa3118b42b183d52130ed81b8811e92cc0c7a081a9180eff33682e" +dependencies = [ + "crabtime-internal", +] + +[[package]] +name = "crabtime-internal" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5201a50958f6a2adc98079508bccaba28b296fcef454e7546a8c645c0069b0de" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn", + "toml", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -2009,6 +2032,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.42" @@ -2071,6 +2103,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.217" diff --git a/Cargo.toml b/Cargo.toml index 53a150c..b4acc8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.6.5" authors = ["Elham Aryanpur "] edition = "2024" description = "General-Purpose, Easy-to-use, Fast, and Portable graphics engine" -documentation = "https://aryanpurtech.github.io/BlueEngineDocs/" +documentation = "https://docs.rs/blue_engine" repository = "https://github.com/AryanpurTech/BlueEngine" keywords = ["gamedev", "graphics", "3D", "2D", "rendering"] categories = ["game-development", "gui", "graphics", "rendering"] diff --git a/README.md b/README.md index decd688..faceada 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Hello World: ```rust use blue_engine::{ - header::{ Engine, ObjectSettings }, + prelude::{ Engine, ObjectSettings }, primitive_shapes::triangle }; @@ -33,12 +33,12 @@ fn main() { } ``` -* [WIP] [Guide](https://aryanpurtech.github.io/BlueEngineDocs/) +- [WIP] [Guide](https://aryanpurtech.github.io/BlueEngineDocs/) -* Check out the [examples](https://github.com/AryanpurTech/BlueEngine/tree/master/examples) folder to get a sense of how things are done +- Check out the [examples](https://github.com/AryanpurTech/BlueEngine/tree/master/examples) folder to get a sense of how things are done -* Check out the [utilities library](https://github.com/AryanpurTech/BlueEngineUtilities) for extra functionality with the engine +- Check out the [utilities library](https://github.com/AryanpurTech/BlueEngineUtilities) for extra functionality with the engine -*the credits to the image on top: NotPB* +_the credits to the image on top: NotPB_ -*the development might seem slow sometimes, its due to multiple repositories being handled and due to my education taking a large chunk of my time. The project isn't dead, just slow.* +_the development might seem slow sometimes, its due to multiple repositories being handled and due to my education taking a large chunk of my time. The project isn't dead, just slow._ diff --git a/crates/blue_engine_core/Cargo.toml b/crates/blue_engine_core/Cargo.toml index 6a3e314..ea9b13a 100644 --- a/crates/blue_engine_core/Cargo.toml +++ b/crates/blue_engine_core/Cargo.toml @@ -27,6 +27,8 @@ bytemuck = { version = "1.16", features = ["derive"] } downcast = "0.11" nalgebra-glm = "0.19" thiserror = "2.0" +crabtime = "1.1.1" + # debug logs env_logger = { version = "0.11", optional = true } # android diff --git a/crates/blue_engine_core/src/definition.rs b/crates/blue_engine_core/src/definition.rs index c813026..50294b7 100644 --- a/crates/blue_engine_core/src/definition.rs +++ b/crates/blue_engine_core/src/definition.rs @@ -9,13 +9,13 @@ use wgpu::{BindGroupLayout, Sampler, Texture, TextureView, util::DeviceExt}; use crate::{ InstanceRaw, UnsignedIntType, - header::{ + prelude::{ Pipeline, PipelineData, ShaderSettings, Shaders, StringBuffer, TextureData, TextureMode, Textures, UniformBuffers, Vertex, VertexBuffers, }, }; -impl crate::header::Renderer { +impl crate::prelude::Renderer { /// Creates a new render pipeline. Could be thought of as like materials in game engines. pub fn build_pipeline( &mut self, diff --git a/crates/blue_engine_core/src/lib.rs b/crates/blue_engine_core/src/lib.rs index 00a760c..b701209 100644 --- a/crates/blue_engine_core/src/lib.rs +++ b/crates/blue_engine_core/src/lib.rs @@ -12,10 +12,10 @@ pub(crate) mod definition; /// interal error definitions of the engine pub mod error; -/// contains all the declarations such as structs, exports, enums, ... -pub mod header; /// contains the definition for Object type, which is a type that make it easier to manage data for rendering. pub mod objects; +/// contains all the declarations such as structs, exports, enums, ... +pub mod prelude; /// contains definition for some 2D and 3D shapes. They are basic shapes and /// can be used as examples of how to create your own content. pub mod primitive_shapes; @@ -29,4 +29,4 @@ pub mod vector; /// contains definition for creation of window and instance creation. pub mod window; #[doc(inline)] -pub use crate::header::*; +pub use crate::prelude::*; diff --git a/crates/blue_engine_core/src/objects.rs b/crates/blue_engine_core/src/objects.rs index dbe51c3..11f71ff 100644 --- a/crates/blue_engine_core/src/objects.rs +++ b/crates/blue_engine_core/src/objects.rs @@ -4,9 +4,9 @@ * The license is same as the one on the root. */ -use crate::header::{ - glm, pixel_to_cartesian, uniform_type, Instance, InstanceRaw, Object, ObjectSettings, Pipeline, - PipelineData, Renderer, RotateAxis, TextureData, Textures, Vertex, +use crate::prelude::{ + Instance, InstanceRaw, Object, ObjectSettings, Pipeline, PipelineData, Renderer, RotateAxis, + TextureData, Textures, Vertex, glm, pixel_to_cartesian, uniform_type, }; use crate::uniform_type::{Array4, Matrix}; use crate::utils::default_resources::{DEFAULT_MATRIX_4, DEFAULT_SHADER, DEFAULT_TEXTURE}; @@ -55,8 +55,8 @@ impl Renderer { let texture = self.build_texture( "Default Texture", TextureData::Bytes(DEFAULT_TEXTURE.to_vec()), - crate::header::TextureMode::Clamp, - //crate::header::TextureFormat::PNG + crate::prelude::TextureMode::Clamp, + //crate::prelude::TextureFormat::PNG )?; let instance = Instance::new([0f32, 0f32, 0f32], [0f32, 0f32, 0f32], [1f32, 1f32, 1f32]); @@ -182,11 +182,7 @@ impl Object { let difference_in_width = if self.size.x != 0.0 && width != 0.0 { let a = pixel_to_cartesian(width, window_size.width); let b = pixel_to_cartesian(self.size.x, window_size.width); - if a != 0f32 && b != 0f32 { - a / b - } else { - b - } + if a != 0f32 && b != 0f32 { a / b } else { b } } else { 0.0 }; @@ -194,22 +190,14 @@ impl Object { let difference_in_height = if self.size.y != 0.0 && height != 0.0 { let a = pixel_to_cartesian(height, window_size.height); let b = pixel_to_cartesian(self.size.y, window_size.height); - if a != 0f32 && b != 0f32 { - a / b - } else { - b - } + if a != 0f32 && b != 0f32 { a / b } else { b } } else { 0.0 }; let difference_in_depth = if self.size.z != 0.0 && depth != 0.0 { let a = pixel_to_cartesian(depth, window_size.width); let b = pixel_to_cartesian(self.size.z, window_size.width); - if a != 0f32 && b != 0f32 { - a / b - } else { - b - } + if a != 0f32 && b != 0f32 { a / b } else { b } } else { 0.0 }; @@ -255,22 +243,22 @@ impl Object { // angle.cos(), -angle.sin() // angle.sin(), angle.cos() let mut result = nalgebra_glm::Mat4::identity(); - + result[(axis_from, axis_from)] = angle.cos() as f32; result[(axis_from, axis_into)] = -angle.sin() as f32; result[(axis_into, axis_from)] = angle.sin() as f32; result[(axis_into, axis_into)] = angle.cos() as f32; - + result } fn rotation_full(euler_angles: nalgebra_glm::Vec3) -> nalgebra_glm::Mat4 { const X: usize = 0; const Y: usize = 1; const Z: usize = 2; - + Self::rotation_single_axis(euler_angles[2], X, Y) * // Rotation around Z (rotation of X into Y) Self::rotation_single_axis(euler_angles[1], Z, X) * // Rotation around Y (rotation of Z into X) - Self::rotation_single_axis(euler_angles[0], Y, Z) // Rotation around X (rotation of Y into Z) + Self::rotation_single_axis(euler_angles[0], Y, Z) // Rotation around X (rotation of Y into Z) } /// Sets the rotation of the object in the axis you specify @@ -348,11 +336,11 @@ impl Object { /// Sets the position of the object in 3D space relative to the window pub fn set_position(&mut self, new_pos: impl Into) -> &mut Self { let new_pos = new_pos.into(); - + self.position.x = new_pos.x; self.position.y = new_pos.y; self.position.z = new_pos.z; - + // self.set_translation((self.position - new_pos) * -1f32); self.update_position_matrix(); self.inverse_matrices(); diff --git a/crates/blue_engine_core/src/header.rs b/crates/blue_engine_core/src/prelude.rs similarity index 99% rename from crates/blue_engine_core/src/header.rs rename to crates/blue_engine_core/src/prelude.rs index 6d6f154..57d6db6 100644 --- a/crates/blue_engine_core/src/header.rs +++ b/crates/blue_engine_core/src/prelude.rs @@ -174,7 +174,7 @@ unsafe impl Sync for ObjectSettings {} /// /// To start using the Blue Engine, you can start by creating a new Engine like follows: /// ``` -/// use blue_engine::header::{Engine, WindowDescriptor}; +/// use blue_engine::prelude::{Engine, WindowDescriptor}; /// /// fn main() { /// let engine = Engine::new().expect("Couldn't create the engine"); diff --git a/crates/blue_engine_core/src/header/imports.rs b/crates/blue_engine_core/src/prelude/imports.rs similarity index 100% rename from crates/blue_engine_core/src/header/imports.rs rename to crates/blue_engine_core/src/prelude/imports.rs diff --git a/crates/blue_engine_core/src/header/uniform_buffer.rs b/crates/blue_engine_core/src/prelude/uniform_buffer.rs similarity index 100% rename from crates/blue_engine_core/src/header/uniform_buffer.rs rename to crates/blue_engine_core/src/prelude/uniform_buffer.rs diff --git a/crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs b/crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs index 7f0ee0c..855ce53 100644 --- a/crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs +++ b/crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs @@ -9,8 +9,8 @@ */ use crate::{ - header::{ObjectSettings, Vertex}, ObjectStorage, Renderer, StringBuffer, Vector2, Vector3, + prelude::{ObjectSettings, Vertex}, }; /// Creates a 2D triangle diff --git a/crates/blue_engine_core/src/render.rs b/crates/blue_engine_core/src/render.rs index 27e002a..e398075 100644 --- a/crates/blue_engine_core/src/render.rs +++ b/crates/blue_engine_core/src/render.rs @@ -2,7 +2,7 @@ use crate::{ CameraContainer, ObjectStorage, PipelineData, - header::{Renderer, ShaderSettings, TextureData, uniform_type}, + prelude::{Renderer, ShaderSettings, TextureData, uniform_type}, utils::default_resources::{DEFAULT_COLOR, DEFAULT_MATRIX_4, DEFAULT_SHADER, DEFAULT_TEXTURE}, }; @@ -138,8 +138,8 @@ impl Renderer { if let Ok(default_texture) = self.build_texture( "Default Texture", TextureData::Bytes(DEFAULT_TEXTURE.to_vec()), - crate::header::TextureMode::Clamp, - //crate::header::TextureFormat::PNG + crate::prelude::TextureMode::Clamp, + //crate::prelude::TextureFormat::PNG ) { let default_uniform = self.build_uniform_buffer(&vec![ self.build_uniform_buffer_part("Transformation Matrix", DEFAULT_MATRIX_4), @@ -328,61 +328,34 @@ impl Renderer { } // =========================== Extract Pipeline Data =========================== -// I couldn't make them into one function, so here they are, four of them - -/// Get the pipeline vertex buffer. -fn get_pipeline_vertex_buffer<'a>( - data: &'a PipelineData, - objects: &'a ObjectStorage, -) -> Option<&'a crate::VertexBuffers> { - match data { - PipelineData::Copy(object_id) => { - let data = objects.get(object_id.as_str()); - if let Some(data) = data { - get_pipeline_vertex_buffer(&data.pipeline.vertex_buffer, objects) - } else { - None - } - } - PipelineData::Data(data) => Some(data), - } -} - -/// Get the pipeline shader. -fn get_pipeline_shader<'a>( - data: &'a PipelineData, - objects: &'a ObjectStorage, -) -> Option<&'a crate::Shaders> { - match data { - PipelineData::Copy(object_id) => { - let data = objects.get(object_id.as_str()); - if let Some(data) = data { - get_pipeline_shader(&data.pipeline.shader, objects) - } else { - None +macro_rules! gen_pipeline { + ($function_name:ident, $buffer_type:ty, $buffer_field:ident) => { + fn $function_name<'a>( + data: &'a PipelineData<$buffer_type>, + objects: &'a ObjectStorage, + ) -> Option<&'a $buffer_type> { + match data { + PipelineData::Copy(object_id) => { + let data = objects.get(object_id.as_str()); + if let Some(data) = data { + $function_name(&data.pipeline.$buffer_field, objects) + } else { + None + } + } + PipelineData::Data(data) => Some(data), } } - PipelineData::Data(data) => Some(data), - } + }; } -/// Get the pipeline texture. -fn get_pipeline_texture<'a>( - data: &'a PipelineData, - objects: &'a ObjectStorage, -) -> Option<&'a crate::Textures> { - match data { - PipelineData::Copy(object_id) => { - let data = objects.get(object_id.as_str()); - if let Some(data) = data { - get_pipeline_texture(&data.pipeline.texture, objects) - } else { - None - } - } - PipelineData::Data(data) => Some(data), - } -} +gen_pipeline!( + get_pipeline_vertex_buffer, + crate::VertexBuffers, + vertex_buffer +); +gen_pipeline!(get_pipeline_shader, crate::Shaders, shader); +gen_pipeline!(get_pipeline_texture, crate::Textures, texture); /// Get the pipeline uniform_buffer. fn get_pipeline_uniform_buffer<'a>( diff --git a/crates/blue_engine_core/src/utils/camera.rs b/crates/blue_engine_core/src/utils/camera.rs index fa5a02e..37afb23 100644 --- a/crates/blue_engine_core/src/utils/camera.rs +++ b/crates/blue_engine_core/src/utils/camera.rs @@ -5,8 +5,8 @@ */ use crate::{ - header::{uniform_type::Matrix, Camera, Renderer, Vector3}, CameraContainer, Projection, + prelude::{Camera, Renderer, Vector3, uniform_type::Matrix}, }; use winit::dpi::PhysicalSize; diff --git a/crates/blue_engine_core/src/utils/default_resources.rs b/crates/blue_engine_core/src/utils/default_resources.rs index 93741ae..ab69700 100644 --- a/crates/blue_engine_core/src/utils/default_resources.rs +++ b/crates/blue_engine_core/src/utils/default_resources.rs @@ -39,8 +39,8 @@ pub const DEFAULT_TEXTURE: &[u8] = &[ pub const DEFAULT_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 0.0]; /// A default matrix 4x4 used in the engine -pub const DEFAULT_MATRIX_4: crate::header::uniform_type::Matrix = - crate::header::uniform_type::Matrix { +pub const DEFAULT_MATRIX_4: crate::prelude::uniform_type::Matrix = + crate::prelude::uniform_type::Matrix { data: [ [1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], diff --git a/crates/blue_engine_core/src/vector.rs b/crates/blue_engine_core/src/vector.rs index 1409939..3d5c3bf 100644 --- a/crates/blue_engine_core/src/vector.rs +++ b/crates/blue_engine_core/src/vector.rs @@ -2,7 +2,7 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Neg, Sub, use crate::{ RotateAmount, RotateAxis, - header::{Vector2, Vector3}, + prelude::{Vector2, Vector3}, }; use bytemuck::Pod; diff --git a/crates/blue_engine_core/src/window.rs b/crates/blue_engine_core/src/window.rs index 28b36cf..80e30c7 100644 --- a/crates/blue_engine_core/src/window.rs +++ b/crates/blue_engine_core/src/window.rs @@ -6,7 +6,7 @@ use crate::{ CameraContainer, ObjectStorage, Window, - header::{Engine, Renderer, WindowDescriptor}, + prelude::{Engine, Renderer, WindowDescriptor}, }; use winit::{ diff --git a/crates/docs/main.md b/crates/docs/main.md index f5a1b3c..0bba811 100644 --- a/crates/docs/main.md +++ b/crates/docs/main.md @@ -14,7 +14,7 @@ A basic program in Blue Engine is as follow: ```rust use blue_engine::{ - header::{ Engine, ObjectSettings }, + prelude::{ Engine, ObjectSettings }, primitive_shapes::triangle }; diff --git a/examples/camera/rotate_around.rs b/examples/camera/rotate_around.rs index 1e7cc83..6b4951b 100644 --- a/examples/camera/rotate_around.rs +++ b/examples/camera/rotate_around.rs @@ -5,7 +5,7 @@ */ use blue_engine::{ - header::{Engine, ObjectSettings, ShaderSettings}, + prelude::{Engine, ObjectSettings, ShaderSettings}, primitive_shapes::square, Vector3, }; diff --git a/examples/shapes/cube.rs b/examples/shapes/cube.rs index 3f2029e..3048a9c 100644 --- a/examples/shapes/cube.rs +++ b/examples/shapes/cube.rs @@ -4,7 +4,7 @@ * The license is same as the one on the root. */ -use blue_engine::{header::Engine, primitive_shapes::cube}; +use blue_engine::{prelude::Engine, primitive_shapes::cube}; fn main() { let mut engine = Engine::new().expect("win"); diff --git a/examples/shapes/square.rs b/examples/shapes/square.rs index a428818..da0e585 100644 --- a/examples/shapes/square.rs +++ b/examples/shapes/square.rs @@ -8,7 +8,7 @@ use blue_engine::{ StringBuffer, Vector2, Vector3, - header::{Engine, ObjectSettings, Vertex}, + prelude::{Engine, ObjectSettings, Vertex}, }; pub fn square(name: impl StringBuffer, engine: &mut Engine) { diff --git a/examples/shapes/triangle.rs b/examples/shapes/triangle.rs index dcb42c7..dcb333b 100644 --- a/examples/shapes/triangle.rs +++ b/examples/shapes/triangle.rs @@ -7,7 +7,7 @@ */ use blue_engine::{ - header::{Engine, ObjectSettings}, + prelude::{Engine, ObjectSettings}, primitive_shapes::triangle, }; diff --git a/examples/utils/instancing.rs b/examples/utils/instancing.rs index 4eead37..03e6f27 100644 --- a/examples/utils/instancing.rs +++ b/examples/utils/instancing.rs @@ -7,7 +7,7 @@ */ use blue_engine::{ - header::{Engine, ObjectSettings}, + prelude::{Engine, ObjectSettings}, primitive_shapes::triangle, Instance, Vector3, }; diff --git a/examples/utils/signals.rs b/examples/utils/signals.rs index b5f6525..ba45993 100644 --- a/examples/utils/signals.rs +++ b/examples/utils/signals.rs @@ -5,7 +5,7 @@ */ use blue_engine::{ - header::{Engine, ObjectSettings}, + prelude::{Engine, ObjectSettings}, primitive_shapes::triangle, Signal, }; From 3f95a40f631782da72fa93533181091332fd5112 Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Sun, 23 Mar 2025 15:10:23 +0300 Subject: [PATCH 3/9] chore: moved prelude, utils and primitive shapes to a cleaner format --- crates/blue_engine_core/src/lib.rs | 6 +- .../src/{prelude.rs => prelude/mod.rs} | 3 +- .../blue_engine_core/src/primitive_shapes.rs | 6 - .../src/primitive_shapes/two_dimensions.rs | 122 ------------------ crates/blue_engine_core/src/utils.rs | 16 --- .../src/utils/current_input.rs | 2 + crates/blue_engine_core/src/utils/mod.rs | 13 ++ .../primitive_shapes.rs} | 118 ++++++++++++++++- .../src/{ => utils}/vector.rs | 0 examples/camera/rotate_around.rs | 2 +- 10 files changed, 134 insertions(+), 154 deletions(-) rename crates/blue_engine_core/src/{prelude.rs => prelude/mod.rs} (99%) delete mode 100644 crates/blue_engine_core/src/primitive_shapes.rs delete mode 100644 crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs delete mode 100644 crates/blue_engine_core/src/utils.rs create mode 100644 crates/blue_engine_core/src/utils/mod.rs rename crates/blue_engine_core/src/{primitive_shapes/three_dimensions.rs => utils/primitive_shapes.rs} (68%) rename crates/blue_engine_core/src/{ => utils}/vector.rs (100%) diff --git a/crates/blue_engine_core/src/lib.rs b/crates/blue_engine_core/src/lib.rs index b701209..e8bca53 100644 --- a/crates/blue_engine_core/src/lib.rs +++ b/crates/blue_engine_core/src/lib.rs @@ -16,16 +16,12 @@ pub mod error; pub mod objects; /// contains all the declarations such as structs, exports, enums, ... pub mod prelude; -/// contains definition for some 2D and 3D shapes. They are basic shapes and -/// can be used as examples of how to create your own content. -pub mod primitive_shapes; /// contains definition for rendering part of the engine. pub mod render; /// Utilities for the engine (soon moving to it's own /// [crate](https://github.com/AryanpurTech/BlueEngineUtilities)). pub mod utils; -/// contains definition for 2D and 3D vectors. -pub mod vector; +pub use utils::*; /// contains definition for creation of window and instance creation. pub mod window; #[doc(inline)] diff --git a/crates/blue_engine_core/src/prelude.rs b/crates/blue_engine_core/src/prelude/mod.rs similarity index 99% rename from crates/blue_engine_core/src/prelude.rs rename to crates/blue_engine_core/src/prelude/mod.rs index 57d6db6..8420dbe 100644 --- a/crates/blue_engine_core/src/prelude.rs +++ b/crates/blue_engine_core/src/prelude/mod.rs @@ -2,11 +2,10 @@ pub mod imports; /// few commonly used uniform buffer structures pub mod uniform_buffer; +use downcast::{Any, downcast}; pub use imports::*; pub use uniform_buffer::*; -use downcast::{Any, downcast}; - /// The uint type used for indices and more #[cfg(not(feature = "u32"))] pub type UnsignedIntType = u16; diff --git a/crates/blue_engine_core/src/primitive_shapes.rs b/crates/blue_engine_core/src/primitive_shapes.rs deleted file mode 100644 index 762325f..0000000 --- a/crates/blue_engine_core/src/primitive_shapes.rs +++ /dev/null @@ -1,6 +0,0 @@ -/// Predefined 3D shapes -pub mod three_dimensions; -/// Predefined 2D shapes -pub mod two_dimensions; -pub use three_dimensions::*; -pub use two_dimensions::*; diff --git a/crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs b/crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs deleted file mode 100644 index 855ce53..0000000 --- a/crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Blue Engine by Elham Aryanpur - * - * The license is same as the one on the root. - */ - -/* - * For the sake of example we never use Vector3::default() or Vector3::x_axis() or any axis. - */ - -use crate::{ - ObjectStorage, Renderer, StringBuffer, Vector2, Vector3, - prelude::{ObjectSettings, Vertex}, -}; - -/// Creates a 2D triangle -pub fn triangle( - name: impl StringBuffer, - settings: ObjectSettings, - renderer: &mut Renderer, - objects: &mut ObjectStorage, -) { - objects.new_object( - name.clone(), - vec![ - Vertex { - position: Vector3::new(0.0, 1.0, 0.0), - uv: Vector2::new(0.5, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(-1.0, -1.0, 0.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(1.0, -1.0, 0.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - ], - vec![0, 1, 2], - settings, - renderer, - ); -} - -/// Creates a 2D square -pub fn square( - name: impl StringBuffer, - settings: ObjectSettings, - renderer: &mut Renderer, - objects: &mut ObjectStorage, -) { - objects.new_object( - name.clone(), - vec![ - Vertex { - position: Vector3::new(1.0, 1.0, 0.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(1.0, -1.0, 0.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(-1.0, -1.0, 0.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(-1.0, 1.0, 0.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - ], - vec![2, 1, 0, 2, 0, 3], - settings, - renderer, - ); -} - -/// Create a 2D rectangle based on a width and height -pub fn rectangle( - width: f32, - height: f32, - name: impl StringBuffer, - settings: ObjectSettings, - renderer: &mut Renderer, - objects: &mut ObjectStorage, -) { - objects.new_object( - name.clone(), - vec![ - Vertex { - position: Vector3::new(width / 2.0, height / 2.0, 0.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(width / 2.0, -height / 2.0, 0.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(-width / 2.0, -height / 2.0, 0.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - Vertex { - position: Vector3::new(-width / 2.0, height / 2.0, 0.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), - }, - ], - vec![2, 1, 0, 2, 0, 3], - settings, - renderer, - ); -} diff --git a/crates/blue_engine_core/src/utils.rs b/crates/blue_engine_core/src/utils.rs deleted file mode 100644 index 3e3badb..0000000 --- a/crates/blue_engine_core/src/utils.rs +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Blue Engine by Elham Aryanpur - * - * The license is same as the one on the root. -*/ - -/// The camera utilities -pub mod camera; -/// default resources used in the engine -pub mod default_resources; - -// taken from -- https://github.com/rukai/winit_input_helper -/// input helper -pub mod winit_input_helper; - -mod current_input; diff --git a/crates/blue_engine_core/src/utils/current_input.rs b/crates/blue_engine_core/src/utils/current_input.rs index 11f4b10..ea0c0c7 100644 --- a/crates/blue_engine_core/src/utils/current_input.rs +++ b/crates/blue_engine_core/src/utils/current_input.rs @@ -1,3 +1,5 @@ +// taken from -- https://github.com/rukai/winit_input_helper + use winit::event::{DeviceEvent, ElementState, MouseButton, MouseScrollDelta, WindowEvent}; use winit::keyboard::{Key, PhysicalKey}; diff --git a/crates/blue_engine_core/src/utils/mod.rs b/crates/blue_engine_core/src/utils/mod.rs new file mode 100644 index 0000000..a1f2495 --- /dev/null +++ b/crates/blue_engine_core/src/utils/mod.rs @@ -0,0 +1,13 @@ +/// The camera utilities +pub mod camera; +/// Input wrapping +mod current_input; +/// default resources used in the engine +pub mod default_resources; +/// contains definition for some 2D and 3D shapes. They are basic shapes and +/// can be used as examples of how to create your own content. +pub mod primitive_shapes; +/// contains definition for 2D and 3D vectors. +pub mod vector; +/// input helper +pub mod winit_input_helper; diff --git a/crates/blue_engine_core/src/primitive_shapes/three_dimensions.rs b/crates/blue_engine_core/src/utils/primitive_shapes.rs similarity index 68% rename from crates/blue_engine_core/src/primitive_shapes/three_dimensions.rs rename to crates/blue_engine_core/src/utils/primitive_shapes.rs index 69cb3bc..10fa9bb 100644 --- a/crates/blue_engine_core/src/primitive_shapes/three_dimensions.rs +++ b/crates/blue_engine_core/src/utils/primitive_shapes.rs @@ -1,9 +1,123 @@ +/* + * For the sake of example we never use Vector3::default() or Vector3::x_axis() or any axis. + */ + use crate::{ - ObjectSettings, ObjectStorage, Renderer, StringBuffer, UnsignedIntType, Vector2, Vector3, - Vertex, + ObjectStorage, Renderer, StringBuffer, Vector2, Vector3, + prelude::{ObjectSettings, UnsignedIntType, Vertex}, }; use std::f32::consts::PI; +/// Creates a 2D triangle +pub fn triangle( + name: impl StringBuffer, + settings: ObjectSettings, + renderer: &mut Renderer, + objects: &mut ObjectStorage, +) { + objects.new_object( + name.clone(), + vec![ + Vertex { + position: Vector3::new(0.0, 1.0, 0.0), + uv: Vector2::new(0.5, 0.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(-1.0, -1.0, 0.0), + uv: Vector2::new(0.0, 1.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(1.0, -1.0, 0.0), + uv: Vector2::new(1.0, 1.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + ], + vec![0, 1, 2], + settings, + renderer, + ); +} + +/// Creates a 2D square +pub fn square( + name: impl StringBuffer, + settings: ObjectSettings, + renderer: &mut Renderer, + objects: &mut ObjectStorage, +) { + objects.new_object( + name.clone(), + vec![ + Vertex { + position: Vector3::new(1.0, 1.0, 0.0), + uv: Vector2::new(1.0, 0.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(1.0, -1.0, 0.0), + uv: Vector2::new(1.0, 1.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(-1.0, -1.0, 0.0), + uv: Vector2::new(0.0, 1.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(-1.0, 1.0, 0.0), + uv: Vector2::new(0.0, 0.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + ], + vec![2, 1, 0, 2, 0, 3], + settings, + renderer, + ); +} + +/// Create a 2D rectangle based on a width and height +pub fn rectangle( + width: f32, + height: f32, + name: impl StringBuffer, + settings: ObjectSettings, + renderer: &mut Renderer, + objects: &mut ObjectStorage, +) { + objects.new_object( + name.clone(), + vec![ + Vertex { + position: Vector3::new(width / 2.0, height / 2.0, 0.0), + uv: Vector2::new(1.0, 0.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(width / 2.0, -height / 2.0, 0.0), + uv: Vector2::new(1.0, 1.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(-width / 2.0, -height / 2.0, 0.0), + uv: Vector2::new(0.0, 1.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + Vertex { + position: Vector3::new(-width / 2.0, height / 2.0, 0.0), + uv: Vector2::new(0.0, 0.0), + normal: Vector3::new(0.0, 0.0, 0.0), + }, + ], + vec![2, 1, 0, 2, 0, 3], + settings, + renderer, + ); +} + +// MARK: 3D + /// Creates a 3D cube pub fn cube(name: impl StringBuffer, renderer: &mut Renderer, objects: &mut ObjectStorage) { objects.new_object( diff --git a/crates/blue_engine_core/src/vector.rs b/crates/blue_engine_core/src/utils/vector.rs similarity index 100% rename from crates/blue_engine_core/src/vector.rs rename to crates/blue_engine_core/src/utils/vector.rs diff --git a/examples/camera/rotate_around.rs b/examples/camera/rotate_around.rs index 6b4951b..2a8c43c 100644 --- a/examples/camera/rotate_around.rs +++ b/examples/camera/rotate_around.rs @@ -5,9 +5,9 @@ */ use blue_engine::{ + Vector3, prelude::{Engine, ObjectSettings, ShaderSettings}, primitive_shapes::square, - Vector3, }; fn main() { From 0c2c941af2a436e215ad7eab9f122a731a9d506e Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Sun, 23 Mar 2025 16:43:30 +0300 Subject: [PATCH 4/9] chore: moved structures from the header to their respective components --- crates/blue_engine_core/src/definition.rs | 83 ++- crates/blue_engine_core/src/objects.rs | 147 ++++- .../blue_engine_core/src/prelude/imports.rs | 2 + crates/blue_engine_core/src/prelude/mod.rs | 601 ++---------------- .../{utils => prelude}/primitive_shapes.rs | 0 .../src/{utils => prelude}/vector.rs | 29 +- crates/blue_engine_core/src/render.rs | 40 +- crates/blue_engine_core/src/utils/camera.rs | 69 +- crates/blue_engine_core/src/utils/mod.rs | 5 - crates/blue_engine_core/src/window.rs | 170 ++++- 10 files changed, 573 insertions(+), 573 deletions(-) rename crates/blue_engine_core/src/{utils => prelude}/primitive_shapes.rs (100%) rename crates/blue_engine_core/src/{utils => prelude}/vector.rs (97%) diff --git a/crates/blue_engine_core/src/definition.rs b/crates/blue_engine_core/src/definition.rs index 50294b7..bc02a84 100644 --- a/crates/blue_engine_core/src/definition.rs +++ b/crates/blue_engine_core/src/definition.rs @@ -9,12 +9,87 @@ use wgpu::{BindGroupLayout, Sampler, Texture, TextureView, util::DeviceExt}; use crate::{ InstanceRaw, UnsignedIntType, - prelude::{ - Pipeline, PipelineData, ShaderSettings, Shaders, StringBuffer, TextureData, TextureMode, - Textures, UniformBuffers, Vertex, VertexBuffers, - }, + prelude::{ShaderSettings, Shaders, StringBuffer, Textures, UniformBuffers, Vertex}, }; +/// Container for pipeline values. Each pipeline takes only 1 vertex shader, +/// 1 fragment shader, 1 texture data, and optionally a vector of uniform data. +#[derive(Debug)] +pub struct Pipeline { + /// the shader buffer that's sent to the gpu + pub shader: PipelineData, + /// The vertex buffer that's sent to the gpu. This includes indices as well + pub vertex_buffer: PipelineData, + /// The texture that's sent to the gpu. + pub texture: PipelineData, + /// the Uniform buffers that are sent to the gpu + pub uniform: PipelineData>, +} +unsafe impl Send for Pipeline {} +unsafe impl Sync for Pipeline {} + +/// Container for pipeline data. Allows for sharing resources with other objects +#[derive(Debug)] +pub enum PipelineData { + /// No data, just a reference to a buffer + Copy(String), + /// The actual data + Data(T), +} + +/// Container for vertex and index buffer +#[derive(Debug)] +pub struct VertexBuffers { + /// An array of vertices. A vertex is a point in 3D space containing + /// an X, Y, and a Z coordinate between -1 and +1 + pub vertex_buffer: wgpu::Buffer, + /// An array of indices. Indices are a way to reuse vertices, + /// this in turn helps greatly in reduction of amount of vertices needed to be sent to the GPU + pub index_buffer: wgpu::Buffer, + /// The length of the vertex buffer + pub length: u32, +} +unsafe impl Send for VertexBuffers {} +unsafe impl Sync for VertexBuffers {} + +/// Defines how the texture data is +#[derive(Debug, Clone)] +pub enum TextureData { + /// the texture file bytes directly + Bytes(Vec), + /// the texture as a [`image::DynamicImage`] + Image(image::DynamicImage), + /// path to a texture file to load + Path(String), +} +unsafe impl Send for TextureData {} +unsafe impl Sync for TextureData {} + +/// Defines how the borders of texture would look like +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TextureMode { + /// Expands the texture to fit the object + Clamp, + /// Repeats the texture instead of stretching + Repeat, + /// Repeats the texture, but mirrors it on edges + MirrorRepeat, +} +unsafe impl Send for TextureMode {} +unsafe impl Sync for TextureMode {} + +/// This function helps in converting pixel value to the value that is between -1 and +1 +pub fn pixel_to_cartesian(value: f32, max: u32) -> f32 { + let mut result = value / max as f32; + + if value == max as f32 { + result = 0.0; + } else if result < max as f32 / 2.0 { + } + + if result > -1.0 { result } else { -1.0 } +} + impl crate::prelude::Renderer { /// Creates a new render pipeline. Could be thought of as like materials in game engines. pub fn build_pipeline( diff --git a/crates/blue_engine_core/src/objects.rs b/crates/blue_engine_core/src/objects.rs index 11f71ff..61d2764 100644 --- a/crates/blue_engine_core/src/objects.rs +++ b/crates/blue_engine_core/src/objects.rs @@ -4,13 +4,150 @@ * The license is same as the one on the root. */ -use crate::prelude::{ - Instance, InstanceRaw, Object, ObjectSettings, Pipeline, PipelineData, Renderer, RotateAxis, - TextureData, Textures, Vertex, glm, pixel_to_cartesian, uniform_type, -}; use crate::uniform_type::{Array4, Matrix}; use crate::utils::default_resources::{DEFAULT_MATRIX_4, DEFAULT_SHADER, DEFAULT_TEXTURE}; -use crate::{ObjectStorage, RotateAmount, StringBuffer, UnsignedIntType, Vector3}; +use crate::{ + Pipeline, PipelineData, Renderer, ShaderSettings, StringBuffer, TextureData, Textures, + UnsignedIntType, Vector3, Vertex, glm, pixel_to_cartesian, uniform_type, +}; + +/// Objects make it easier to work with Blue Engine, it automates most of work needed for +/// creating 3D objects and showing them on screen. A range of default objects are available +/// as well as ability to customize each of them and even create your own! You can also +/// customize almost everything there is about them! +pub struct Object { + /// Give your object a name, which can help later on for debugging. + pub name: std::sync::Arc, + /// A list of Vertex + pub vertices: Vec, + /// A list of indices that dictates the order that vertices appear + pub indices: Vec, + /// Describes how to uniform buffer is structures + pub uniform_layout: wgpu::BindGroupLayout, + /// Pipeline holds all the data that is sent to GPU, including shaders and textures + pub pipeline: Pipeline, + /// List of instances of this object + pub instances: Vec, + /// instance buffer + pub instance_buffer: wgpu::Buffer, + /// Dictates the size of your object in relation to the world + pub size: Vector3, + /// Dictates the position of your object in pixels + pub position: Vector3, + /// Dictates the rotation of your object + pub rotation: Vector3, + // flags the object to be updated until next frame + pub(crate) changed: bool, + /// Transformation matrices helps to apply changes to your object, including position, orientation, ... + /// Best choice is to let the Object system handle it + pub position_matrix: nalgebra_glm::Mat4, + /// Transformation matrices helps to apply changes to your object, including position, orientation, ... + /// Best choice is to let the Object system handle it + pub scale_matrix: nalgebra_glm::Mat4, + /// Transformation matrices helps to apply changes to your object, including position, orientation, ... + /// Best choice is to let the Object system handle it + pub rotation_matrix: nalgebra_glm::Mat4, + /// Transformation matrix, but inversed + pub inverse_transformation_matrix: crate::uniform_type::Matrix, + /// The main color of your object + pub color: crate::uniform_type::Array4, + /// A struct making it easier to manipulate specific parts of shader + pub shader_builder: crate::objects::ShaderBuilder, + /// Shader settings + pub shader_settings: ShaderSettings, + /// Camera have any effect on the object? + pub camera_effect: Option>, + /// Uniform Buffers to be sent to GPU. These are raw and not compiled for GPU yet + pub uniform_buffers: Vec, + /// Should be rendered or not + pub is_visible: bool, + /// Objects with higher number get rendered later and appear "on top" when occupying the same space + pub render_order: usize, +} +unsafe impl Send for Object {} +unsafe impl Sync for Object {} + +/// Extra settings to customize objects on time of creation +#[derive(Debug, Clone)] +pub struct ObjectSettings { + /// Should it be affected by camera? + pub camera_effect: Option>, + /// Shader Settings + pub shader_settings: ShaderSettings, +} +impl Default for ObjectSettings { + fn default() -> Self { + Self { + camera_effect: Some("main".into()), + shader_settings: ShaderSettings::default(), + } + } +} +unsafe impl Send for ObjectSettings {} +unsafe impl Sync for ObjectSettings {} + +/// A unified way to handle objects +/// +/// This is a container for objects that is used to apply different operations on the objects at the same time. +/// It can deref to the object hashmap itself when needed. +pub struct ObjectStorage(std::collections::HashMap); +impl ObjectStorage { + /// Creates a new object storage + pub fn new() -> Self { + ObjectStorage(std::collections::HashMap::new()) + } +} +impl Default for ObjectStorage { + fn default() -> Self { + Self::new() + } +} +unsafe impl Send for ObjectStorage {} +unsafe impl Sync for ObjectStorage {} +crate::macros::impl_deref!(ObjectStorage, std::collections::HashMap); + +/// Instance buffer data that is sent to GPU +#[repr(C)] +#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +pub struct InstanceRaw { + /// The transformation matrix of the instance + pub model: uniform_type::Matrix, +} + +/// Instance buffer data storage +#[derive(Debug, Clone, Copy)] +pub struct Instance { + /// The position of the instance + pub position: Vector3, + /// The rotation of the instance + pub rotation: Vector3, + /// The scale of the instance + pub scale: Vector3, +} + +/// Defines how the rotation axis is +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RotateAxis { + #[doc(hidden)] + X, + #[doc(hidden)] + Y, + #[doc(hidden)] + Z, +} +unsafe impl Send for RotateAxis {} +unsafe impl Sync for RotateAxis {} + +/// Defines how the rotation amount is +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum RotateAmount { + #[doc(hidden)] + Radians(f32), + #[doc(hidden)] + Degrees(f32), +} +unsafe impl Send for RotateAmount {} +unsafe impl Sync for RotateAmount {} impl Renderer { /// Creates a new object diff --git a/crates/blue_engine_core/src/prelude/imports.rs b/crates/blue_engine_core/src/prelude/imports.rs index 310bf0e..657e093 100644 --- a/crates/blue_engine_core/src/prelude/imports.rs +++ b/crates/blue_engine_core/src/prelude/imports.rs @@ -34,6 +34,8 @@ pub use wgpu::RenderPassColorAttachment; pub use wgpu::RenderPassDescriptor; /// Surface Texture pub use wgpu::TextureView; +/// Depth format +pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; /// Input helper pub use crate::utils::winit_input_helper::WinitInputHelper as InputHelper; diff --git a/crates/blue_engine_core/src/prelude/mod.rs b/crates/blue_engine_core/src/prelude/mod.rs index 8420dbe..08bb3a5 100644 --- a/crates/blue_engine_core/src/prelude/mod.rs +++ b/crates/blue_engine_core/src/prelude/mod.rs @@ -1,10 +1,25 @@ +use downcast::{Any, downcast}; /// re-exports from dependencies that are useful pub mod imports; +pub use imports::*; /// few commonly used uniform buffer structures pub mod uniform_buffer; -use downcast::{Any, downcast}; -pub use imports::*; pub use uniform_buffer::*; +/// contains definition for some 2D and 3D shapes. They are basic shapes and +/// can be used as examples of how to create your own content. +pub mod primitive_shapes; +/// contains definition for 2D and 3D vectors. +pub mod vector; +pub use crate::camera::{Camera, CameraContainer, Projection}; +pub use crate::definition::{ + Pipeline, PipelineData, TextureData, TextureMode, VertexBuffers, pixel_to_cartesian, +}; +pub use crate::objects::{ + Instance, InstanceRaw, Object, ObjectSettings, ObjectStorage, RotateAmount, RotateAxis, +}; +pub use crate::render::Renderer; +pub use crate::window::{ShaderSettings, Window, WindowDescriptor}; +pub use vector::{Vector2, Vector3}; /// The uint type used for indices and more #[cfg(not(feature = "u32"))] @@ -12,38 +27,44 @@ pub type UnsignedIntType = u16; #[cfg(feature = "u32")] pub type UnsignedIntType = u32; -macro_rules! impl_deref { - ($struct:ty,$type:ty) => { - impl std::ops::Deref for $struct { - type Target = $type; - - fn deref(&self) -> &Self::Target { - &self.0 +/// +pub mod macros { + macro_rules! impl_deref { + ($struct:ty,$type:ty) => { + impl std::ops::Deref for $struct { + type Target = $type; + + fn deref(&self) -> &Self::Target { + &self.0 + } } - } - impl std::ops::DerefMut for $struct { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + impl std::ops::DerefMut for $struct { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } - } - }; -} + }; + } -macro_rules! impl_deref_field { - ($struct:ty,$type:ty,$field:ident) => { - impl std::ops::Deref for $struct { - type Target = $type; + macro_rules! impl_deref_field { + ($struct:ty,$type:ty,$field:ident) => { + impl std::ops::Deref for $struct { + type Target = $type; - fn deref(&self) -> &Self::Target { - &self.$field + fn deref(&self) -> &Self::Target { + &self.$field + } } - } - impl std::ops::DerefMut for $struct { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.$field + impl std::ops::DerefMut for $struct { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.$field + } } - } - }; + }; + } + + pub(crate) use impl_deref; + pub(crate) use impl_deref_field; } /// Will contain all details about a vertex and will be sent to GPU @@ -87,81 +108,6 @@ impl Vertex { unsafe impl Send for Vertex {} unsafe impl Sync for Vertex {} -/// Objects make it easier to work with Blue Engine, it automates most of work needed for -/// creating 3D objects and showing them on screen. A range of default objects are available -/// as well as ability to customize each of them and even create your own! You can also -/// customize almost everything there is about them! -pub struct Object { - /// Give your object a name, which can help later on for debugging. - pub name: std::sync::Arc, - /// A list of Vertex - pub vertices: Vec, - /// A list of indices that dictates the order that vertices appear - pub indices: Vec, - /// Describes how to uniform buffer is structures - pub uniform_layout: wgpu::BindGroupLayout, - /// Pipeline holds all the data that is sent to GPU, including shaders and textures - pub pipeline: Pipeline, - /// List of instances of this object - pub instances: Vec, - /// instance buffer - pub instance_buffer: wgpu::Buffer, - /// Dictates the size of your object in relation to the world - pub size: Vector3, - /// Dictates the position of your object in pixels - pub position: Vector3, - /// Dictates the rotation of your object - pub rotation: Vector3, - // flags the object to be updated until next frame - pub(crate) changed: bool, - /// Transformation matrices helps to apply changes to your object, including position, orientation, ... - /// Best choice is to let the Object system handle it - pub position_matrix: nalgebra_glm::Mat4, - /// Transformation matrices helps to apply changes to your object, including position, orientation, ... - /// Best choice is to let the Object system handle it - pub scale_matrix: nalgebra_glm::Mat4, - /// Transformation matrices helps to apply changes to your object, including position, orientation, ... - /// Best choice is to let the Object system handle it - pub rotation_matrix: nalgebra_glm::Mat4, - /// Transformation matrix, but inversed - pub inverse_transformation_matrix: crate::uniform_type::Matrix, - /// The main color of your object - pub color: crate::uniform_type::Array4, - /// A struct making it easier to manipulate specific parts of shader - pub shader_builder: crate::objects::ShaderBuilder, - /// Shader settings - pub shader_settings: ShaderSettings, - /// Camera have any effect on the object? - pub camera_effect: Option>, - /// Uniform Buffers to be sent to GPU. These are raw and not compiled for GPU yet - pub uniform_buffers: Vec, - /// Should be rendered or not - pub is_visible: bool, - /// Objects with higher number get rendered later and appear "on top" when occupying the same space - pub render_order: usize, -} -unsafe impl Send for Object {} -unsafe impl Sync for Object {} - -/// Extra settings to customize objects on time of creation -#[derive(Debug, Clone)] -pub struct ObjectSettings { - /// Should it be affected by camera? - pub camera_effect: Option>, - /// Shader Settings - pub shader_settings: ShaderSettings, -} -impl Default for ObjectSettings { - fn default() -> Self { - Self { - camera_effect: Some("main".into()), - shader_settings: ShaderSettings::default(), - } - } -} -unsafe impl Send for ObjectSettings {} -unsafe impl Sync for ObjectSettings {} - /// The engine is the main starting point of using the Blue Engine. /// Everything that runs on Blue Engine will be under this struct. /// The structure of engine is monolithic, but the underlying data and the way it works is not. @@ -248,317 +194,6 @@ pub struct Engine { unsafe impl Send for Engine {} unsafe impl Sync for Engine {} -/// Container for pipeline values. Each pipeline takes only 1 vertex shader, -/// 1 fragment shader, 1 texture data, and optionally a vector of uniform data. -#[derive(Debug)] -pub struct Pipeline { - /// the shader buffer that's sent to the gpu - pub shader: PipelineData, - /// The vertex buffer that's sent to the gpu. This includes indices as well - pub vertex_buffer: PipelineData, - /// The texture that's sent to the gpu. - pub texture: PipelineData, - /// the Uniform buffers that are sent to the gpu - pub uniform: PipelineData>, -} -unsafe impl Send for Pipeline {} -unsafe impl Sync for Pipeline {} - -/// Container for pipeline data. Allows for sharing resources with other objects -#[derive(Debug)] -pub enum PipelineData { - /// No data, just a reference to a buffer - Copy(String), - /// The actual data - Data(T), -} - -/// Container for vertex and index buffer -#[derive(Debug)] -pub struct VertexBuffers { - /// An array of vertices. A vertex is a point in 3D space containing - /// an X, Y, and a Z coordinate between -1 and +1 - pub vertex_buffer: wgpu::Buffer, - /// An array of indices. Indices are a way to reuse vertices, - /// this in turn helps greatly in reduction of amount of vertices needed to be sent to the GPU - pub index_buffer: wgpu::Buffer, - /// The length of the vertex buffer - pub length: u32, -} -unsafe impl Send for VertexBuffers {} -unsafe impl Sync for VertexBuffers {} - -/// Main renderer class. this will contain all methods and data related to the renderer -#[derive(Debug)] -pub struct Renderer { - /// A [`wgpu::Surface`] represents a platform-specific surface - /// (e.g. a window) onto which rendered images may be presented. - pub surface: Option>, - /// Context for all of the gpu objects - pub instance: wgpu::Instance, - /// Handle to a physical graphics and/or compute device. - #[allow(unused)] - pub adapter: wgpu::Adapter, - /// Open connection to a graphics and/or compute device. - pub device: wgpu::Device, - /// Handle to a command queue on a device. - pub queue: wgpu::Queue, - /// Describes a [`wgpu::Surface`] - pub config: wgpu::SurfaceConfiguration, - /// The size of the window - pub size: winit::dpi::PhysicalSize, - /// The texture bind group layout - pub texture_bind_group_layout: wgpu::BindGroupLayout, - /// The uniform bind group layout - pub default_uniform_bind_group_layout: wgpu::BindGroupLayout, - /// The depth buffer, used to render object depth - pub depth_buffer: (wgpu::Texture, wgpu::TextureView, wgpu::Sampler), - /// The default data used within the renderer - pub default_data: Option<(crate::Textures, crate::Shaders, crate::UniformBuffers)>, - /// The camera used in the engine - pub camera: Option, - /// Background clear color - pub clear_color: wgpu::Color, - /// Scissor cut section of the screen to render to - /// (x, y, width, height) - pub scissor_rect: Option<(u32, u32, u32, u32)>, -} -unsafe impl Sync for Renderer {} -unsafe impl Send for Renderer {} - -/// Descriptor and settings for a window. -#[derive(Debug, Clone)] -pub struct WindowDescriptor { - /// The width of the window - pub width: u32, - /// The height of the window - pub height: u32, - /// The title of the window - pub title: &'static str, - /// Should the window contain the keys like minimize, maximize, or resize? - pub decorations: bool, - /// Should the window be resizable - pub resizable: bool, - /// Define how much power should the app ask for - pub power_preference: crate::PowerPreference, - /// The backend to use for the draw - pub backends: crate::Backends, - /// The features to be enabled on a backend - /// - /// read more at [wgpu::Features] - pub features: crate::wgpu::Features, - /// Controls how the events are processed - /// - /// read more at [winit::event_loop::ControlFlow] - pub control_flow: crate::winit::event_loop::ControlFlow, - /// The presentation mode of renderer for things like VSync - /// - /// read more at [wgpu::PresentMode] - pub present_mode: crate::wgpu::PresentMode, - /// Limits to be required based on the generation of the GPU and the API. - /// - /// read more at [wgpu::Limits] - pub limits: crate::wgpu::Limits, - /// The alpha mode which specifies how the alpha channel of - /// the textures should be handled during compositing. - pub alpha_mode: crate::wgpu::CompositeAlphaMode, - /// The desired frame latency. - /// - /// read more at [wgpu::SurfaceConfiguration::desired_maximum_frame_latency] - pub desired_maximum_frame_latency: u32, - /// How the memory should be utilized - /// - /// read more at [wgpu::MemoryHints] - pub memory_hints: crate::wgpu::MemoryHints, -} -impl std::default::Default for WindowDescriptor { - /// Will quickly create a window with default settings - fn default() -> Self { - let backends = crate::Backends::all(); - Self { - width: 800, - height: 600, - title: "Blue Engine", - decorations: true, - resizable: true, - power_preference: crate::PowerPreference::LowPower, - backends, - features: if backends == wgpu::Backends::VULKAN { - wgpu::Features::POLYGON_MODE_LINE | wgpu::Features::POLYGON_MODE_POINT - } else if backends - .contains(wgpu::Backends::VULKAN | wgpu::Backends::METAL | wgpu::Backends::DX12) - { - wgpu::Features::POLYGON_MODE_LINE - } else { - wgpu::Features::empty() - }, - control_flow: crate::winit::event_loop::ControlFlow::Poll, - present_mode: crate::wgpu::PresentMode::AutoNoVsync, - limits: crate::wgpu::Limits::default(), - alpha_mode: crate::wgpu::CompositeAlphaMode::Auto, - desired_maximum_frame_latency: 2, - memory_hints: crate::MemoryHints::Performance, - } - } -} -unsafe impl Send for WindowDescriptor {} -unsafe impl Sync for WindowDescriptor {} - -/// Container for the projection used by the camera -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub enum Projection { - /// Perspective projection - /// - /// This is the default project used by the video games and majority of graphics - Perspective { - /// The field of view - fov: f32, - }, - /// Orthographic projection - /// - /// This projection gives you a 2D view of the scene - Orthographic { - /// The size of the view - zoom: f32, - }, -} - -/// Container for the camera feature. The settings here are needed for -/// algebra equations needed for camera vision and movement. Please leave it to the renderer to handle -#[derive(Debug)] -pub struct Camera { - /// The position of the camera in 3D space - pub position: Vector3, - /// The target at which the camera should be looking - pub target: Vector3, - /// The up vector of the camera. This defines the elevation of the camera - pub up: Vector3, - /// The resolution of the camera view - pub resolution: (f32, f32), //maybe this should be a Vector2i - /// The projection of the camera - pub projection: Projection, - /// The closest view of camera - pub near: f32, - /// The furthest view of camera - pub far: f32, - /// The final data that will be sent to GPU - pub view_data: nalgebra_glm::Mat4, - // For checking and rebuilding it's uniform buffer - pub(crate) changed: bool, - /// The uniform data of the camera to be sent to the gpu - pub uniform_data: UniformBuffers, - /// The position and target of the camera - pub(crate) add_position_and_target: bool, -} -unsafe impl Send for Camera {} -unsafe impl Sync for Camera {} - -/// Container for Cameras -/// -/// This allows for different objects have a different camera perspective. -#[derive(Debug)] -pub struct CameraContainer { - /// The list of cameras - // Arc is used instead of String for performance - pub cameras: std::collections::HashMap, Camera>, -} -impl_deref_field!( - CameraContainer, - std::collections::HashMap, Camera>, - cameras -); - -/// These definitions are taken from wgpu API docs -#[derive(Debug, Clone, Copy)] -pub struct ShaderSettings { - // ===== PRIMITIVE ===== // - /// The primitive topology used to interpret vertices - pub topology: crate::ShaderPrimitive, - /// When drawing strip topologies with indices, this is the - /// required format for the index buffer. This has no effect - /// on non-indexed or non-strip draws. - pub strip_index_format: Option, - /// The face to consider the front for the purpose of - /// culling and stencil operations. - pub front_face: crate::FrontFace, - /// The face culling mode - pub cull_mode: Option, - /// Controls the way each polygon is rasterized. Can be - /// either `Fill` (default), `Line` or `Point` - /// - /// Setting this to something other than `Fill` requires - /// `NON_FILL_POLYGON_MODE` feature to be enabled - pub polygon_mode: crate::PolygonMode, - /// If set to true, the polygon depth is clamped to 0-1 - /// range instead of being clipped. - /// - /// Enabling this requires the `DEPTH_CLAMPING` feature - /// to be enabled - pub clamp_depth: bool, - /// If set to true, the primitives are rendered with - /// conservative overestimation. I.e. any rastered - /// pixel touched by it is filled. Only valid for PolygonMode::Fill! - /// - /// Enabling this requires `CONSERVATIVE_RASTERIZATION` - /// features to be enabled. - pub conservative: bool, - - // ===== Multisample ===== // - /// The number of samples calculated per pixel (for MSAA). - /// For non-multisampled textures, this should be `1` - pub count: u32, - /// Bitmask that restricts the samples of a pixel modified - /// by this pipeline. All samples can be enabled using the - /// value `!0` - pub mask: u64, - /// When enabled, produces another sample mask per pixel - /// based on the alpha output value, that is ANDead with the - /// sample_mask and the primitive coverage to restrict the - /// set of samples affected by a primitive. - - /// The implicit mask produced for alpha of zero is guaranteed - /// to be zero, and for alpha of one is guaranteed to be all - /// 1-s. - pub alpha_to_coverage_enabled: bool, -} -impl Default for ShaderSettings { - fn default() -> Self { - Self { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - clamp_depth: false, - conservative: false, - count: 1, - mask: !0, - alpha_to_coverage_enabled: true, - } - } -} -unsafe impl Send for ShaderSettings {} -unsafe impl Sync for ShaderSettings {} - -/// Instance buffer data that is sent to GPU -#[repr(C)] -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -pub struct InstanceRaw { - /// The transformation matrix of the instance - pub model: uniform_type::Matrix, -} - -/// Instance buffer data storage -#[derive(Debug, Clone, Copy)] -pub struct Instance { - /// The position of the instance - pub position: Vector3, - /// The rotation of the instance - pub rotation: Vector3, - /// The scale of the instance - pub scale: Vector3, -} - /// Allows all events to be fetched directly, making it easier to add custom additions to the engine. pub trait Signal: Any { /// This is ran as soon as the engine is properly initialized and all components are ready @@ -616,66 +251,10 @@ pub trait Signal: Any { // so we use downcast and not the std::any::Any downcast!(dyn Signal); -/// Defines how the rotation axis is -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RotateAxis { - #[doc(hidden)] - X, - #[doc(hidden)] - Y, - #[doc(hidden)] - Z, -} -unsafe impl Send for RotateAxis {} -unsafe impl Sync for RotateAxis {} - -/// Defines how the rotation amount is -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum RotateAmount { - #[doc(hidden)] - Radians(f32), - #[doc(hidden)] - Degrees(f32), -} -unsafe impl Send for RotateAmount {} -unsafe impl Sync for RotateAmount {} - -/// Defines how the texture data is -#[derive(Debug, Clone)] -pub enum TextureData { - /// the texture file bytes directly - Bytes(Vec), - /// the texture as a [`image::DynamicImage`] - Image(image::DynamicImage), - /// path to a texture file to load - Path(String), -} -unsafe impl Send for TextureData {} -unsafe impl Sync for TextureData {} - -/// Defines how the borders of texture would look like -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TextureMode { - /// Expands the texture to fit the object - Clamp, - /// Repeats the texture instead of stretching - Repeat, - /// Repeats the texture, but mirrors it on edges - MirrorRepeat, -} -unsafe impl Send for TextureMode {} -unsafe impl Sync for TextureMode {} - -/// This function helps in converting pixel value to the value that is between -1 and +1 -pub fn pixel_to_cartesian(value: f32, max: u32) -> f32 { - let mut result = value / max as f32; - - if value == max as f32 { - result = 0.0; - } else if result < max as f32 / 2.0 { - } - - if result > -1.0 { result } else { -1.0 } +/// Handles the live events in the engine +pub struct SignalStorage { + /// list of events with key and the event + pub events: Vec<(String, Box)>, } /// A unified way to handle strings @@ -714,77 +293,3 @@ impl StringBufferTrait for &str { } } impl StringBuffer for &str {} - -/// A unified way to handle objects -/// -/// This is a container for objects that is used to apply different operations on the objects at the same time. -/// It can deref to the object hashmap itself when needed. -pub struct ObjectStorage(std::collections::HashMap); -impl ObjectStorage { - /// Creates a new object storage - pub fn new() -> Self { - ObjectStorage(std::collections::HashMap::new()) - } -} -impl Default for ObjectStorage { - fn default() -> Self { - Self::new() - } -} -unsafe impl Send for ObjectStorage {} -unsafe impl Sync for ObjectStorage {} - -impl_deref!(ObjectStorage, std::collections::HashMap); - -/// Depth format -pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; - -/// Handles the live events in the engine -pub struct SignalStorage { - /// list of events with key and the event - pub events: Vec<(String, Box)>, -} - -/// Handles the order in which a functionality in the engine should be executed -pub enum ExecuteOrder { - /// The main function that is the update_loop - UpdateLoopFunction, -} - -/// A wrapper for winit window to make it easier to use and more ergonomic. -#[derive(Debug)] -pub struct Window { - /// The winit window itself. - pub window: Option>, - /// Default attributes of the window - pub default_attributes: winit::window::WindowAttributes, - /// Whether the engine should close. - pub should_close: bool, -} -impl_deref_field!( - Window, - Option>, - window -); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Zeroable)] -#[repr(C)] -/// General purposes 3D vector -pub struct Vector3 { - /// X coordinate in 3D space - pub x: f32, - /// Y coordinate in 3D space - pub y: f32, - /// Z coordinate in 3D space - pub z: f32, -} - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Zeroable)] -#[repr(C)] -/// General purposes 2D vector -pub struct Vector2 { - /// X coordinate in 2D space - pub x: f32, - /// Y coordinate in 2D space - pub y: f32, -} diff --git a/crates/blue_engine_core/src/utils/primitive_shapes.rs b/crates/blue_engine_core/src/prelude/primitive_shapes.rs similarity index 100% rename from crates/blue_engine_core/src/utils/primitive_shapes.rs rename to crates/blue_engine_core/src/prelude/primitive_shapes.rs diff --git a/crates/blue_engine_core/src/utils/vector.rs b/crates/blue_engine_core/src/prelude/vector.rs similarity index 97% rename from crates/blue_engine_core/src/utils/vector.rs rename to crates/blue_engine_core/src/prelude/vector.rs index 3d5c3bf..dc23a4f 100644 --- a/crates/blue_engine_core/src/utils/vector.rs +++ b/crates/blue_engine_core/src/prelude/vector.rs @@ -1,10 +1,29 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; -use crate::{ - RotateAmount, RotateAxis, - prelude::{Vector2, Vector3}, -}; -use bytemuck::Pod; +use crate::{RotateAmount, RotateAxis}; +use bytemuck::{Pod, Zeroable}; + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Zeroable)] +#[repr(C)] +/// General purposes 3D vector +pub struct Vector3 { + /// X coordinate in 3D space + pub x: f32, + /// Y coordinate in 3D space + pub y: f32, + /// Z coordinate in 3D space + pub z: f32, +} + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Zeroable)] +#[repr(C)] +/// General purposes 2D vector +pub struct Vector2 { + /// X coordinate in 2D space + pub x: f32, + /// Y coordinate in 2D space + pub y: f32, +} // Constructors impl Vector3 { diff --git a/crates/blue_engine_core/src/render.rs b/crates/blue_engine_core/src/render.rs index e398075..f3753e9 100644 --- a/crates/blue_engine_core/src/render.rs +++ b/crates/blue_engine_core/src/render.rs @@ -2,10 +2,48 @@ use crate::{ CameraContainer, ObjectStorage, PipelineData, - prelude::{Renderer, ShaderSettings, TextureData, uniform_type}, + prelude::{ShaderSettings, TextureData, uniform_type}, utils::default_resources::{DEFAULT_COLOR, DEFAULT_MATRIX_4, DEFAULT_SHADER, DEFAULT_TEXTURE}, }; +/// Main renderer class. this will contain all methods and data related to the renderer +#[derive(Debug)] +pub struct Renderer { + /// A [`wgpu::Surface`] represents a platform-specific surface + /// (e.g. a window) onto which rendered images may be presented. + pub surface: Option>, + /// Context for all of the gpu objects + pub instance: wgpu::Instance, + /// Handle to a physical graphics and/or compute device. + #[allow(unused)] + pub adapter: wgpu::Adapter, + /// Open connection to a graphics and/or compute device. + pub device: wgpu::Device, + /// Handle to a command queue on a device. + pub queue: wgpu::Queue, + /// Describes a [`wgpu::Surface`] + pub config: wgpu::SurfaceConfiguration, + /// The size of the window + pub size: winit::dpi::PhysicalSize, + /// The texture bind group layout + pub texture_bind_group_layout: wgpu::BindGroupLayout, + /// The uniform bind group layout + pub default_uniform_bind_group_layout: wgpu::BindGroupLayout, + /// The depth buffer, used to render object depth + pub depth_buffer: (wgpu::Texture, wgpu::TextureView, wgpu::Sampler), + /// The default data used within the renderer + pub default_data: Option<(crate::Textures, crate::Shaders, crate::UniformBuffers)>, + /// The camera used in the engine + pub camera: Option, + /// Background clear color + pub clear_color: wgpu::Color, + /// Scissor cut section of the screen to render to + /// (x, y, width, height) + pub scissor_rect: Option<(u32, u32, u32, u32)>, +} +unsafe impl Sync for Renderer {} +unsafe impl Send for Renderer {} + impl Renderer { /// Creates a new renderer. pub(crate) async fn new( diff --git a/crates/blue_engine_core/src/utils/camera.rs b/crates/blue_engine_core/src/utils/camera.rs index 37afb23..4a21412 100644 --- a/crates/blue_engine_core/src/utils/camera.rs +++ b/crates/blue_engine_core/src/utils/camera.rs @@ -4,13 +4,76 @@ * The license is same as the one on the root. */ +use super::default_resources::{DEFAULT_MATRIX_4, OPENGL_TO_WGPU_MATRIX}; use crate::{ - CameraContainer, Projection, - prelude::{Camera, Renderer, Vector3, uniform_type::Matrix}, + UniformBuffers, + prelude::{Renderer, Vector3, uniform_type::Matrix}, }; use winit::dpi::PhysicalSize; -use super::default_resources::{DEFAULT_MATRIX_4, OPENGL_TO_WGPU_MATRIX}; +/// Container for the projection used by the camera +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub enum Projection { + /// Perspective projection + /// + /// This is the default project used by the video games and majority of graphics + Perspective { + /// The field of view + fov: f32, + }, + /// Orthographic projection + /// + /// This projection gives you a 2D view of the scene + Orthographic { + /// The size of the view + zoom: f32, + }, +} + +/// Container for the camera feature. The settings here are needed for +/// algebra equations needed for camera vision and movement. Please leave it to the renderer to handle +#[derive(Debug)] +pub struct Camera { + /// The position of the camera in 3D space + pub position: Vector3, + /// The target at which the camera should be looking + pub target: Vector3, + /// The up vector of the camera. This defines the elevation of the camera + pub up: Vector3, + /// The resolution of the camera view + pub resolution: (f32, f32), //maybe this should be a Vector2i + /// The projection of the camera + pub projection: Projection, + /// The closest view of camera + pub near: f32, + /// The furthest view of camera + pub far: f32, + /// The final data that will be sent to GPU + pub view_data: nalgebra_glm::Mat4, + // For checking and rebuilding it's uniform buffer + pub(crate) changed: bool, + /// The uniform data of the camera to be sent to the gpu + pub uniform_data: UniformBuffers, + /// The position and target of the camera + pub(crate) add_position_and_target: bool, +} +unsafe impl Send for Camera {} +unsafe impl Sync for Camera {} + +/// Container for Cameras +/// +/// This allows for different objects have a different camera perspective. +#[derive(Debug)] +pub struct CameraContainer { + /// The list of cameras + // Arc is used instead of String for performance + pub cameras: std::collections::HashMap, Camera>, +} +crate::macros::impl_deref_field!( + CameraContainer, + std::collections::HashMap, Camera>, + cameras +); impl Camera { /// Creates a new camera. this should've been automatically done at the time of creating an engine diff --git a/crates/blue_engine_core/src/utils/mod.rs b/crates/blue_engine_core/src/utils/mod.rs index a1f2495..1006009 100644 --- a/crates/blue_engine_core/src/utils/mod.rs +++ b/crates/blue_engine_core/src/utils/mod.rs @@ -4,10 +4,5 @@ pub mod camera; mod current_input; /// default resources used in the engine pub mod default_resources; -/// contains definition for some 2D and 3D shapes. They are basic shapes and -/// can be used as examples of how to create your own content. -pub mod primitive_shapes; -/// contains definition for 2D and 3D vectors. -pub mod vector; /// input helper pub mod winit_input_helper; diff --git a/crates/blue_engine_core/src/window.rs b/crates/blue_engine_core/src/window.rs index 80e30c7..947a7b7 100644 --- a/crates/blue_engine_core/src/window.rs +++ b/crates/blue_engine_core/src/window.rs @@ -5,8 +5,8 @@ */ use crate::{ - CameraContainer, ObjectStorage, Window, - prelude::{Engine, Renderer, WindowDescriptor}, + CameraContainer, ObjectStorage, + prelude::{Engine, Renderer}, }; use winit::{ @@ -16,6 +16,172 @@ use winit::{ window::WindowAttributes, }; +/// A wrapper for winit window to make it easier to use and more ergonomic. +#[derive(Debug)] +pub struct Window { + /// The winit window itself. + pub window: Option>, + /// Default attributes of the window + pub default_attributes: winit::window::WindowAttributes, + /// Whether the engine should close. + pub should_close: bool, +} +crate::macros::impl_deref_field!( + Window, + Option>, + window +); + +/// Descriptor and settings for a window. +#[derive(Debug, Clone)] +pub struct WindowDescriptor { + /// The width of the window + pub width: u32, + /// The height of the window + pub height: u32, + /// The title of the window + pub title: &'static str, + /// Should the window contain the keys like minimize, maximize, or resize? + pub decorations: bool, + /// Should the window be resizable + pub resizable: bool, + /// Define how much power should the app ask for + pub power_preference: crate::PowerPreference, + /// The backend to use for the draw + pub backends: crate::Backends, + /// The features to be enabled on a backend + /// + /// read more at [wgpu::Features] + pub features: crate::wgpu::Features, + /// Controls how the events are processed + /// + /// read more at [winit::event_loop::ControlFlow] + pub control_flow: crate::winit::event_loop::ControlFlow, + /// The presentation mode of renderer for things like VSync + /// + /// read more at [wgpu::PresentMode] + pub present_mode: crate::wgpu::PresentMode, + /// Limits to be required based on the generation of the GPU and the API. + /// + /// read more at [wgpu::Limits] + pub limits: crate::wgpu::Limits, + /// The alpha mode which specifies how the alpha channel of + /// the textures should be handled during compositing. + pub alpha_mode: crate::wgpu::CompositeAlphaMode, + /// The desired frame latency. + /// + /// read more at [wgpu::SurfaceConfiguration::desired_maximum_frame_latency] + pub desired_maximum_frame_latency: u32, + /// How the memory should be utilized + /// + /// read more at [wgpu::MemoryHints] + pub memory_hints: crate::wgpu::MemoryHints, +} +impl std::default::Default for WindowDescriptor { + /// Will quickly create a window with default settings + fn default() -> Self { + let backends = crate::Backends::all(); + Self { + width: 800, + height: 600, + title: "Blue Engine", + decorations: true, + resizable: true, + power_preference: crate::PowerPreference::LowPower, + backends, + features: if backends == wgpu::Backends::VULKAN { + wgpu::Features::POLYGON_MODE_LINE | wgpu::Features::POLYGON_MODE_POINT + } else if backends + .contains(wgpu::Backends::VULKAN | wgpu::Backends::METAL | wgpu::Backends::DX12) + { + wgpu::Features::POLYGON_MODE_LINE + } else { + wgpu::Features::empty() + }, + control_flow: crate::winit::event_loop::ControlFlow::Poll, + present_mode: crate::wgpu::PresentMode::AutoNoVsync, + limits: crate::wgpu::Limits::default(), + alpha_mode: crate::wgpu::CompositeAlphaMode::Auto, + desired_maximum_frame_latency: 2, + memory_hints: crate::MemoryHints::Performance, + } + } +} +unsafe impl Send for WindowDescriptor {} +unsafe impl Sync for WindowDescriptor {} + +/// These definitions are taken from wgpu API docs +#[derive(Debug, Clone, Copy)] +pub struct ShaderSettings { + // ===== PRIMITIVE ===== // + /// The primitive topology used to interpret vertices + pub topology: crate::ShaderPrimitive, + /// When drawing strip topologies with indices, this is the + /// required format for the index buffer. This has no effect + /// on non-indexed or non-strip draws. + pub strip_index_format: Option, + /// The face to consider the front for the purpose of + /// culling and stencil operations. + pub front_face: crate::FrontFace, + /// The face culling mode + pub cull_mode: Option, + /// Controls the way each polygon is rasterized. Can be + /// either `Fill` (default), `Line` or `Point` + /// + /// Setting this to something other than `Fill` requires + /// `NON_FILL_POLYGON_MODE` feature to be enabled + pub polygon_mode: crate::PolygonMode, + /// If set to true, the polygon depth is clamped to 0-1 + /// range instead of being clipped. + /// + /// Enabling this requires the `DEPTH_CLAMPING` feature + /// to be enabled + pub clamp_depth: bool, + /// If set to true, the primitives are rendered with + /// conservative overestimation. I.e. any rastered + /// pixel touched by it is filled. Only valid for PolygonMode::Fill! + /// + /// Enabling this requires `CONSERVATIVE_RASTERIZATION` + /// features to be enabled. + pub conservative: bool, + + // ===== Multisample ===== // + /// The number of samples calculated per pixel (for MSAA). + /// For non-multisampled textures, this should be `1` + pub count: u32, + /// Bitmask that restricts the samples of a pixel modified + /// by this pipeline. All samples can be enabled using the + /// value `!0` + pub mask: u64, + /// When enabled, produces another sample mask per pixel + /// based on the alpha output value, that is ANDead with the + /// sample_mask and the primitive coverage to restrict the + /// set of samples affected by a primitive. + + /// The implicit mask produced for alpha of zero is guaranteed + /// to be zero, and for alpha of one is guaranteed to be all + /// 1-s. + pub alpha_to_coverage_enabled: bool, +} +impl Default for ShaderSettings { + fn default() -> Self { + Self { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + clamp_depth: false, + conservative: false, + count: 1, + mask: !0, + alpha_to_coverage_enabled: true, + } + } +} +unsafe impl Send for ShaderSettings {} +unsafe impl Sync for ShaderSettings {} + impl Engine { /// Creates a new window in current thread using default settings. pub fn new() -> Result { From 378e51d1081b43abd1c32e1e1a5ed4a0e54dee85 Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Mon, 24 Mar 2025 23:28:49 +0300 Subject: [PATCH 5/9] feat: starting to autogenerate the Vector types --- crates/blue_engine_core/src/prelude/vector.rs | 66 +++---- crates/blue_engine_core/src/window.rs | 162 ++++-------------- 2 files changed, 73 insertions(+), 155 deletions(-) diff --git a/crates/blue_engine_core/src/prelude/vector.rs b/crates/blue_engine_core/src/prelude/vector.rs index dc23a4f..8bdac4e 100644 --- a/crates/blue_engine_core/src/prelude/vector.rs +++ b/crates/blue_engine_core/src/prelude/vector.rs @@ -1,30 +1,46 @@ -use std::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; - use crate::{RotateAmount, RotateAxis}; use bytemuck::{Pod, Zeroable}; +use std::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Zeroable)] -#[repr(C)] -/// General purposes 3D vector -pub struct Vector3 { - /// X coordinate in 3D space - pub x: f32, - /// Y coordinate in 3D space - pub y: f32, - /// Z coordinate in 3D space - pub z: f32, -} +#[crabtime::function] +fn gen_vectors(parts: Vec) { + fn part_builder(parts: &Vec, modification: F) -> String + where + F: Fn(&String) -> String, + { + parts.iter().map(modification).collect::>().join("") + } + + let parts_len = parts.len(); + let struct_fields = part_builder(&parts, |part| format!("///\npub {part}: f32,")); + + let fn_parameter = part_builder(&parts, |part| format!("{part}: f32,")); + let new_fn_parameter = part_builder(&parts, |part| format!("{part},")); + let new_fn = crabtime::quote! { + /// + pub const fn new({{fn_parameter}}) -> Self { + Self { {{new_fn_parameter}} } + } + }; -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Zeroable)] -#[repr(C)] -/// General purposes 2D vector -pub struct Vector2 { - /// X coordinate in 2D space - pub x: f32, - /// Y coordinate in 2D space - pub y: f32, + crabtime::output! { + #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Zeroable)] + #[repr(C)] + /// General purposes {{parts_len}}D vector + pub struct Vector{{parts_len}} { + {{struct_fields}} + } + + impl Vector{{parts_len}} { + {{new_fn}} + } + } } +gen_vectors!(["x", "y"]); +gen_vectors!(["x", "y", "z"]); +gen_vectors!(["x", "y", "z", "a"]); + // Constructors impl Vector3 { /// A vector with all components set to 0. @@ -38,10 +54,6 @@ impl Vector3 { /// A vector with all components set to 0 except for the z component, which is set to 1. pub const UNIT_Z: Self = Self::new(0.0, 0.0, 1.0); - /// Create a new 3D position with the given coordinates - pub const fn new(x: f32, y: f32, z: f32) -> Self { - Self { x, y, z } - } /// Returns a vector with all components set to 0 except for the x component, which is set to 1. pub const fn x_axis() -> Self { Self::new(1.0, 0.0, 0.0) @@ -578,10 +590,6 @@ impl Vector2 { /// A vector with all components set to 0 except for the y component, which is set to 1. pub const UNIT_Y: Self = Self::new(0.0, 1.0); - /// Create a new 2D position with the given coordinates - pub const fn new(x: f32, y: f32) -> Self { - Self { x, y } - } /// Returns a vector with all components set to 0 except for the x component, which is set to 1. pub const fn x_axis() -> Self { Self::new(1.0, 0.0) diff --git a/crates/blue_engine_core/src/window.rs b/crates/blue_engine_core/src/window.rs index 947a7b7..8f06acf 100644 --- a/crates/blue_engine_core/src/window.rs +++ b/crates/blue_engine_core/src/window.rs @@ -505,6 +505,19 @@ impl ApplicationHandler for Engine { } } +macro_rules! gen_window_component_functions { + ($fn_name:ident, $name:ident, $data_type:ty) => { + /// see [winit::window::Window::$fn_name] + pub fn $fn_name(&mut self, value: $data_type) { + if let Some(window) = self.window.as_mut() { + window.$fn_name(value); + } else { + self.default_attributes.$name = value; + } + } + }; +} + impl Window { /// create a new window pub fn new(default_attributes: winit::window::WindowAttributes) -> Self { @@ -523,23 +536,28 @@ impl Window { // ====================================================== WINDOW SETTERS ====================================================== // //MARK: SETTERS - /// see [winit::window::Window::set_min_inner_size] - pub fn set_min_inner_size(&mut self, value: Option) { - if let Some(window) = self.window.as_mut() { - window.set_min_inner_size(value); - } else { - self.default_attributes.min_inner_size = value; - } - } - - /// see [winit::window::Window::set_max_inner_size] - pub fn set_max_inner_size(&mut self, value: Option) { - if let Some(window) = self.window.as_mut() { - window.set_max_inner_size(value); - } else { - self.default_attributes.max_inner_size = value; - } - } + gen_window_component_functions!(set_min_inner_size, min_inner_size, Option); + gen_window_component_functions!(set_max_inner_size, max_inner_size, Option); + gen_window_component_functions!(set_resizable, resizable, bool); + gen_window_component_functions!( + set_enabled_buttons, + enabled_buttons, + winit::window::WindowButtons + ); + gen_window_component_functions!(set_maximized, maximized, bool); + gen_window_component_functions!(set_visible, visible, bool); + gen_window_component_functions!(set_transparent, transparent, bool); + gen_window_component_functions!(set_blur, blur, bool); + gen_window_component_functions!(set_decorations, decorations, bool); + gen_window_component_functions!(set_window_icon, window_icon, Option); + gen_window_component_functions!( + set_resize_increments, + resize_increments, + Option + ); + gen_window_component_functions!(set_content_protected, content_protected, bool); + gen_window_component_functions!(set_window_level, window_level, winit::window::WindowLevel); + gen_window_component_functions!(set_cursor, cursor, winit::window::Cursor); /// see [winit::window::Window::set_outer_position] pub fn set_outer_position(&mut self, value: winit::dpi::Position) { @@ -550,24 +568,6 @@ impl Window { } } - /// see [winit::window::Window::set_resizable] - pub fn set_resizable(&mut self, value: bool) { - if let Some(window) = self.window.as_mut() { - window.set_resizable(value); - } else { - self.default_attributes.resizable = value; - } - } - - /// see [winit::window::Window::set_enabled_buttons] - pub fn set_enabled_buttons(&mut self, value: winit::window::WindowButtons) { - if let Some(window) = self.window.as_mut() { - window.set_enabled_buttons(value); - } else { - self.default_attributes.enabled_buttons = value; - } - } - /// see [winit::window::Window::set_title] pub fn set_title(&mut self, value: String) { if let Some(window) = self.window.as_mut() { @@ -577,61 +577,7 @@ impl Window { } } - /// see [winit::window::Window::set_maximized] - pub fn set_maximized(&mut self, value: bool) { - if let Some(window) = self.window.as_mut() { - window.set_maximized(value); - } else { - self.default_attributes.maximized = value; - } - } - - /// see [winit::window::Window::set_visible] - pub fn set_visible(&mut self, value: bool) { - if let Some(window) = self.window.as_mut() { - window.set_visible(value); - } else { - self.default_attributes.visible = value; - } - } - - /// see [winit::window::Window::set_transparent] - pub fn set_transparent(&mut self, value: bool) { - if let Some(window) = self.window.as_mut() { - window.set_transparent(value); - } else { - self.default_attributes.transparent = value; - } - } - - /// see [winit::window::Window::set_blur] - pub fn set_blur(&mut self, value: bool) { - if let Some(window) = self.window.as_mut() { - window.set_blur(value); - } else { - self.default_attributes.blur = value; - } - } - - /// see [winit::window::Window::set_decorations] - pub fn set_decorations(&mut self, value: bool) { - if let Some(window) = self.window.as_mut() { - window.set_decorations(value); - } else { - self.default_attributes.decorations = value; - } - } - - /// see [winit::window::Window::set_window_icon] - pub fn set_window_icon(&mut self, value: Option) { - if let Some(window) = self.window.as_mut() { - window.set_window_icon(value); - } else { - self.default_attributes.window_icon = value; - } - } - - /// see [winit::window::Window::set_preferred_theme] + /// see [winit::window::Window::set_theme] pub fn set_preferred_theme(&mut self, value: Option) { if let Some(window) = self.window.as_mut() { window.set_theme(value); @@ -640,42 +586,6 @@ impl Window { } } - /// see [winit::window::Window::set_resize_increments] - pub fn set_resize_increments(&mut self, value: Option) { - if let Some(window) = self.window.as_mut() { - window.set_resize_increments(value); - } else { - self.default_attributes.resize_increments = value; - } - } - - /// see [winit::window::Window::set_content_protected] - pub fn set_content_protected(&mut self, value: bool) { - if let Some(window) = self.window.as_mut() { - window.set_content_protected(value); - } else { - self.default_attributes.content_protected = value; - } - } - - /// see [winit::window::Window::set_window_level] - pub fn set_window_level(&mut self, value: winit::window::WindowLevel) { - if let Some(window) = self.window.as_mut() { - window.set_window_level(value); - } else { - self.default_attributes.window_level = value; - } - } - - /// see [winit::window::Window::set_cursor] - pub fn set_cursor(&mut self, value: winit::window::Cursor) { - if let Some(window) = self.window.as_mut() { - window.set_cursor(value); - } else { - self.default_attributes.cursor = value; - } - } - /// see [winit::window::Window::set_fullscreen] pub fn set_fullscreen_borderless(&mut self, value: bool) { let full_screen_result = if value { From 5e11dcab46954df13ac3761e428316f565ba7f8a Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Mon, 31 Mar 2025 20:52:17 +0300 Subject: [PATCH 6/9] chore: removed dev example --- examples/dev/README.md | 3 - examples/dev/dev.rs | 164 ------------------ examples/dev/resource/BlueLogoDiscord.png | Bin 10555 -> 0 bytes examples/dev/resource/DefaultTexture.png | Bin 489 -> 0 bytes .../dev/resource/JetBrainsMono-Medium.ttf | Bin 173444 -> 0 bytes examples/dev/resource/default_shader.wgsl | 46 ----- 6 files changed, 213 deletions(-) delete mode 100644 examples/dev/README.md delete mode 100644 examples/dev/dev.rs delete mode 100644 examples/dev/resource/BlueLogoDiscord.png delete mode 100644 examples/dev/resource/DefaultTexture.png delete mode 100644 examples/dev/resource/JetBrainsMono-Medium.ttf delete mode 100644 examples/dev/resource/default_shader.wgsl diff --git a/examples/dev/README.md b/examples/dev/README.md deleted file mode 100644 index 5a72b23..0000000 --- a/examples/dev/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# - -this folder and it's files are for testing and development only. diff --git a/examples/dev/dev.rs b/examples/dev/dev.rs deleted file mode 100644 index 3d9982d..0000000 --- a/examples/dev/dev.rs +++ /dev/null @@ -1,164 +0,0 @@ -#![allow(unused)] - -use blue_engine::{ - primitive_shapes::{cube, square, triangle, uv_sphere}, - uniform_type::Matrix, - utils::default_resources::DEFAULT_MATRIX_4, - Engine, Instance, ObjectSettings, PolygonMode, PowerPreference, RotateAxis, ShaderSettings, - TextureData, Vector3, Vertex, WindowDescriptor, -}; - -fn main() { - let mut engine = Engine::new_config(blue_engine::WindowDescriptor { - power_preference: blue_engine::PowerPreference::HighPerformance, - present_mode: blue_engine::wgpu::PresentMode::Fifo, - ..Default::default() - }) - .expect("win"); - - //let test_instance = Instance::default(); - //println!("{:?}", test_instance.to_raw()); - - let texture = engine - .renderer - .build_texture( - "background", - TextureData::Path("resources/BlueLogoDiscord.png".to_string()), - blue_engine::TextureMode::Clamp, - ) - .unwrap(); - let texture2 = engine - .renderer - .build_texture( - "background", - TextureData::Path("resources/BlueLogoDiscord.png".to_string()), - blue_engine::TextureMode::Clamp, - ) - .unwrap(); - - let texture3 = engine - .renderer - .build_texture( - "background", - TextureData::Path("resources/BlueLogoDiscord.png".to_string()), - blue_engine::TextureMode::Clamp, - ) - .unwrap(); - - square( - "main", - ObjectSettings::default(), - &mut engine.renderer, - &mut engine.objects, - ); - - engine.objects.get_mut("main").unwrap().set_texture(texture); - engine - .objects - .get_mut("main") - .unwrap() - .set_position(Vector3::new(-1f32, 0f32, 0f32)); - - square( - "alt", - ObjectSettings::default(), - &mut engine.renderer, - &mut engine.objects, - ); - engine.objects.get_mut("alt").unwrap().set_texture(texture2); - engine - .objects - .get_mut("alt") - .unwrap() - .set_position(Vector3::new(0.2f32, 0f32, 0.001f32)); - - square( - "alt2", - ObjectSettings::default(), - &mut engine.renderer, - &mut engine.objects, - ); - engine - .objects - .get_mut("alt2") - .unwrap() - .set_texture(texture3); - engine - .objects - .get_mut("alt2") - .unwrap() - .set_position(Vector3::new(-0.2f32, 0f32, 0.001f32)); - - engine.window.set_fullscreen_borderless(true); - - let speed = -0.05; - - let mut last_time = std::time::Instant::now(); - let mut frames = 0; - engine - .update_loop(move |renderer, _window, objects, input, camera, plugins| { - // calculate FPS - let current_time = std::time::Instant::now(); - frames += 1; - if current_time - last_time >= std::time::Duration::from_secs(1) { - println!("{}ms/frame", 1000f32 / frames as f32); - frames = 0; - last_time = current_time; - } - - let sprite = objects.get_mut("alt").unwrap(); - - if input.key_held(blue_engine::KeyCode::Escape) { - _window.close_engine(); - } - if input.key_held(blue_engine::KeyCode::ArrowUp) { - sprite.set_position(( - sprite.position.x, - sprite.position.y - speed, - sprite.position.z, - )); - //lm.ambient_color.data = [1f32, 1f32, 1f32, 1f32]; - } - if input.key_held(blue_engine::KeyCode::ArrowDown) { - sprite.set_position(( - sprite.position.x, - sprite.position.y + speed, - sprite.position.z, - )); - //lm.ambient_color.data = [0.1f32, 0.1f32, 0.1f32, 1f32]; - } - - if input.key_held(blue_engine::KeyCode::ArrowLeft) { - sprite.set_position(( - sprite.position.x + speed, - sprite.position.y, - sprite.position.z, - )); - } - if input.key_held(blue_engine::KeyCode::ArrowRight) { - sprite.set_position(( - sprite.position.x - speed, - sprite.position.y, - sprite.position.z, - )); - } - - if input.key_held(blue_engine::KeyCode::KeyE) { - sprite.set_position(( - sprite.position.x, - sprite.position.y, - sprite.position.z + speed, - )); - } - if input.key_held(blue_engine::KeyCode::KeyQ) { - sprite.set_position(( - sprite.position.x, - sprite.position.y, - sprite.position.z - speed, - )); - } - }) - .expect("Error during update loop"); - - println!("Engine have been closed") -} diff --git a/examples/dev/resource/BlueLogoDiscord.png b/examples/dev/resource/BlueLogoDiscord.png deleted file mode 100644 index 9c579d297336e99c81088a448c199afbe8619c5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10555 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 diff --git a/examples/dev/resource/default_shader.wgsl b/examples/dev/resource/default_shader.wgsl deleted file mode 100644 index b0a8994..0000000 --- a/examples/dev/resource/default_shader.wgsl +++ /dev/null @@ -1,46 +0,0 @@ -// Vertex Stage - -[[block]] -struct VertexUniforms { - camera_matrix: mat4x4; -}; -[[group(1), binding(0)]] -var vertex_uniforms: VertexUniforms; - -struct VertexInput { - [[location(0)]] position: vec3; - [[location(1)]] texture_coordinates: vec2; -}; - -struct VertexOutput { - [[builtin(position)]] position: vec4; - [[location(0)]] texture_coordinates: vec2; -}; - -[[stage(vertex)]] -fn main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; - out.position = vertex_uniforms.camera_matrix * vec4(input.position, 1.0); - out.texture_coordinates = input.texture_coordinates; - return out; -} - -// Fragment Stage - -[[block]] -struct FragmentUniforms { - color: vec4; -}; -[[group(1), binding(1)]] -var fragment_uniforms: FragmentUniforms; - -[[group(0), binding(0)]] -var texture_diffuse: texture_2d; - -[[group(0), binding(1)]] -var sampler_diffuse: sampler; - -[[stage(fragment)]] -fn main(input: VertexOutput) -> [[location(0)]] vec4 { - return textureSample(texture_diffuse, sampler_diffuse, input.texture_coordinates) * fragment_uniforms.color; -} \ No newline at end of file From cb43a08cab00cfa13fb70b4b5692a5cdbaef2a52 Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Mon, 31 Mar 2025 20:52:19 +0300 Subject: [PATCH 7/9] chore: moved from nalgebra to glam, and moved native Vector types to glam Vector --- .gitignore | 1 + Cargo.lock | 12 +- crates/blue_engine_core/Cargo.toml | 2 +- crates/blue_engine_core/src/objects.rs | 199 +--- .../blue_engine_core/src/prelude/imports.rs | 80 +- crates/blue_engine_core/src/prelude/mod.rs | 12 +- .../src/prelude/primitive_shapes.rs | 216 ++-- .../src/prelude/uniform_buffer.rs | 164 --- crates/blue_engine_core/src/prelude/vector.rs | 1022 ----------------- crates/blue_engine_core/src/render.rs | 13 +- crates/blue_engine_core/src/utils/camera.rs | 42 +- .../src/utils/default_resources.rs | 25 +- examples/shapes/square.rs | 26 +- 13 files changed, 258 insertions(+), 1556 deletions(-) delete mode 100644 crates/blue_engine_core/src/prelude/uniform_buffer.rs delete mode 100644 crates/blue_engine_core/src/prelude/vector.rs diff --git a/.gitignore b/.gitignore index 9d03560..164bcbd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ tools res resources/*.png crates/blue_engine_core/Cargo.lock +**dev** \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 46379db..f8e0746 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,9 +322,9 @@ dependencies = [ "crabtime", "downcast", "env_logger", + "glam", "image", "log", - "nalgebra-glm", "pollster", "thiserror 2.0.9", "wgpu", @@ -819,6 +819,16 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3aa70d918d2b234126ff4f850f628f172542bf0603ded26b8ee36e5e22d5f9" +dependencies = [ + "bytemuck", + "serde", +] + [[package]] name = "glow" version = "0.16.0" diff --git a/crates/blue_engine_core/Cargo.toml b/crates/blue_engine_core/Cargo.toml index ea9b13a..c5e8880 100644 --- a/crates/blue_engine_core/Cargo.toml +++ b/crates/blue_engine_core/Cargo.toml @@ -25,7 +25,6 @@ winit = { version = "0.30", features = ["rwh_06"] } wgpu = { version = "24.0.3" } bytemuck = { version = "1.16", features = ["derive"] } downcast = "0.11" -nalgebra-glm = "0.19" thiserror = "2.0" crabtime = "1.1.1" @@ -34,6 +33,7 @@ env_logger = { version = "0.11", optional = true } # android log = { version = "0.4", optional = true } android_logger = { version = "0.15.0", optional = true } +glam = { version = "0.30.1", features = ["bytemuck", "serde"] } [target.'cfg(target_arch = "wasm32")'.dependencies] wgpu = { version = "24.0.3", features = ["webgl"] } diff --git a/crates/blue_engine_core/src/objects.rs b/crates/blue_engine_core/src/objects.rs index 61d2764..35e375a 100644 --- a/crates/blue_engine_core/src/objects.rs +++ b/crates/blue_engine_core/src/objects.rs @@ -4,11 +4,10 @@ * The license is same as the one on the root. */ -use crate::uniform_type::{Array4, Matrix}; -use crate::utils::default_resources::{DEFAULT_MATRIX_4, DEFAULT_SHADER, DEFAULT_TEXTURE}; +use crate::utils::default_resources::{DEFAULT_SHADER, DEFAULT_TEXTURE}; use crate::{ - Pipeline, PipelineData, Renderer, ShaderSettings, StringBuffer, TextureData, Textures, - UnsignedIntType, Vector3, Vertex, glm, pixel_to_cartesian, uniform_type, + Matrix4, Pipeline, PipelineData, Quaternion, Renderer, ShaderSettings, StringBuffer, + TextureData, Textures, UnsignedIntType, Vector3, Vector4, Vertex, pixel_to_cartesian, }; /// Objects make it easier to work with Blue Engine, it automates most of work needed for @@ -35,22 +34,22 @@ pub struct Object { /// Dictates the position of your object in pixels pub position: Vector3, /// Dictates the rotation of your object - pub rotation: Vector3, + pub rotation: Quaternion, // flags the object to be updated until next frame pub(crate) changed: bool, /// Transformation matrices helps to apply changes to your object, including position, orientation, ... /// Best choice is to let the Object system handle it - pub position_matrix: nalgebra_glm::Mat4, + pub position_matrix: Matrix4, /// Transformation matrices helps to apply changes to your object, including position, orientation, ... /// Best choice is to let the Object system handle it - pub scale_matrix: nalgebra_glm::Mat4, + pub scale_matrix: Matrix4, /// Transformation matrices helps to apply changes to your object, including position, orientation, ... /// Best choice is to let the Object system handle it - pub rotation_matrix: nalgebra_glm::Mat4, + pub rotation_quaternion: Quaternion, /// Transformation matrix, but inversed - pub inverse_transformation_matrix: crate::uniform_type::Matrix, + pub inverse_transformation_matrix: Matrix4, /// The main color of your object - pub color: crate::uniform_type::Array4, + pub color: Vector4, /// A struct making it easier to manipulate specific parts of shader pub shader_builder: crate::objects::ShaderBuilder, /// Shader settings @@ -111,7 +110,7 @@ crate::macros::impl_deref!(ObjectStorage, std::collections::HashMap &mut Self { - let mut rotation_matrix = self.rotation_matrix; - let axis = match axis { - RotateAxis::X => { - self.rotation.x += angle; - Vector3::x_axis() - } - RotateAxis::Y => { - self.rotation.y += angle; - Vector3::y_axis() - } - RotateAxis::Z => { - self.rotation.z += angle; - Vector3::z_axis() - } - }; - - rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, angle.to_radians(), &axis.into()); - self.rotation_matrix = rotation_matrix; - self.inverse_matrices(); - - self.changed = true; - self - } - - fn rotation_single_axis(angle: f32, axis_from: usize, axis_into: usize) -> nalgebra_glm::Mat4 { - // angle.cos(), -angle.sin() - // angle.sin(), angle.cos() - let mut result = nalgebra_glm::Mat4::identity(); - - result[(axis_from, axis_from)] = angle.cos() as f32; - result[(axis_from, axis_into)] = -angle.sin() as f32; - result[(axis_into, axis_from)] = angle.sin() as f32; - result[(axis_into, axis_into)] = angle.cos() as f32; - - result - } - fn rotation_full(euler_angles: nalgebra_glm::Vec3) -> nalgebra_glm::Mat4 { - const X: usize = 0; - const Y: usize = 1; - const Z: usize = 2; - - Self::rotation_single_axis(euler_angles[2], X, Y) * // Rotation around Z (rotation of X into Y) - Self::rotation_single_axis(euler_angles[1], Z, X) * // Rotation around Y (rotation of Z into X) - Self::rotation_single_axis(euler_angles[0], Y, Z) // Rotation around X (rotation of Y into Z) - } - /// Sets the rotation of the object in the axis you specify - pub fn set_rotation(&mut self, amount: RotateAmount, axis: RotateAxis) -> &mut Self { - let amount_radians = match amount { - RotateAmount::Radians(amount) => amount, - RotateAmount::Degrees(amount) => amount.to_radians(), - }; - match axis { - RotateAxis::X => { - self.rotation.x = amount_radians; - Vector3::x_axis() - } - RotateAxis::Y => { - self.rotation.y = amount_radians; - Vector3::y_axis() - } - RotateAxis::Z => { - self.rotation.z = amount_radians; - Vector3::z_axis() - } - }; - - self.rotation_matrix = Self::rotation_full(self.rotation.into()); + /// + /// This function does NOT normalize the rotation. + pub fn set_rotation(&mut self, rotation: Vector3) -> &mut Self { + self.rotation_quaternion = Quaternion::from_rotation_x(rotation.x) + * Quaternion::from_rotation_y(rotation.y) + * Quaternion::from_rotation_z(rotation.z); self.inverse_matrices(); self.changed = true; @@ -428,29 +354,27 @@ impl Object { /// Rotates the object in the axis you specify pub fn rotate(&mut self, amount: RotateAmount, axis: RotateAxis) -> &mut Self { - let mut rotation_matrix = self.rotation_matrix; - let amount_radians = match amount { RotateAmount::Radians(amount) => amount, RotateAmount::Degrees(amount) => amount.to_radians(), }; + let axis = match axis { RotateAxis::X => { self.rotation.x += amount_radians; - Vector3::x_axis() + Quaternion::from_rotation_x(amount_radians) } RotateAxis::Y => { self.rotation.y += amount_radians; - Vector3::y_axis() + Quaternion::from_rotation_y(amount_radians) } RotateAxis::Z => { self.rotation.z += amount_radians; - Vector3::z_axis() + Quaternion::from_rotation_z(amount_radians) } }; - rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, amount_radians, &axis.into()); - self.rotation_matrix = rotation_matrix; + self.rotation_quaternion *= axis; self.inverse_matrices(); self.changed = true; @@ -460,10 +384,7 @@ impl Object { /// Moves the object by the amount you specify in the axis you specify pub fn set_translation(&mut self, new_pos: impl Into) -> &mut Self { self.position -= new_pos.into(); - - let mut position_matrix = self.position_matrix; - position_matrix = nalgebra_glm::translate(&position_matrix, &self.position.into()); - self.position_matrix = position_matrix; + self.position_matrix *= Matrix4::from_translation(self.position); self.inverse_matrices(); self.changed = true; @@ -495,17 +416,17 @@ impl Object { // 0.0, 0.0, 0.0, 1.0; // ] - self.position_matrix = DEFAULT_MATRIX_4.to_im(); - self.position_matrix[(0, 3)] = self.position[0]; - self.position_matrix[(1, 3)] = self.position[1]; - self.position_matrix[(2, 3)] = self.position[2]; + self.position_matrix = Matrix4 { + x_axis: Vector4::new(1f32, 0f32, 0f32, self.position[0]), + y_axis: Vector4::new(0f32, 1f32, 0f32, self.position[1]), + z_axis: Vector4::new(0f32, 0f32, 1f32, self.position[2]), + w_axis: Vector4::new(0f32, 0f32, 0f32, 1f32), + }; } /// Changes the color of the object. If textures exist, the color of textures will change pub fn set_color(&mut self, red: f32, green: f32, blue: f32, alpha: f32) -> &mut Self { - self.color = Array4 { - data: [red, green, blue, alpha], - }; + self.color = Vector4::new(red, green, blue, alpha); self.changed = true; self } @@ -541,10 +462,11 @@ impl Object { /// build an inverse of the transformation matrix to be sent to the gpu for lighting and other things. pub fn inverse_matrices(&mut self) { - self.inverse_transformation_matrix = - Matrix::from_im(nalgebra_glm::transpose(&nalgebra_glm::inverse( - &(self.position_matrix * self.rotation_matrix * self.scale_matrix), - ))); + self.inverse_transformation_matrix = Matrix4::transpose(&Matrix4::inverse( + &(self.position_matrix + * Matrix4::from_quat(self.rotation_quaternion) + * self.scale_matrix), + )); } /// Update and apply changes done to an object @@ -620,9 +542,7 @@ impl Object { pub fn update_uniform_buffer(&mut self, renderer: &mut Renderer) { self.uniform_buffers[0] = renderer.build_uniform_buffer_part( "Transformation Matrix", - uniform_type::Matrix::from_im( - self.position_matrix * self.rotation_matrix * self.scale_matrix, - ), + self.position_matrix * Matrix4::from_quat(self.rotation_quaternion) * self.scale_matrix, ); self.uniform_buffers[1] = renderer.build_uniform_buffer_part("Color", self.color); @@ -639,9 +559,7 @@ impl Object { ) -> crate::UniformBuffers { self.uniform_buffers[0] = renderer.build_uniform_buffer_part( "Transformation Matrix", - uniform_type::Matrix::from_im( - self.position_matrix * self.rotation_matrix * self.scale_matrix, - ), + self.position_matrix * Matrix4::from_quat(self.rotation_quaternion) * self.scale_matrix, ); self.uniform_buffers[1] = renderer.build_uniform_buffer_part("Color", self.color); @@ -796,12 +714,15 @@ impl Instance { /// Gathers all information and builds a Raw Instance to be sent to GPU pub fn to_raw(&self) -> InstanceRaw { - let position_matrix = glm::translate(&DEFAULT_MATRIX_4.to_im(), &self.position.into()); - let rotation_matrix = - nalgebra_glm::rotate(&DEFAULT_MATRIX_4.to_im(), 0f32, &self.rotation.into()); - let scale_matrix = glm::scale(&DEFAULT_MATRIX_4.to_im(), &self.scale.into()); + let position_matrix = Matrix4::IDENTITY * Matrix4::from_translation(self.position); + let rotation_matrix = Matrix4::from_quat( + Quaternion::from_rotation_x(self.rotation.x) + * Quaternion::from_rotation_y(self.rotation.y) + * Quaternion::from_rotation_z(self.rotation.z), + ); + let scale_matrix = Matrix4::IDENTITY * Matrix4::from_scale(self.scale); InstanceRaw { - model: Matrix::from_im(position_matrix * rotation_matrix * scale_matrix), + model: position_matrix * rotation_matrix * scale_matrix, } } diff --git a/crates/blue_engine_core/src/prelude/imports.rs b/crates/blue_engine_core/src/prelude/imports.rs index 657e093..19d9964 100644 --- a/crates/blue_engine_core/src/prelude/imports.rs +++ b/crates/blue_engine_core/src/prelude/imports.rs @@ -1,72 +1,50 @@ -/// Shaders are programs that runs on the GPU -pub type Shaders = wgpu::RenderPipeline; -/// Uniform Buffers are small amount of data that are sent from CPU to GPU -pub type UniformBuffers = wgpu::BindGroup; -/// Textures are image data that are sent to GPU to be set to a surface -pub type Textures = wgpu::BindGroup; -/// Primitive type the input mesh is composed of. -pub type ShaderPrimitive = wgpu::PrimitiveTopology; -/// Format of indices used with pipeline. -pub type IndexFormat = wgpu::IndexFormat; -/// Vertex winding order which classifies the "front" face of a triangle. -pub type FrontFace = wgpu::FrontFace; -/// Face of a vertex. -pub type CullMode = wgpu::Face; -/// Type of drawing mode for polygons -pub type PolygonMode = wgpu::PolygonMode; -/// Power Preference when choosing a physical adapter. -pub type PowerPreference = wgpu::PowerPreference; - -/// Pod trait for custom uniform buffer structure -pub use bytemuck::Pod; -/// Zeroable trait for custom uniform buffer structure -pub use bytemuck::Zeroable; +pub use downcast; +pub use image; +pub use wgpu; +pub use winit; -/// Backends pub use wgpu::Backends; -/// Encoder from wgpu pub use wgpu::CommandEncoder; pub use wgpu::LoadOp; -/// Memory hints pub use wgpu::MemoryHints; pub use wgpu::Operations; pub use wgpu::RenderPassColorAttachment; pub use wgpu::RenderPassDescriptor; -/// Surface Texture pub use wgpu::TextureView; -/// Depth format -pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; -/// Input helper -pub use crate::utils::winit_input_helper::WinitInputHelper as InputHelper; -/// all of downcast -pub use downcast; -/// all of image -pub use image; -/// all of nalgebra_glm -pub use nalgebra_glm as glm; -/// all of wgpu -pub use wgpu; -/// all of winit -pub use winit; -/// WindowSize pub use winit::dpi::*; -/// Device Events pub use winit::event::DeviceEvent; -/// Element State pub use winit::event::ElementState; -/// Winit Events pub use winit::event::Event; -/// Keyboard input identifier pub use winit::event::KeyEvent; -/// The mouse button identifier pub use winit::event::MouseButton; -/// WindowEvents pub use winit::event::WindowEvent; -/// Event Loop pub use winit::event_loop::EventLoop; -/// Keyboard keys identifier pub use winit::keyboard::Key; pub use winit::keyboard::KeyCode; -/// Fullscreen enum pub use winit::window::Fullscreen; + +// Math types +pub type Vector2 = glam::Vec2; +pub type Vector3 = glam::Vec3; +pub type Vector4 = glam::Vec4; +pub type Matrix2 = glam::Mat2; +pub type Matrix3 = glam::Mat3; +pub type Matrix4 = glam::Mat4; +pub type Quaternion = glam::Quat; + +/// Input helper +pub use crate::utils::winit_input_helper::WinitInputHelper as InputHelper; +pub use bytemuck::Pod; +pub use bytemuck::Zeroable; + +pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; +pub type Shaders = wgpu::RenderPipeline; +pub type UniformBuffers = wgpu::BindGroup; +pub type Textures = wgpu::BindGroup; +pub type ShaderPrimitive = wgpu::PrimitiveTopology; +pub type IndexFormat = wgpu::IndexFormat; +pub type FrontFace = wgpu::FrontFace; +pub type CullMode = wgpu::Face; +pub type PolygonMode = wgpu::PolygonMode; +pub type PowerPreference = wgpu::PowerPreference; diff --git a/crates/blue_engine_core/src/prelude/mod.rs b/crates/blue_engine_core/src/prelude/mod.rs index 08bb3a5..bd4545b 100644 --- a/crates/blue_engine_core/src/prelude/mod.rs +++ b/crates/blue_engine_core/src/prelude/mod.rs @@ -2,14 +2,9 @@ use downcast::{Any, downcast}; /// re-exports from dependencies that are useful pub mod imports; pub use imports::*; -/// few commonly used uniform buffer structures -pub mod uniform_buffer; -pub use uniform_buffer::*; /// contains definition for some 2D and 3D shapes. They are basic shapes and /// can be used as examples of how to create your own content. pub mod primitive_shapes; -/// contains definition for 2D and 3D vectors. -pub mod vector; pub use crate::camera::{Camera, CameraContainer, Projection}; pub use crate::definition::{ Pipeline, PipelineData, TextureData, TextureMode, VertexBuffers, pixel_to_cartesian, @@ -19,7 +14,6 @@ pub use crate::objects::{ }; pub use crate::render::Renderer; pub use crate::window::{ShaderSettings, Window, WindowDescriptor}; -pub use vector::{Vector2, Vector3}; /// The uint type used for indices and more #[cfg(not(feature = "u32"))] @@ -73,11 +67,11 @@ pub mod macros { #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct Vertex { /// Contains position data for the vertex in 3D space - pub position: Vector3, + pub position: [f32; 3], /// Contains uv position data for the vertex - pub uv: Vector2, + pub uv: [f32; 2], /// Contains the normal face of the vertex - pub normal: Vector3, + pub normal: [f32; 3], } impl Vertex { pub(crate) fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { diff --git a/crates/blue_engine_core/src/prelude/primitive_shapes.rs b/crates/blue_engine_core/src/prelude/primitive_shapes.rs index 10fa9bb..18d85cf 100644 --- a/crates/blue_engine_core/src/prelude/primitive_shapes.rs +++ b/crates/blue_engine_core/src/prelude/primitive_shapes.rs @@ -3,7 +3,7 @@ */ use crate::{ - ObjectStorage, Renderer, StringBuffer, Vector2, Vector3, + ObjectStorage, Renderer, StringBuffer, prelude::{ObjectSettings, UnsignedIntType, Vertex}, }; use std::f32::consts::PI; @@ -19,19 +19,19 @@ pub fn triangle( name.clone(), vec![ Vertex { - position: Vector3::new(0.0, 1.0, 0.0), - uv: Vector2::new(0.5, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [0.0, 1.0, 0.0], + uv: [0.5, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, -1.0, 0.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, -1.0, 0.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, -1.0, 0.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, -1.0, 0.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, ], vec![0, 1, 2], @@ -51,24 +51,24 @@ pub fn square( name.clone(), vec![ Vertex { - position: Vector3::new(1.0, 1.0, 0.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, 1.0, 0.0], + uv: [1.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, -1.0, 0.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, -1.0, 0.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, -1.0, 0.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, -1.0, 0.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, 1.0, 0.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, 1.0, 0.0], + uv: [0.0, 0.0], + normal: [0.0, 0.0, 0.0], }, ], vec![2, 1, 0, 2, 0, 3], @@ -90,24 +90,24 @@ pub fn rectangle( name.clone(), vec![ Vertex { - position: Vector3::new(width / 2.0, height / 2.0, 0.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [width / 2.0, height / 2.0, 0.0], + uv: [1.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(width / 2.0, -height / 2.0, 0.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [width / 2.0, -height / 2.0, 0.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-width / 2.0, -height / 2.0, 0.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-width / 2.0, -height / 2.0, 0.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-width / 2.0, height / 2.0, 0.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-width / 2.0, height / 2.0, 0.0], + uv: [0.0, 0.0], + normal: [0.0, 0.0, 0.0], }, ], vec![2, 1, 0, 2, 0, 3], @@ -125,129 +125,129 @@ pub fn cube(name: impl StringBuffer, renderer: &mut Renderer, objects: &mut Obje vec![ // Front Face Vertex { - position: Vector3::new(-1.0, -1.0, 1.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, -1.0, 1.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, -1.0, 1.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, -1.0, 1.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, 1.0, 1.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, 1.0, 1.0], + uv: [1.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, 1.0, 1.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, 1.0, 1.0], + uv: [0.0, 0.0], + normal: [0.0, 0.0, 0.0], }, // Back Face Vertex { - position: Vector3::new(-1.0, 1.0, -1.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, 1.0, -1.0], + uv: [1.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, 1.0, -1.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, 1.0, -1.0], + uv: [0.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, -1.0, -1.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, -1.0, -1.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, -1.0, -1.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, -1.0, -1.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, // Right face Vertex { - position: Vector3::new(1.0, -1.0, -1.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, -1.0, -1.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, 1.0, -1.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, 1.0, -1.0], + uv: [1.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, 1.0, 1.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, 1.0, 1.0], + uv: [0.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, -1.0, 1.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, -1.0, 1.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, // Left face Vertex { - position: Vector3::new(-1.0, -1.0, 1.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, -1.0, 1.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, 1.0, 1.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, 1.0, 1.0], + uv: [1.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, 1.0, -1.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, 1.0, -1.0], + uv: [0.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, -1.0, -1.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, -1.0, -1.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, // Top face Vertex { - position: Vector3::new(1.0, 1.0, -1.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, 1.0, -1.0], + uv: [1.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, 1.0, -1.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, 1.0, -1.0], + uv: [0.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, 1.0, 1.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, 1.0, 1.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, 1.0, 1.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, 1.0, 1.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, // Bottom face Vertex { - position: Vector3::new(1.0, -1.0, 1.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, -1.0, 1.0], + uv: [1.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, -1.0, 1.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, -1.0, 1.0], + uv: [0.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, -1.0, -1.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, -1.0, -1.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, -1.0, -1.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, -1.0, -1.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, ], vec![ @@ -296,8 +296,8 @@ pub fn uv_sphere( vertices.push(Vertex { position: [x, y, z].into(), - uv: Vector2::new((j as f32) / sectors, (i as f32) / stacks), - normal: Vector3::new(x * length_inv, y * length_inv, z * length_inv), + uv: [(j as f32) / sectors, (i as f32) / stacks], + normal: [x * length_inv, y * length_inv, z * length_inv], }); } } diff --git a/crates/blue_engine_core/src/prelude/uniform_buffer.rs b/crates/blue_engine_core/src/prelude/uniform_buffer.rs deleted file mode 100644 index 9f4fcd1..0000000 --- a/crates/blue_engine_core/src/prelude/uniform_buffer.rs +++ /dev/null @@ -1,164 +0,0 @@ -/// A container for uniform buffer types -pub mod uniform_type { - - /// 4 by 4, 32 bit float matrix uniform buffer - #[repr(C)] - #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] - pub struct Matrix { - /// data structure for the matrix - pub data: [[f32; 4]; 4], - } - impl Matrix { - /// Replaces it's values by the new values provided - pub fn update(&mut self, uniform: Matrix) { - self.data = uniform.data; - } - - /// Converts internal matrix to the uniform matrix - pub fn from_im(matrix: nalgebra_glm::Mat4) -> Self { - let mtx = matrix.as_slice(); - - Self { - data: [ - [mtx[0], mtx[1], mtx[2], mtx[3]], - [mtx[4], mtx[5], mtx[6], mtx[7]], - [mtx[8], mtx[9], mtx[10], mtx[11]], - [mtx[12], mtx[13], mtx[14], mtx[15]], - ], - } - } - - /// Converts uniform matrix to internal matrix - pub fn to_im(&self) -> nalgebra_glm::Mat4 { - let mtx = self.data; - - nalgebra_glm::mat4( - mtx[0][0], mtx[0][1], mtx[0][2], mtx[0][3], mtx[1][0], mtx[1][1], mtx[1][2], - mtx[1][3], mtx[2][0], mtx[2][1], mtx[2][2], mtx[2][3], mtx[3][0], mtx[3][1], - mtx[3][2], mtx[3][3], - ) - } - } - impl std::ops::Mul for Matrix { - type Output = Matrix; - - fn mul(self, rhs: Self) -> Self::Output { - let a = self.data; - let b = rhs.data; - Matrix { - data: [ - [ - a[0][0] * b[0][0], - a[0][1] * b[1][0], - a[0][2] * b[2][0], - a[0][3] * b[3][0], - ], - [ - a[1][0] * b[0][1], - a[1][1] * b[1][1], - a[1][2] * b[2][1], - a[1][3] * b[3][1], - ], - [ - a[2][0] * b[0][2], - a[2][1] * b[1][2], - a[2][2] * b[2][2], - a[2][3] * b[3][2], - ], - [ - a[3][0] * b[0][3], - a[3][1] * b[1][3], - a[3][2] * b[2][3], - a[3][3] * b[3][3], - ], - ], - } - } - } - - /// An array with length 3, each 32 bit float value, uniform buffer - #[repr(C)] - #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] - pub struct Array3 { - /// data structure for the array - pub data: [f32; 3], - } - impl std::ops::Mul for Array3 { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - Self { - data: [ - self.data[0] * rhs.data[0], - self.data[1] * rhs.data[1], - self.data[2] * rhs.data[2], - ], - } - } - } - impl std::ops::Mul for Array3 { - type Output = Self; - - fn mul(self, rhs: f32) -> Self::Output { - Self { - data: [self.data[0] * rhs, self.data[1] * rhs, self.data[2] * rhs], - } - } - } - - /// An array with length 4, each 32 bit float value, uniform buffer - #[repr(C)] - #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] - pub struct Array4 { - /// data structure for the array - pub data: [f32; 4], - } - impl Array4 { - /// Replaces it's values by the new values provided - pub fn update(&mut self, uniform: Array4) { - self.data = uniform.data; - } - } - impl std::ops::Mul for Array4 { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - Array4 { - data: [ - self.data[0] * rhs.data[0], - self.data[1] * rhs.data[1], - self.data[2] * rhs.data[2], - self.data[3] * rhs.data[3], - ], - } - } - } - impl std::ops::Mul for Array4 { - type Output = Array4; - - fn mul(self, rhs: f32) -> Self::Output { - Array4 { - data: [ - self.data[0] * rhs, - self.data[1] * rhs, - self.data[2] * rhs, - self.data[3] * rhs, - ], - } - } - } - - /// A 32 bit float uniform buffer - #[repr(C)] - #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] - pub struct Float { - /// data structure for the float - pub data: f32, - } - impl Float { - /// Replaces it's values by the new values provided - pub fn update(&mut self, uniform: Float) { - self.data = uniform.data; - } - } -} diff --git a/crates/blue_engine_core/src/prelude/vector.rs b/crates/blue_engine_core/src/prelude/vector.rs deleted file mode 100644 index 8bdac4e..0000000 --- a/crates/blue_engine_core/src/prelude/vector.rs +++ /dev/null @@ -1,1022 +0,0 @@ -use crate::{RotateAmount, RotateAxis}; -use bytemuck::{Pod, Zeroable}; -use std::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; - -#[crabtime::function] -fn gen_vectors(parts: Vec) { - fn part_builder(parts: &Vec, modification: F) -> String - where - F: Fn(&String) -> String, - { - parts.iter().map(modification).collect::>().join("") - } - - let parts_len = parts.len(); - let struct_fields = part_builder(&parts, |part| format!("///\npub {part}: f32,")); - - let fn_parameter = part_builder(&parts, |part| format!("{part}: f32,")); - let new_fn_parameter = part_builder(&parts, |part| format!("{part},")); - let new_fn = crabtime::quote! { - /// - pub const fn new({{fn_parameter}}) -> Self { - Self { {{new_fn_parameter}} } - } - }; - - crabtime::output! { - #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Zeroable)] - #[repr(C)] - /// General purposes {{parts_len}}D vector - pub struct Vector{{parts_len}} { - {{struct_fields}} - } - - impl Vector{{parts_len}} { - {{new_fn}} - } - } -} - -gen_vectors!(["x", "y"]); -gen_vectors!(["x", "y", "z"]); -gen_vectors!(["x", "y", "z", "a"]); - -// Constructors -impl Vector3 { - /// A vector with all components set to 0. - pub const ZERO: Self = Self::new(0.0, 0.0, 0.0); - /// A vector with all components set to 1. - pub const ONE: Self = Self::new(1.0, 1.0, 1.0); - /// A vector with all components set to 0 except for the x component, which is set to 1. - pub const UNIT_X: Self = Self::new(1.0, 0.0, 0.0); - /// A vector with all components set to 0 except for the y component, which is set to 1. - pub const UNIT_Y: Self = Self::new(0.0, 1.0, 0.0); - /// A vector with all components set to 0 except for the z component, which is set to 1. - pub const UNIT_Z: Self = Self::new(0.0, 0.0, 1.0); - - /// Returns a vector with all components set to 0 except for the x component, which is set to 1. - pub const fn x_axis() -> Self { - Self::new(1.0, 0.0, 0.0) - } - /// Returns a vector with all components set to 0 except for the y component, which is set to 1. - pub const fn y_axis() -> Self { - Self::new(0.0, 1.0, 0.0) - } - /// Returns a vector with all components set to 0 except for the z component, which is set to 1. - pub const fn z_axis() -> Self { - Self::new(0.0, 0.0, 1.0) - } - /// Returns a 2D vector with the x and y coordinates of the 3D vector - pub const fn xy(&self) -> Vector2 { - Vector2::new(self.x, self.y) - } - /// Returns a 2D vector with the x and z coordinates of the 3D vector - pub const fn xz(&self) -> Vector2 { - Vector2::new(self.x, self.z) - } - /// Returns a 2D vector with the y and z coordinates of the 3D vector - pub const fn yz(&self) -> Vector2 { - Vector2::new(self.y, self.z) - } -} - -// Methods -impl Vector3 { - /// Returns a new vector with all components in absolute values (i.e. positive). - pub fn abs(&self) -> Self { - Self { - x: self.x.abs(), - y: self.y.abs(), - z: self.z.abs(), - } - } - /// Returns the unsigned minimum angle to the given vector, in radians. - pub fn angle_to(&self, to: Self) -> f32 { - let dot = self.dot(to); - let len = self.length() * to.length(); - (dot / len).acos() - } - /// Returns the vector "bounced off" from a plane defined by the given normal ``n``. - /// - ///> Note: bounce performs the operation that most engines and frameworks call ``reflect()``. - pub fn bounce(&self, n: Self) -> Self { - *self - n * 2.0 * self.dot(n) - } - /// Returns a new vector with all components rounded up (towards positive infinity). - pub fn ceil(&self) -> Self { - Self { - x: self.x.ceil(), - y: self.y.ceil(), - z: self.z.ceil(), - } - } - /// Returns a new vector with all components clamped between the components of ``min`` and ``max`` - pub fn clamp(&self, min: f32, max: f32) -> Self { - Self { - x: self.x.clamp(min, max), - y: self.y.clamp(min, max), - z: self.z.clamp(min, max), - } - } - /// Returns a new vector with all components clamped between ``min`` and ``max`` - pub fn clampf(&self, min: f32, max: f32) -> Self { - Self { - x: self.x.clamp(min, max), - y: self.y.clamp(min, max), - z: self.z.clamp(min, max), - } - } - /// Returns the cross product of this vector and ``with``. - /// - /// This returns a vector perpendicular to both ``self`` and ``with``, which - /// would be the normal vector of the plane defined by the two vectors. - /// As there are two such vectors, in opposite directions, this method - /// returns the vector defined by a right-handed coordinate system. - /// If the two vectors are parallel this returns an empty vector, making - /// it useful for testing if two vectors are parallel. - pub const fn cross(&self, with: Self) -> Self { - Self { - x: self.y * with.z - self.z * with.y, - y: self.z * with.x - self.x * with.z, - z: self.x * with.y - self.y * with.x, - } - } - /// Returns the normalized vector pointing from this vector to ``to``. This - /// is equivalent to using ``(b - a).normalized()``. - pub fn direction_to(&self, to: Self) -> Self { - (to - *self).normalize() - } - /// Returns the squared distance between this vector and ``to``. - /// - /// This method runs faster than [``Vector3::distance_to``], so prefer it if - /// you need to compare vectors or need the squared distance for some formula. - pub fn distance_squared_to(&self, to: Self) -> f32 { - (*self - to).length_squared() - } - /// Returns the distance between this vector and ``to``. - pub fn distance_to(&self, to: Self) -> f32 { - (*self - to).length() - } - /// Returns the dot product of this vector and ``with``. This can be used to - /// compare the angle between two vectors. For example, this can be used to - /// determine whether an enemy is facing the player. - /// - /// The dot product will be ``0`` for a right angle (90 degrees), greater than - /// 0 for angles narrower than 90 degrees and lower than 0 for angles wider than 90 degrees. - /// - /// When using unit (normalized) vectors, the result will always be between - /// ``-1.0`` (180 degree angle) when the vectors are facing opposite directions, - /// and ``1.0`` (0 degree angle) when the vectors are aligned. - /// - ///> Note: ``a.dot(b)`` is equivalent to ``b.dot(a)``. - pub fn dot(&self, with: Self) -> f32 { - self.x * with.x + self.y * with.y + self.z * with.z - } - /// Returns a new vector with all components rounded down (towards negative infinity). - pub fn floor(&self) -> Self { - Self { - x: self.x.floor(), - y: self.y.floor(), - z: self.z.floor(), - } - } - /// Returns the inverse of the vector. This is the same as: - ///```rs - /// Vector3 { - /// x: 1.0 / self.x, - /// y: 1.0 / self.y, - /// z: 1.0 / self.z - /// } - /// ``` - pub const fn inverse(&self) -> Self { - Self { - x: 1.0 / self.x, - y: 1.0 / self.y, - z: 1.0 / self.z, - } - } - /// Returns true if the vector is normalized, i.e. its length is approximately equal to 1. - #[must_use] - pub fn is_normalized(&self) -> bool { - (self.x * self.x + self.y * self.y + self.z * self.z - 1.0).abs() < 0.0001 - } - /// Returns the length (magnitude) of this vector. - pub fn length(&self) -> f32 { - (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() - } - /// Returns the squared length (squared magnitude) of this vector. - /// - /// This method runs faster than [`Vector3::length`], so prefer it if you - /// need to compare vectors or need the squared distance for some formula. - pub const fn length_squared(&self) -> f32 { - self.x * self.x + self.y * self.y + self.z * self.z - } - /// Returns the result of the linear interpolation between this vector and ``to`` by amount ``weight``. - /// ``weight`` is on the range of ``0.0`` to ``1.0``, representing the amount of interpolation. - pub fn lerp(&self, to: Self, weight: f32) -> Self { - *self * (1.0 - weight) + to * weight - } - /// Returns the vector with a maximum length by limiting its length to ``length``. - pub fn limit_length(&self, max_length: f32) -> Self { - let length = self.length(); - if length > max_length { - *self * (max_length / length) - } else { - *self - } - } - /// Returns the component-wise minimum of ``self`` and ``with``, equivalent to: - /// ```rs - /// Vector3 { - /// x: self.x.max(with.x), - /// y: self.y.max(with.y), - /// z: self.z.max(with.z), - /// } - /// ``` - pub fn max(&self, with: Vector3) -> Self { - Self { - x: self.x.max(with.x), - y: self.y.max(with.y), - z: self.z.max(with.z), - } - } - /// Returns the component-wise maximum of ``self`` and ``with``, equivalent to: - /// ```rs - /// Vector3 { - /// x: self.x.max(with), - /// y: self.y.max(with), - /// z: self.z.max(with), - /// } - /// ``` - pub fn maxf(&self, with: f32) -> Self { - Self { - x: self.x.max(with), - y: self.y.max(with), - z: self.z.max(with), - } - } - /// Returns the component-wise minimum of ``self`` and ``with``, equivalent to: - /// ```rs - /// Vector3 { - /// x: self.x.min(with.x), - /// y: self.y.min(with.y), - /// z: self.z.min(with.z), - /// } - /// ``` - pub fn min(&self, with: Vector3) -> Self { - Self { - x: self.x.min(with.x), - y: self.y.min(with.y), - z: self.z.min(with.z), - } - } - /// Returns the component-wise minimum of ``self`` and ``with``, equivalent to: - /// ```rs - /// Vector3 { - /// x: self.x.min(with), - /// y: self.y.min(with), - /// z: self.z.min(with), - /// } - /// ``` - pub fn minf(&self, with: f32) -> Self { - Self { - x: self.x.min(with), - y: self.y.min(with), - z: self.z.min(with), - } - } - /// Normalize the vector (make the length of the vector 1) - pub fn normalize(&self) -> Self { - let length = (self.x * self.x + self.y * self.y + self.z * self.z).sqrt(); - Self { - x: self.x / length, - y: self.y / length, - z: self.z / length, - } - } - /// Returns a new vector with all components rounded to the nearest - /// integer, with halfway cases rounded away from zero. - pub fn round(&self) -> Self { - Self { - x: self.x.round(), - y: self.y.round(), - z: self.z.round(), - } - } - /// Rotates the vector around the given axis by the given angle. - pub fn rotate(&self, axis: RotateAxis, angle: RotateAmount) -> Self { - let angle = match angle { - RotateAmount::Degrees(d) => d.to_radians(), - RotateAmount::Radians(r) => r, - }; - match axis { - RotateAxis::X => { - let cos = angle.cos(); - let sin = angle.sin(); - Self { - x: self.x, - y: self.y * cos - self.z * sin, - z: self.y * sin + self.z * cos, - } - } - RotateAxis::Y => { - let cos = angle.cos(); - let sin = angle.sin(); - Self { - x: self.x * cos + self.z * sin, - y: self.y, - z: -self.x * sin + self.z * cos, - } - } - RotateAxis::Z => { - let cos = angle.cos(); - let sin = angle.sin(); - Self { - x: self.x * cos - self.y * sin, - y: self.x * sin + self.y * cos, - z: self.z, - } - } - } - } -} - -unsafe impl Send for Vector3 {} -unsafe impl Sync for Vector3 {} - -unsafe impl Pod for Vector3 {} - -impl Add for Vector3 { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { - x: self.x + other.x, - y: self.y + other.y, - z: self.z + other.z, - } - } -} - -impl Add for Vector3 { - type Output = Self; - - fn add(self, scalar: f32) -> Self { - Self { - x: self.x + scalar, - y: self.y + scalar, - z: self.z + scalar, - } - } -} - -impl AddAssign for Vector3 { - fn add_assign(&mut self, other: Self) { - self.x += other.x; - self.y += other.y; - self.z += other.z; - } -} - -impl AddAssign for Vector3 { - fn add_assign(&mut self, scalar: f32) { - self.x += scalar; - self.y += scalar; - self.z += scalar; - } -} - -impl Sub for Vector3 { - type Output = Self; - - fn sub(self, other: Self) -> Self { - Self { - x: self.x - other.x, - y: self.y - other.y, - z: self.z - other.z, - } - } -} - -impl Sub for Vector3 { - type Output = Self; - - fn sub(self, scalar: f32) -> Self { - Self { - x: self.x - scalar, - y: self.y - scalar, - z: self.z - scalar, - } - } -} - -impl SubAssign for Vector3 { - fn sub_assign(&mut self, other: Self) { - self.x -= other.x; - self.y -= other.y; - self.z -= other.z; - } -} - -impl SubAssign for Vector3 { - fn sub_assign(&mut self, scalar: f32) { - self.x -= scalar; - self.y -= scalar; - self.z -= scalar; - } -} - -impl Mul for Vector3 { - type Output = Self; - - fn mul(self, other: Self) -> Self { - Self { - x: self.x * other.x, - y: self.y * other.y, - z: self.z * other.z, - } - } -} - -impl Mul for Vector3 { - type Output = Self; - - fn mul(self, scalar: f32) -> Self { - Self { - x: self.x * scalar, - y: self.y * scalar, - z: self.z * scalar, - } - } -} - -impl MulAssign for Vector3 { - fn mul_assign(&mut self, other: Self) { - self.x *= other.x; - self.y *= other.y; - self.z *= other.z; - } -} - -impl MulAssign for Vector3 { - fn mul_assign(&mut self, scalar: f32) { - self.x *= scalar; - self.y *= scalar; - self.z *= scalar; - } -} - -impl Div for Vector3 { - type Output = Self; - - fn div(self, other: Self) -> Self { - Self { - x: self.x / other.x, - y: self.y / other.y, - z: self.z / other.z, - } - } -} - -impl Div for Vector3 { - type Output = Self; - - fn div(self, scalar: f32) -> Self { - Self { - x: self.x / scalar, - y: self.y / scalar, - z: self.z / scalar, - } - } -} - -impl DivAssign for Vector3 { - fn div_assign(&mut self, other: Self) { - self.x /= other.x; - self.y /= other.y; - self.z /= other.z; - } -} - -impl DivAssign for Vector3 { - fn div_assign(&mut self, scalar: f32) { - self.x /= scalar; - self.y /= scalar; - self.z /= scalar; - } -} - -impl Index for Vector3 { - type Output = f32; - - fn index(&self, index: usize) -> &Self::Output { - match index { - 0 => &self.x, - 1 => &self.y, - 2 => &self.z, - _ => panic!("Index out of bounds"), - } - } -} - -impl From<[f32; 3]> for Vector3 { - fn from(pos: [f32; 3]) -> Self { - Self { - x: pos[0], - y: pos[1], - z: pos[2], - } - } -} - -impl From for [f32; 3] { - fn from(pos: Vector3) -> Self { - [pos.x, pos.y, pos.z] - } -} - -impl From<(f32, f32, f32)> for Vector3 { - fn from(pos: (f32, f32, f32)) -> Self { - Self { - x: pos.0, - y: pos.1, - z: pos.2, - } - } -} - -impl From for (f32, f32, f32) { - fn from(pos: Vector3) -> Self { - (pos.x, pos.y, pos.z) - } -} - -impl From for Vector3 { - fn from(pos: nalgebra_glm::Vec3) -> Self { - Self { - x: pos.x, - y: pos.y, - z: pos.z, - } - } -} - -impl From for nalgebra_glm::Vec3 { - fn from(pos: Vector3) -> Self { - nalgebra_glm::vec3(pos.x, pos.y, pos.z) - } -} - -impl Neg for Vector3 { - type Output = Self; - - fn neg(self) -> Self { - Self { - x: -self.x, - y: -self.y, - z: -self.z, - } - } -} - -// Constructors -impl Vector2 { - /// A vector with all components set to 0. - pub const ZERO: Self = Self::new(0.0, 0.0); - /// A vector with all components set to 1. - pub const ONE: Self = Self::new(1.0, 1.0); - /// A vector with all components set to 0 except for the x component, which is set to 1. - pub const UNIT_X: Self = Self::new(1.0, 0.0); - /// A vector with all components set to 0 except for the y component, which is set to 1. - pub const UNIT_Y: Self = Self::new(0.0, 1.0); - - /// Returns a vector with all components set to 0 except for the x component, which is set to 1. - pub const fn x_axis() -> Self { - Self::new(1.0, 0.0) - } - /// Returns a vector with all components set to 0 except for the y component, which is set to 1. - pub const fn y_axis() -> Self { - Self::new(0.0, 1.0) - } -} - -// Methods -impl Vector2 { - /// Returns a new vector with all components in absolute values (i.e. positive). - pub fn abs(&self) -> Self { - Self { - x: self.x.abs(), - y: self.y.abs(), - } - } - /// Returns the unsigned minimum angle to the given vector, in radians. - pub fn angle_to(&self, to: Self) -> f32 { - let dot = self.dot(to); - let len = self.length() * to.length(); - (dot / len).acos() - } - /// Returns the vector "bounced off" from a plane defined by the given normal ``n``. - /// - ///> Note: bounce performs the operation that most engines and frameworks call ``reflect()``. - pub fn bounce(&self, n: Self) -> Self { - *self - n * 2.0 * self.dot(n) - } - /// Returns a new vector with all components rounded up (towards positive infinity). - pub fn ceil(&self) -> Self { - Self { - x: self.x.ceil(), - y: self.y.ceil(), - } - } - /// Returns a new vector with all components clamped between the components of ``min`` and ``max`` - pub fn clamp(&self, min: f32, max: f32) -> Self { - Self { - x: self.x.clamp(min, max), - y: self.y.clamp(min, max), - } - } - /// Returns a new vector with all components clamped between ``min`` and ``max`` - pub fn clampf(&self, min: f32, max: f32) -> Self { - Self { - x: self.x.clamp(min, max), - y: self.y.clamp(min, max), - } - } - /// Returns the cross product of this vector and `with`. - /// - /// This returns a scalar representing the magnitude of the vector perpendicular to the plane defined by the two vectors. - /// If the two vectors are parallel, this returns `0.0`, making it useful for testing if two vectors are parallel. - pub const fn cross(&self, with: Self) -> f32 { - self.x * with.y - self.y * with.x - } - /// Returns the normalized vector pointing from this vector to ``to``. This is equivalent to using ``(b - a).normalized()``. - pub fn direction_to(&self, to: Self) -> Self { - (to - *self).normalize() - } - /// Returns the squared distance between this vector and ``to``. - /// - /// This method runs faster than [``Vector2::distance_to``], so prefer it if you need to compare vectors or need the squared distance for some formula. - pub fn distance_squared_to(&self, to: Self) -> f32 { - (*self - to).length_squared() - } - /// Returns the distance between this vector and ``to``. - pub fn distance_to(&self, to: Self) -> f32 { - (*self - to).length() - } - /// Returns the dot product of this vector and ``with``. This can be used to compare the angle between two vectors. For example, this can be used to determine whether an enemy is facing the player. - /// - /// The dot product will be ``0`` for a right angle (90 degrees), greater than 0 for angles narrower than 90 degrees and lower than 0 for angles wider than 90 degrees. - /// - /// When using unit (normalized) vectors, the result will always be between ``-1.0`` (180 degree angle) when the vectors are facing opposite directions, and ``1.0`` (0 degree angle) when the vectors are aligned. - /// - ///> Note: ``a.dot(b)`` is equivalent to ``b.dot(a)``. - pub fn dot(&self, with: Self) -> f32 { - self.x * with.x + self.y * with.y - } - /// Returns a new vector with all components rounded down (towards negative infinity). - pub fn floor(&self) -> Self { - Self { - x: self.x.floor(), - y: self.y.floor(), - } - } - /// Returns the inverse of the vector. This is the same as: - ///```rs - /// Vector3 { - /// x: 1.0 / self.x, - /// y: 1.0 / self.y, - /// z: 1.0 / self.z - /// } - /// ``` - pub const fn inverse(&self) -> Self { - Self { - x: 1.0 / self.x, - y: 1.0 / self.y, - } - } - /// Returns true if the vector is normalized, i.e. its length is approximately equal to 1. - #[must_use] - pub fn is_normalized(&self) -> bool { - (self.x * self.x + self.y * self.y - 1.0).abs() < 0.0001 - } - /// Returns the length (magnitude) of this vector. - pub fn length(&self) -> f32 { - (self.x * self.x + self.y * self.y).sqrt() - } - /// Returns the squared length (squared magnitude) of this vector. - /// - /// This method runs faster than [`Vector2::length`], so prefer it if you need to compare vectors or need the squared distance for some formula. - pub const fn length_squared(&self) -> f32 { - self.x * self.x + self.y * self.y - } - /// Returns the result of the linear interpolation between this vector and ``to`` by amount ``weight``. - /// ``weight`` is on the range of ``0.0`` to ``1.0``, representing the amount of interpolation. - pub fn lerp(&self, to: Self, weight: f32) -> Self { - *self * (1.0 - weight) + to * weight - } - /// Returns the vector with a maximum length by limiting its length to ``length``. - pub fn limit_length(&self, max_length: f32) -> Self { - let length = self.length(); - if length > max_length { - *self * (max_length / length) - } else { - *self - } - } - /// Returns the component-wise minimum of ``self`` and ``with``, equivalent to: - /// ```rs - /// Vector2 { - /// x: self.x.max(with.x), - /// y: self.y.max(with.y), - /// } - /// ``` - pub fn max(&self, with: Vector3) -> Self { - Self { - x: self.x.max(with.x), - y: self.y.max(with.y), - } - } - /// Returns the component-wise maximum of ``self`` and ``with``, equivalent to: - /// ```rs - /// Vector2 { - /// x: self.x.max(with), - /// y: self.y.max(with), - /// } - /// ``` - pub fn maxf(&self, with: f32) -> Self { - Self { - x: self.x.max(with), - y: self.y.max(with), - } - } - /// Returns the component-wise minimum of ``self`` and ``with``, equivalent to: - /// ```rs - /// Vector2 { - /// x: self.x.min(with.x), - /// y: self.y.min(with.y), - /// } - /// ``` - pub fn min(&self, with: Vector3) -> Self { - Self { - x: self.x.min(with.x), - y: self.y.min(with.y), - } - } - /// Returns the component-wise minimum of ``self`` and ``with``, equivalent to: - /// ```rs - /// Vector2 { - /// x: self.x.min(with), - /// y: self.y.min(with), - /// } - /// ``` - pub fn minf(&self, with: f32) -> Self { - Self { - x: self.x.min(with), - y: self.y.min(with), - } - } - /// Normalize the vector (make the length of the vector 1) - pub fn normalize(&self) -> Self { - let length = (self.x * self.x + self.y * self.y).sqrt(); - Self { - x: self.x / length, - y: self.y / length, - } - } - /// Returns a new vector with all components rounded to the nearest integer, with halfway cases rounded away from zero. - pub fn round(&self) -> Self { - Self { - x: self.x.round(), - y: self.y.round(), - } - } - /// Rotates the vector around the given axis by the given angle. - pub fn rotate(&self, axis: RotateAxis, angle: RotateAmount) -> Self { - // axis might be unused - let angle = match angle { - RotateAmount::Degrees(d) => d.to_radians(), - RotateAmount::Radians(r) => r, - }; - - let (sin, cos) = angle.sin_cos(); - - match axis { - RotateAxis::Z => Self { - x: self.x * cos - self.y * sin, - y: self.x * sin + self.y * cos, - }, - _ => *self, // For Vector2, only Z-axis rotation makes sense - } - } -} - -unsafe impl Send for Vector2 {} -unsafe impl Sync for Vector2 {} - -unsafe impl Pod for Vector2 {} - -impl Add for Vector2 { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { - x: self.x + other.x, - y: self.y + other.y, - } - } -} - -impl Add for Vector2 { - type Output = Self; - - fn add(self, scalar: f32) -> Self { - Self { - x: self.x + scalar, - y: self.y + scalar, - } - } -} - -impl AddAssign for Vector2 { - fn add_assign(&mut self, other: Self) { - self.x += other.x; - self.y += other.y; - } -} - -impl AddAssign for Vector2 { - fn add_assign(&mut self, scalar: f32) { - self.x += scalar; - self.y += scalar; - } -} - -impl Sub for Vector2 { - type Output = Self; - - fn sub(self, other: Self) -> Self { - Self { - x: self.x - other.x, - y: self.y - other.y, - } - } -} - -impl Sub for Vector2 { - type Output = Self; - - fn sub(self, scalar: f32) -> Self { - Self { - x: self.x - scalar, - y: self.y - scalar, - } - } -} - -impl SubAssign for Vector2 { - fn sub_assign(&mut self, other: Self) { - self.x -= other.x; - self.y -= other.y; - } -} - -impl SubAssign for Vector2 { - fn sub_assign(&mut self, scalar: f32) { - self.x -= scalar; - self.y -= scalar; - } -} - -impl Mul for Vector2 { - type Output = Self; - - fn mul(self, other: Self) -> Self { - Self { - x: self.x * other.x, - y: self.y * other.y, - } - } -} - -impl Mul for Vector2 { - type Output = Self; - - fn mul(self, scalar: f32) -> Self { - Self { - x: self.x * scalar, - y: self.y * scalar, - } - } -} - -impl MulAssign for Vector2 { - fn mul_assign(&mut self, other: Self) { - self.x *= other.x; - self.y *= other.y; - } -} - -impl MulAssign for Vector2 { - fn mul_assign(&mut self, scalar: f32) { - self.x *= scalar; - self.y *= scalar; - } -} - -impl Div for Vector2 { - type Output = Self; - - fn div(self, other: Self) -> Self { - Self { - x: self.x / other.x, - y: self.y / other.y, - } - } -} - -impl Div for Vector2 { - type Output = Self; - - fn div(self, scalar: f32) -> Self { - Self { - x: self.x / scalar, - y: self.y / scalar, - } - } -} - -impl DivAssign for Vector2 { - fn div_assign(&mut self, other: Self) { - self.x /= other.x; - self.y /= other.y; - } -} - -impl DivAssign for Vector2 { - fn div_assign(&mut self, scalar: f32) { - self.x /= scalar; - self.y /= scalar; - } -} - -impl From<[f32; 2]> for Vector2 { - fn from(pos: [f32; 2]) -> Self { - Self { - x: pos[0], - y: pos[1], - } - } -} - -impl From for [f32; 2] { - fn from(pos: Vector2) -> Self { - [pos.x, pos.y] - } -} - -impl From<(f32, f32)> for Vector2 { - fn from(pos: (f32, f32)) -> Self { - Self { x: pos.0, y: pos.1 } - } -} - -impl From for (f32, f32) { - fn from(pos: Vector2) -> Self { - (pos.x, pos.y) - } -} - -impl From for Vector2 { - fn from(pos: nalgebra_glm::Vec2) -> Self { - Self { x: pos.x, y: pos.y } - } -} - -impl From for nalgebra_glm::Vec2 { - fn from(pos: Vector2) -> Self { - nalgebra_glm::vec2(pos.x, pos.y) - } -} - -impl Index for Vector2 { - type Output = f32; - - fn index(&self, index: usize) -> &Self::Output { - match index { - 0 => &self.x, - 1 => &self.y, - _ => panic!("Index out of bounds"), - } - } -} - -impl Neg for Vector2 { - type Output = Self; - - fn neg(self) -> Self { - Self { - x: -self.x, - y: -self.y, - } - } -} diff --git a/crates/blue_engine_core/src/render.rs b/crates/blue_engine_core/src/render.rs index f3753e9..ac186e7 100644 --- a/crates/blue_engine_core/src/render.rs +++ b/crates/blue_engine_core/src/render.rs @@ -2,8 +2,8 @@ use crate::{ CameraContainer, ObjectStorage, PipelineData, - prelude::{ShaderSettings, TextureData, uniform_type}, - utils::default_resources::{DEFAULT_COLOR, DEFAULT_MATRIX_4, DEFAULT_SHADER, DEFAULT_TEXTURE}, + prelude::{ShaderSettings, TextureData}, + utils::default_resources::{DEFAULT_COLOR, DEFAULT_SHADER, DEFAULT_TEXTURE}, }; /// Main renderer class. this will contain all methods and data related to the renderer @@ -180,13 +180,8 @@ impl Renderer { //crate::prelude::TextureFormat::PNG ) { let default_uniform = self.build_uniform_buffer(&vec![ - self.build_uniform_buffer_part("Transformation Matrix", DEFAULT_MATRIX_4), - self.build_uniform_buffer_part( - "Color", - uniform_type::Array4 { - data: DEFAULT_COLOR, - }, - ), + self.build_uniform_buffer_part("Transformation Matrix", crate::Matrix4::IDENTITY), + self.build_uniform_buffer_part("Color", DEFAULT_COLOR), ]); let default_shader = self.build_shader( diff --git a/crates/blue_engine_core/src/utils/camera.rs b/crates/blue_engine_core/src/utils/camera.rs index 4a21412..8821dd2 100644 --- a/crates/blue_engine_core/src/utils/camera.rs +++ b/crates/blue_engine_core/src/utils/camera.rs @@ -4,10 +4,10 @@ * The license is same as the one on the root. */ -use super::default_resources::{DEFAULT_MATRIX_4, OPENGL_TO_WGPU_MATRIX}; +use super::default_resources::OPENGL_TO_WGPU_MATRIX; use crate::{ - UniformBuffers, - prelude::{Renderer, Vector3, uniform_type::Matrix}, + Matrix4, UniformBuffers, + prelude::{Renderer, Vector3}, }; use winit::dpi::PhysicalSize; @@ -49,7 +49,7 @@ pub struct Camera { /// The furthest view of camera pub far: f32, /// The final data that will be sent to GPU - pub view_data: nalgebra_glm::Mat4, + pub view_data: Matrix4, // For checking and rebuilding it's uniform buffer pub(crate) changed: bool, /// The uniform data of the camera to be sent to the gpu @@ -79,7 +79,7 @@ impl Camera { /// Creates a new camera. this should've been automatically done at the time of creating an engine pub fn new(window_size: PhysicalSize, renderer: &mut Renderer) -> Self { let camera_uniform = renderer.build_uniform_buffer(&[ - renderer.build_uniform_buffer_part("Camera Uniform", DEFAULT_MATRIX_4) + renderer.build_uniform_buffer_part("Camera Uniform", crate::Matrix4::IDENTITY) ]); let mut camera = Self { @@ -92,7 +92,7 @@ impl Camera { }, near: 0.1, far: 100.0, - view_data: DEFAULT_MATRIX_4.to_im(), + view_data: Matrix4::IDENTITY, changed: true, uniform_data: camera_uniform.0, add_position_and_target: false, @@ -113,7 +113,7 @@ impl Camera { /// Updates the view uniform matrix that decides how camera works pub fn build_view_orthographic_matrix(&mut self) { let view = self.build_view_matrix(); - let ortho = nalgebra_glm::ortho( + let ortho = Matrix4::orthographic_rh( 0f32, self.resolution.0, 0f32, @@ -126,8 +126,8 @@ impl Camera { } /// Returns a matrix uniform buffer from camera data that can be sent to GPU - pub fn camera_uniform_buffer(&self) -> Matrix { - Matrix::from_im(self.view_data) + pub fn camera_uniform_buffer(&self) -> Matrix4 { + self.view_data } /// Sets the position of camera @@ -204,25 +204,25 @@ impl Camera { } /// Builds a view matrix for camera projection - pub fn build_view_matrix(&self) -> nalgebra_glm::Mat4 { - nalgebra_glm::look_at_rh( - &self.position.into(), - &if self.add_position_and_target { + pub fn build_view_matrix(&self) -> Matrix4 { + Matrix4::look_at_rh( + self.position.into(), + if self.add_position_and_target { (self.position + self.target).into() } else { self.target.into() }, - &self.up.into(), + self.up.into(), ) } /// Builds a projection matrix for camera - pub fn build_projection_matrix(&self) -> nalgebra_glm::Mat4 { + pub fn build_projection_matrix(&self) -> Matrix4 { let aspect = self.resolution.0 / self.resolution.1; match self.projection { crate::Projection::Perspective { fov } => { - nalgebra_glm::perspective(aspect, fov, self.near, self.far) + Matrix4::perspective_rh(aspect, fov, self.near, self.far) } crate::Projection::Orthographic { zoom } => { let width = zoom; @@ -233,7 +233,7 @@ impl Camera { let bottom = height * -0.5; let top = height * 0.5; - nalgebra_glm::ortho(left, right, bottom, top, self.near, self.far) + Matrix4::orthographic_rh(left, right, bottom, top, self.near, self.far) } } } @@ -262,9 +262,9 @@ impl CameraContainer { } } /// Returns a matrix uniform buffer from camera data that can be sent to GPU - pub fn camera_uniform_buffer(&self) -> Option { + pub fn camera_uniform_buffer(&self) -> Option { if let Some(main_camera) = self.cameras.get("main") { - Some(Matrix::from_im(main_camera.view_data)) + Some(main_camera.view_data) } else { None } @@ -334,7 +334,7 @@ impl CameraContainer { } } /// Builds a view matrix for camera projection - pub fn build_view_matrix(&self) -> Option { + pub fn build_view_matrix(&self) -> Option { if let Some(main_camera) = self.cameras.get("main") { Some(main_camera.build_view_matrix()) } else { @@ -342,7 +342,7 @@ impl CameraContainer { } } /// Builds a projection matrix for camera - pub fn build_projection_matrix(&self) -> Option { + pub fn build_projection_matrix(&self) -> Option { if let Some(main_camera) = self.cameras.get("main") { Some(main_camera.build_projection_matrix()) } else { diff --git a/crates/blue_engine_core/src/utils/default_resources.rs b/crates/blue_engine_core/src/utils/default_resources.rs index ab69700..d8772e6 100644 --- a/crates/blue_engine_core/src/utils/default_resources.rs +++ b/crates/blue_engine_core/src/utils/default_resources.rs @@ -36,24 +36,13 @@ pub const DEFAULT_TEXTURE: &[u8] = &[ ]; /// The default color used for each object -pub const DEFAULT_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 0.0]; - -/// A default matrix 4x4 used in the engine -pub const DEFAULT_MATRIX_4: crate::prelude::uniform_type::Matrix = - crate::prelude::uniform_type::Matrix { - data: [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - }; +pub const DEFAULT_COLOR: crate::Vector4 = crate::Vector4::new(1.0, 1.0, 1.0, 1.0); /// A transformation matrix used to convert opengl projections to wgpu #[rustfmt::skip] -pub const OPENGL_TO_WGPU_MATRIX: nalgebra_glm::Mat4 = nalgebra_glm::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.5, - 0.0, 0.0, 0.0, 1.0, -); +pub const OPENGL_TO_WGPU_MATRIX: crate::Matrix4 = crate::Matrix4 { + x_axis: crate::Vector4::new(1.0, 0.0, 0.0, 0.0), + y_axis: crate::Vector4::new(0.0, 1.0, 0.0, 0.0), + z_axis: crate::Vector4::new(0.0, 0.0, 0.5, 0.5), + w_axis: crate::Vector4::new(0.0, 0.0, 0.0, 1.0), +}; diff --git a/examples/shapes/square.rs b/examples/shapes/square.rs index da0e585..ada13b1 100644 --- a/examples/shapes/square.rs +++ b/examples/shapes/square.rs @@ -7,31 +7,31 @@ */ use blue_engine::{ - StringBuffer, Vector2, Vector3, + StringBuffer, prelude::{Engine, ObjectSettings, Vertex}, }; pub fn square(name: impl StringBuffer, engine: &mut Engine) { let vertices = vec![ Vertex { - position: Vector3::new(1.0, 1.0, 0.0), - uv: Vector2::new(1.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, 1.0, 0.0], + uv: [1.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(1.0, -1.0, 0.0), - uv: Vector2::new(1.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [1.0, -1.0, 0.0], + uv: [1.0, 0.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, -1.0, 0.0), - uv: Vector2::new(0.0, 1.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, -1.0, 0.0], + uv: [0.0, 1.0], + normal: [0.0, 0.0, 0.0], }, Vertex { - position: Vector3::new(-1.0, 1.0, 0.0), - uv: Vector2::new(0.0, 0.0), - normal: Vector3::new(0.0, 0.0, 0.0), + position: [-1.0, 1.0, 0.0], + uv: [0.0, 0.0], + normal: [0.0, 0.0, 0.0], }, ]; From 7e352837d2fc5ec79479be8234a01cbd89431521 Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Tue, 1 Apr 2025 00:24:19 +0300 Subject: [PATCH 8/9] chore: finalized some documentation and cleanups --- Cargo.lock | 54 ++------------ Cargo.toml | 8 +- crates/blue_engine_core/Cargo.toml | 14 ++-- crates/blue_engine_core/src/definition.rs | 73 ++++++++++++++++++- .../blue_engine_core/src/prelude/imports.rs | 33 +++++++++ crates/blue_engine_core/src/prelude/mod.rs | 5 +- crates/blue_engine_core/src/window.rs | 72 ------------------ crates/blue_engine_dynamic/Cargo.toml | 4 +- 8 files changed, 129 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8e0746..df3c4c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,24 +307,25 @@ dependencies = [ [[package]] name = "blue_engine" -version = "0.6.5" +version = "0.7.0" dependencies = [ - "blue_engine_core 0.6.5", + "blue_engine_core 0.7.0", "blue_engine_dynamic", ] [[package]] name = "blue_engine_core" version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91b19075a86cbddc80275bab8f131f5c3fca21a4ec6f100f64637213bc18f60e" dependencies = [ "android_logger", "bytemuck", - "crabtime", "downcast", "env_logger", - "glam", "image", "log", + "nalgebra-glm", "pollster", "thiserror 2.0.9", "wgpu", @@ -333,17 +334,15 @@ dependencies = [ [[package]] name = "blue_engine_core" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91b19075a86cbddc80275bab8f131f5c3fca21a4ec6f100f64637213bc18f60e" +version = "0.7.0" dependencies = [ "android_logger", "bytemuck", "downcast", "env_logger", + "glam", "image", "log", - "nalgebra-glm", "pollster", "thiserror 2.0.9", "wgpu", @@ -356,7 +355,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86550ddc6fb6455656ebe19545929b0e458e17a8a764d9c9a636d0c20e7f8665" dependencies = [ - "blue_engine_core 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "blue_engine_core 0.6.5", ] [[package]] @@ -555,28 +554,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crabtime" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495b2c0e1afa3118b42b183d52130ed81b8811e92cc0c7a081a9180eff33682e" -dependencies = [ - "crabtime-internal", -] - -[[package]] -name = "crabtime-internal" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5201a50958f6a2adc98079508bccaba28b296fcef454e7546a8c645c0069b0de" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn", - "toml", -] - [[package]] name = "crc32fast" version = "1.4.2" @@ -2042,15 +2019,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.42" @@ -2113,12 +2081,6 @@ dependencies = [ "tiny-skia", ] -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" - [[package]] name = "serde" version = "1.0.217" diff --git a/Cargo.toml b/Cargo.toml index b4acc8f..8c65b65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blue_engine" -version = "0.6.5" +version = "0.7.0" authors = ["Elham Aryanpur "] edition = "2024" description = "General-Purpose, Easy-to-use, Fast, and Portable graphics engine" @@ -33,12 +33,12 @@ android_game_activity = [ u32 = ["blue_engine_core?/u32", "blue_engine_dynamic?/u32"] [dependencies] -# blue_engine_core = { version = "0.6.5", optional = true } -blue_engine_core = { path = "crates/blue_engine_core", optional = true } +blue_engine_core = { version = "0.7.0", optional = true } +# blue_engine_core = { path = "crates/blue_engine_core", optional = true } # Wasm does not support dynamic linking. [target.'cfg(not(target_family = "wasm"))'.dependencies] -blue_engine_dynamic = { version = "0.6.5", optional = true } +blue_engine_dynamic = { version = "0.7.0", optional = true } # blue_engine_dynamic = { path = "crates/blue_engine_dynamic", optional = true } # ========== EXAMPLES ========== # diff --git a/crates/blue_engine_core/Cargo.toml b/crates/blue_engine_core/Cargo.toml index c5e8880..e8f53e2 100644 --- a/crates/blue_engine_core/Cargo.toml +++ b/crates/blue_engine_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blue_engine_core" -version = "0.6.5" +version = "0.7.0" authors = ["Elham Aryanpur "] edition = "2024" description = "USE blue_engine THIS IS FOR INTERNAL USE" @@ -18,22 +18,22 @@ android_game_activity = ["winit/android-game-activity"] # using u32 for indices and others u32 = [] +glam_fast_math = ["glam/fast-math"] + [dependencies] -image = { version = "0.25" } -pollster = "0.4" winit = { version = "0.30", features = ["rwh_06"] } wgpu = { version = "24.0.3" } +image = { version = "0.25" } +pollster = "0.4" bytemuck = { version = "1.16", features = ["derive"] } downcast = "0.11" thiserror = "2.0" -crabtime = "1.1.1" - -# debug logs env_logger = { version = "0.11", optional = true } +glam = { version = "0.30.1", features = ["bytemuck", "serde"] } + # android log = { version = "0.4", optional = true } android_logger = { version = "0.15.0", optional = true } -glam = { version = "0.30.1", features = ["bytemuck", "serde"] } [target.'cfg(target_arch = "wasm32")'.dependencies] wgpu = { version = "24.0.3", features = ["webgl"] } diff --git a/crates/blue_engine_core/src/definition.rs b/crates/blue_engine_core/src/definition.rs index bc02a84..1d66499 100644 --- a/crates/blue_engine_core/src/definition.rs +++ b/crates/blue_engine_core/src/definition.rs @@ -9,7 +9,7 @@ use wgpu::{BindGroupLayout, Sampler, Texture, TextureView, util::DeviceExt}; use crate::{ InstanceRaw, UnsignedIntType, - prelude::{ShaderSettings, Shaders, StringBuffer, Textures, UniformBuffers, Vertex}, + prelude::{Shaders, StringBuffer, Textures, UniformBuffers, Vertex}, }; /// Container for pipeline values. Each pipeline takes only 1 vertex shader, @@ -78,6 +78,77 @@ pub enum TextureMode { unsafe impl Send for TextureMode {} unsafe impl Sync for TextureMode {} +/// These definitions are taken from wgpu API docs +#[derive(Debug, Clone, Copy)] +pub struct ShaderSettings { + // ===== PRIMITIVE ===== // + /// The primitive topology used to interpret vertices + pub topology: crate::ShaderPrimitive, + /// When drawing strip topologies with indices, this is the + /// required format for the index buffer. This has no effect + /// on non-indexed or non-strip draws. + pub strip_index_format: Option, + /// The face to consider the front for the purpose of + /// culling and stencil operations. + pub front_face: crate::FrontFace, + /// The face culling mode + pub cull_mode: Option, + /// Controls the way each polygon is rasterized. Can be + /// either `Fill` (default), `Line` or `Point` + /// + /// Setting this to something other than `Fill` requires + /// `NON_FILL_POLYGON_MODE` feature to be enabled + pub polygon_mode: crate::PolygonMode, + /// If set to true, the polygon depth is clamped to 0-1 + /// range instead of being clipped. + /// + /// Enabling this requires the `DEPTH_CLAMPING` feature + /// to be enabled + pub clamp_depth: bool, + /// If set to true, the primitives are rendered with + /// conservative overestimation. I.e. any rastered + /// pixel touched by it is filled. Only valid for PolygonMode::Fill! + /// + /// Enabling this requires `CONSERVATIVE_RASTERIZATION` + /// features to be enabled. + pub conservative: bool, + // ===== Multisample ===== // + /// The number of samples calculated per pixel (for MSAA). + /// For non-multisampled textures, this should be `1` + pub count: u32, + /// Bitmask that restricts the samples of a pixel modified + /// by this pipeline. All samples can be enabled using the + /// value `!0` + pub mask: u64, + /// When enabled, produces another sample mask per pixel + /// based on the alpha output value, that is ANDead with the + /// sample_mask and the primitive coverage to restrict the + /// set of samples affected by a primitive. + + /// The implicit mask produced for alpha of zero is guaranteed + /// to be zero, and for alpha of one is guaranteed to be all + /// 1-s. + pub alpha_to_coverage_enabled: bool, +} +impl Default for ShaderSettings { + fn default() -> Self { + Self { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + clamp_depth: false, + conservative: false, + count: 1, + mask: !0, + alpha_to_coverage_enabled: true, + } + } +} +unsafe impl Send for ShaderSettings {} +unsafe impl Sync for ShaderSettings {} + /// This function helps in converting pixel value to the value that is between -1 and +1 pub fn pixel_to_cartesian(value: f32, max: u32) -> f32 { let mut result = value / max as f32; diff --git a/crates/blue_engine_core/src/prelude/imports.rs b/crates/blue_engine_core/src/prelude/imports.rs index 19d9964..cce03d0 100644 --- a/crates/blue_engine_core/src/prelude/imports.rs +++ b/crates/blue_engine_core/src/prelude/imports.rs @@ -1,4 +1,5 @@ pub use downcast; +pub use glam; pub use image; pub use wgpu; pub use winit; @@ -25,12 +26,34 @@ pub use winit::keyboard::KeyCode; pub use winit::window::Fullscreen; // Math types +/// A 2-dimensional vector. pub type Vector2 = glam::Vec2; +/// A 3-dimensional vector. pub type Vector3 = glam::Vec3; +/// A 4-dimensional vector. pub type Vector4 = glam::Vec4; +/// A 2x2 column major matrix. +/// +/// SIMD vector types are used for storage on supported platforms. +/// +/// This type is 16 byte aligned. pub type Matrix2 = glam::Mat2; +/// A 3x3 column major matrix. +/// +/// This 3x3 matrix type features convenience methods for creating and using linear and +/// affine transformations. pub type Matrix3 = glam::Mat3; +/// A 4x4 column major matrix. +/// +/// This 4x4 matrix type features convenience methods for creating and using affine transforms and perspective projections. pub type Matrix4 = glam::Mat4; +/// A quaternion representing an orientation. +/// +/// This quaternion is intended to be of unit length but may denormalize due to floating point "error creep" which can occur when successive quaternion operations are applied. +/// +/// SIMD vector types are used for storage on supported platforms. +/// +/// This type is 16 byte aligned. pub type Quaternion = glam::Quat; /// Input helper @@ -38,13 +61,23 @@ pub use crate::utils::winit_input_helper::WinitInputHelper as InputHelper; pub use bytemuck::Pod; pub use bytemuck::Zeroable; +/// Depth Format pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; +/// Shaders are programs that runs on the GPU pub type Shaders = wgpu::RenderPipeline; +/// Uniform Buffers are small amount of data that are sent from CPU to GPU pub type UniformBuffers = wgpu::BindGroup; +/// Textures are image data that are sent to GPU to be set to a surface pub type Textures = wgpu::BindGroup; +/// Primitive type the input mesh is composed of. pub type ShaderPrimitive = wgpu::PrimitiveTopology; +/// Format of indices used with pipeline. pub type IndexFormat = wgpu::IndexFormat; +/// Vertex winding order which classifies the "front" face of a triangle. pub type FrontFace = wgpu::FrontFace; +/// Face of a vertex. pub type CullMode = wgpu::Face; +/// Type of drawing mode for polygons pub type PolygonMode = wgpu::PolygonMode; +/// Power Preference when choosing a physical adapter. pub type PowerPreference = wgpu::PowerPreference; diff --git a/crates/blue_engine_core/src/prelude/mod.rs b/crates/blue_engine_core/src/prelude/mod.rs index bd4545b..051ace4 100644 --- a/crates/blue_engine_core/src/prelude/mod.rs +++ b/crates/blue_engine_core/src/prelude/mod.rs @@ -7,13 +7,14 @@ pub use imports::*; pub mod primitive_shapes; pub use crate::camera::{Camera, CameraContainer, Projection}; pub use crate::definition::{ - Pipeline, PipelineData, TextureData, TextureMode, VertexBuffers, pixel_to_cartesian, + Pipeline, PipelineData, ShaderSettings, TextureData, TextureMode, VertexBuffers, + pixel_to_cartesian, }; pub use crate::objects::{ Instance, InstanceRaw, Object, ObjectSettings, ObjectStorage, RotateAmount, RotateAxis, }; pub use crate::render::Renderer; -pub use crate::window::{ShaderSettings, Window, WindowDescriptor}; +pub use crate::window::{Window, WindowDescriptor}; /// The uint type used for indices and more #[cfg(not(feature = "u32"))] diff --git a/crates/blue_engine_core/src/window.rs b/crates/blue_engine_core/src/window.rs index 8f06acf..7ad9c11 100644 --- a/crates/blue_engine_core/src/window.rs +++ b/crates/blue_engine_core/src/window.rs @@ -110,78 +110,6 @@ impl std::default::Default for WindowDescriptor { unsafe impl Send for WindowDescriptor {} unsafe impl Sync for WindowDescriptor {} -/// These definitions are taken from wgpu API docs -#[derive(Debug, Clone, Copy)] -pub struct ShaderSettings { - // ===== PRIMITIVE ===== // - /// The primitive topology used to interpret vertices - pub topology: crate::ShaderPrimitive, - /// When drawing strip topologies with indices, this is the - /// required format for the index buffer. This has no effect - /// on non-indexed or non-strip draws. - pub strip_index_format: Option, - /// The face to consider the front for the purpose of - /// culling and stencil operations. - pub front_face: crate::FrontFace, - /// The face culling mode - pub cull_mode: Option, - /// Controls the way each polygon is rasterized. Can be - /// either `Fill` (default), `Line` or `Point` - /// - /// Setting this to something other than `Fill` requires - /// `NON_FILL_POLYGON_MODE` feature to be enabled - pub polygon_mode: crate::PolygonMode, - /// If set to true, the polygon depth is clamped to 0-1 - /// range instead of being clipped. - /// - /// Enabling this requires the `DEPTH_CLAMPING` feature - /// to be enabled - pub clamp_depth: bool, - /// If set to true, the primitives are rendered with - /// conservative overestimation. I.e. any rastered - /// pixel touched by it is filled. Only valid for PolygonMode::Fill! - /// - /// Enabling this requires `CONSERVATIVE_RASTERIZATION` - /// features to be enabled. - pub conservative: bool, - - // ===== Multisample ===== // - /// The number of samples calculated per pixel (for MSAA). - /// For non-multisampled textures, this should be `1` - pub count: u32, - /// Bitmask that restricts the samples of a pixel modified - /// by this pipeline. All samples can be enabled using the - /// value `!0` - pub mask: u64, - /// When enabled, produces another sample mask per pixel - /// based on the alpha output value, that is ANDead with the - /// sample_mask and the primitive coverage to restrict the - /// set of samples affected by a primitive. - - /// The implicit mask produced for alpha of zero is guaranteed - /// to be zero, and for alpha of one is guaranteed to be all - /// 1-s. - pub alpha_to_coverage_enabled: bool, -} -impl Default for ShaderSettings { - fn default() -> Self { - Self { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - clamp_depth: false, - conservative: false, - count: 1, - mask: !0, - alpha_to_coverage_enabled: true, - } - } -} -unsafe impl Send for ShaderSettings {} -unsafe impl Sync for ShaderSettings {} - impl Engine { /// Creates a new window in current thread using default settings. pub fn new() -> Result { diff --git a/crates/blue_engine_dynamic/Cargo.toml b/crates/blue_engine_dynamic/Cargo.toml index f344974..dfd1435 100644 --- a/crates/blue_engine_dynamic/Cargo.toml +++ b/crates/blue_engine_dynamic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blue_engine_dynamic" -version = "0.6.5" +version = "0.7.0" authors = ["Elham Aryanpur "] edition = "2024" description = "USE blue_engine THIS IS FOR INTERNAL USE" @@ -20,5 +20,5 @@ android_game_activity = ["blue_engine_core/android_game_activity"] u32 = ["blue_engine_core/u32"] [dependencies] -blue_engine_core = { version = "0.6.5" } +blue_engine_core = { version = "0.7.0" } # blue_engine_core = { path = "../blue_engine_core" } From 17726e7dfbbc6f35afebea70fe254aac578fc3f9 Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Tue, 1 Apr 2025 00:27:22 +0300 Subject: [PATCH 9/9] cargo lock update --- Cargo.lock | 128 +++-------------------------------------------------- 1 file changed, 6 insertions(+), 122 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df3c4c5..22099b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,15 +160,6 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - [[package]] name = "arbitrary" version = "1.4.1" @@ -309,32 +300,15 @@ dependencies = [ name = "blue_engine" version = "0.7.0" dependencies = [ - "blue_engine_core 0.7.0", + "blue_engine_core", "blue_engine_dynamic", ] -[[package]] -name = "blue_engine_core" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91b19075a86cbddc80275bab8f131f5c3fca21a4ec6f100f64637213bc18f60e" -dependencies = [ - "android_logger", - "bytemuck", - "downcast", - "env_logger", - "image", - "log", - "nalgebra-glm", - "pollster", - "thiserror 2.0.9", - "wgpu", - "winit", -] - [[package]] name = "blue_engine_core" version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45baa33b4181e0993a4ea441a7c3c815074daa1f82e063afc6d0d2af2121d480" dependencies = [ "android_logger", "bytemuck", @@ -351,11 +325,11 @@ dependencies = [ [[package]] name = "blue_engine_dynamic" -version = "0.6.5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86550ddc6fb6455656ebe19545929b0e458e17a8a764d9c9a636d0c20e7f8665" +checksum = "8c4e111620ed6065959985e399e31349e583abf62d902fbd977d670d849c738d" dependencies = [ - "blue_engine_core 0.6.5", + "blue_engine_core", ] [[package]] @@ -1149,16 +1123,6 @@ dependencies = [ "libc", ] -[[package]] -name = "matrixmultiply" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" -dependencies = [ - "autocfg", - "rawpointer", -] - [[package]] name = "maybe-rayon" version = "0.1.1" @@ -1237,33 +1201,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "nalgebra" -version = "0.33.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" -dependencies = [ - "approx", - "matrixmultiply", - "num-complex", - "num-rational", - "num-traits", - "simba", - "typenum", -] - -[[package]] -name = "nalgebra-glm" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e441f43bccdf40cb6bd4294321e6983c5bc7b9886112d19fd4c9813976b117e4" -dependencies = [ - "approx", - "nalgebra", - "num-traits", - "simba", -] - [[package]] name = "ndk" version = "0.9.0" @@ -1335,15 +1272,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-derive" version = "0.4.2" @@ -1928,12 +1856,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - [[package]] name = "rayon" version = "1.10.0" @@ -2038,15 +1960,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" -[[package]] -name = "safe_arch" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" -dependencies = [ - "bytemuck", -] - [[package]] name = "same-file" version = "1.0.6" @@ -2116,19 +2029,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "simba" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" -dependencies = [ - "approx", - "num-complex", - "num-traits", - "paste", - "wide", -] - [[package]] name = "simd-adler32" version = "0.3.7" @@ -2416,12 +2316,6 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - [[package]] name = "unicode-ident" version = "1.0.14" @@ -2802,16 +2696,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wide" -version = "0.7.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e6db2670d2be78525979e9a5f9c69d296fd7d670549fe9ebf70f8708cb5019" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "winapi-util" version = "0.1.9"