diff --git a/.gitignore b/.gitignore index a69aa05..9d03560 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ target tools res resources/*.png +crates/blue_engine_core/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index 345f3b7..3c35bb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -306,10 +306,27 @@ dependencies = [ name = "blue_engine" version = "0.6.0" dependencies = [ - "blue_engine_core", + "blue_engine_core 0.6.0", "blue_engine_dynamic", ] +[[package]] +name = "blue_engine_core" +version = "0.6.0" +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.0" @@ -335,7 +352,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d0cd9abed2ba61755516749271862760059a264b6011077afb3dff5d9857583" dependencies = [ - "blue_engine_core", + "blue_engine_core 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 730a698..580a90d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ u16 = ["blue_engine_core?/u16", "blue_engine_dynamic?/u16"] u32 = ["blue_engine_core?/u32", "blue_engine_dynamic?/u32"] [dependencies] -blue_engine_core = { version = "0.6.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] diff --git a/crates/blue_engine_core/src/header.rs b/crates/blue_engine_core/src/header.rs index f71e855..a70c721 100644 --- a/crates/blue_engine_core/src/header.rs +++ b/crates/blue_engine_core/src/header.rs @@ -1,778 +1,801 @@ -/* - * 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 -pub mod uniform_buffer; -pub use imports::*; -pub use uniform_buffer::*; - -use downcast::{downcast, Any}; - -/// The uint type used for indices and more -#[cfg(feature = "u16")] -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 - } - } - 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; - - fn deref(&self) -> &Self::Target { - &self.$field - } - } - impl std::ops::DerefMut for $struct { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.$field - } - } - }; -} - -/// Will contain all details about a vertex and will be sent to GPU -// Will be turned to C code and sent to GPU -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct Vertex { - /// Contains position data for the vertex in 3D space - pub position: [f32; 3], - /// Contains uv position data for the vertex - pub uv: [f32; 2], - /// Contains the normal face of the vertex - pub normal: [f32; 3], -} -impl Vertex { - pub(crate) fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - wgpu::VertexAttribute { - offset: 0, - shader_location: 0, - format: wgpu::VertexFormat::Float32x3, - }, - wgpu::VertexAttribute { - offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x2, - }, - wgpu::VertexAttribute { - offset: std::mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, - shader_location: 2, - format: wgpu::VertexFormat::Float32x3, - }, - ], - } - } -} -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: glm::Vec3, - /// Dictates the position of your object in pixels - pub position: glm::Vec3, - /// Dictates the rotation of your object - pub rotation: glm::Vec3, - // 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. -/// It gives a set of default data to work with, -/// but also allow you to go beyond that and work as low level as you wish to. -/// -/// You can also use the Engine to build you own custom structure the way you wish for it to be. -/// Possibilities are endless! -/// -/// To start using the Blue Engine, you can start by creating a new Engine like follows: -/// ``` -/// use blue_engine::header::{Engine, WindowDescriptor}; -/// -/// fn main() { -/// let engine = Engine::new().expect("Couldn't create the engine"); -/// } -/// ``` -/// The WindowDescriptor simply holds what features you would like for your window. -/// If you are reading this on later version of -/// the engine, you might be able to even run the engine in headless mode -/// meaning there would not be a need for a window and the -/// renders would come as image files. -/// -/// If you so wish to have a window, you would need to start a window update loop. -/// The update loop of window runs a frame every few millisecond, -/// and gives you details of what is happening during this time, like input events. -/// You can also modify existing parts of the engine during -/// this update loop, such as changing camera to look differently, -/// or creating a new object on the scene, or even changing window details! -/// -/// The update loop is just a method of the Engine struct -/// that have one argument which is a callback function. -/// ``` -/// -/// ``` -/// [THE DATA HERE IS WORK IN PROGRESS!] -pub struct Engine { - /// The renderer does exactly what it is called. - /// It works with the GPU to render frames according to the data you gave it. - pub renderer: Renderer, - /// The event_loop handles the events of the window and inputs. - /// - /// #### USED INTERNALLY - pub event_loop_control_flow: crate::winit::event_loop::ControlFlow, - /// The window handles everything about window and inputs. - /// This includes ability to modify window and listen toinput devices for changes. - /// - /// ### The window is not available before update_loop. - pub window: Window, - /// The object system is a way to make it easier to work with the engine. - /// Obviously you can work without it, but it's for those who - /// do not have the know-how, or wish to handle all the work of rendering data manually. - pub objects: ObjectStorage, - /// The camera handles the way the scene looks when rendered. - /// You can modify everything there is to camera through this. - pub camera: CameraContainer, - /// Handles all engine plugins - pub signals: SignalStorage, - - /// holds the update_loop function - /// - /// #### USED INTERNALLY - #[allow(clippy::type_complexity)] - pub update_loop: Option< - Box< - dyn 'static - + FnMut( - // Core - &mut Renderer, - &mut Window, - &mut ObjectStorage, - &crate::utils::winit_input_helper::WinitInputHelper, - &mut CameraContainer, - &mut crate::SignalStorage, - ), - >, - >, - - /// input events - /// - /// #### USED INTERNALLY - pub input_events: crate::utils::winit_input_helper::WinitInputHelper, -} -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: nalgebra_glm::Vec3, - /// The target at which the camera should be looking - pub target: nalgebra_glm::Vec3, - /// The up vector of the camera. This defines the elevation of the camera - pub up: nalgebra_glm::Vec3, - /// The resolution of the camera view - pub resolution: (f32, f32), - /// 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: nalgebra_glm::Vec3, - /// The rotation of the instance - pub rotation: nalgebra_glm::Vec3, - /// The scale of the instance - pub scale: nalgebra_glm::Vec3, -} - -/// 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 - #[allow(clippy::too_many_arguments)] - fn init( - &mut self, - _renderer: &mut crate::Renderer, - _window: &crate::Window, - _objects: &mut ObjectStorage, - _camera: &mut crate::CameraContainer, - ) { - } - - /// This is ran at the device events when available - #[allow(clippy::too_many_arguments)] - fn device_events( - &mut self, - _renderer: &mut crate::Renderer, - _window: &crate::Window, - _objects: &mut ObjectStorage, - _events: &crate::DeviceEvent, - _input: &crate::InputHelper, - _camera: &mut crate::CameraContainer, - ) { - } - - /// This is ran at the window events when available - #[allow(clippy::too_many_arguments)] - fn window_events( - &mut self, - _renderer: &mut crate::Renderer, - _window: &crate::Window, - _objects: &mut ObjectStorage, - _events: &crate::WindowEvent, - _input: &crate::InputHelper, - _camera: &mut crate::CameraContainer, - ) { - } - - /// ran before the frame is rendered - #[allow(clippy::too_many_arguments)] - fn frame( - &mut self, - _renderer: &mut crate::Renderer, - _window: &crate::Window, - _objects: &mut ObjectStorage, - _camera: &mut crate::CameraContainer, - _input: &crate::InputHelper, - _encoder: &mut crate::CommandEncoder, - _view: &crate::TextureView, - ) { - } -} -// The engine needs to know the functions of Signal to do things internally, -// 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 - } -} - -/// A unified way to handle strings -pub trait StringBuffer: StringBufferTrait + Clone {} -/// A trait for [StringBuffer] -pub trait StringBufferTrait { - /// Returns the string as &[`str`] - fn as_str(&self) -> &str; - /// Returns the string as [`String`] - fn as_string(&self) -> String; - /// Returns Arc for ease of computation - fn as_arc(&self) -> std::sync::Arc; -} - -impl StringBufferTrait for String { - fn as_str(&self) -> &str { - self.as_ref() - } - fn as_string(&self) -> String { - self.clone() - } - fn as_arc(&self) -> std::sync::Arc { - self.as_str().into() - } -} -impl StringBuffer for String {} -impl StringBufferTrait for &str { - fn as_str(&self) -> &str { - self - } - fn as_string(&self) -> String { - self.to_string() - } - fn as_arc(&self) -> std::sync::Arc { - self.as_str().into() - } -} -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 -); +/* + * 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 +pub mod uniform_buffer; +pub use imports::*; +pub use uniform_buffer::*; + +use downcast::{downcast, Any}; + +/// The uint type used for indices and more +#[cfg(feature = "u16")] +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 + } + } + 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; + + fn deref(&self) -> &Self::Target { + &self.$field + } + } + impl std::ops::DerefMut for $struct { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.$field + } + } + }; +} + +/// Will contain all details about a vertex and will be sent to GPU +// Will be turned to C code and sent to GPU +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Vertex { + /// Contains position data for the vertex in 3D space + pub position: Vector3, + /// Contains uv position data for the vertex + pub uv: Vector2, + /// Contains the normal face of the vertex + pub normal: Vector3, +} +impl Vertex { + pub(crate) fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + // This should be replaced with `std::mem::size_of::() as wgpu::BufferAddress` + offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, + shader_location: 2, + format: wgpu::VertexFormat::Float32x3, + }, + ], + } + } +} +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. +/// It gives a set of default data to work with, +/// but also allow you to go beyond that and work as low level as you wish to. +/// +/// You can also use the Engine to build you own custom structure the way you wish for it to be. +/// Possibilities are endless! +/// +/// To start using the Blue Engine, you can start by creating a new Engine like follows: +/// ``` +/// use blue_engine::header::{Engine, WindowDescriptor}; +/// +/// fn main() { +/// let engine = Engine::new().expect("Couldn't create the engine"); +/// } +/// ``` +/// The WindowDescriptor simply holds what features you would like for your window. +/// If you are reading this on later version of +/// the engine, you might be able to even run the engine in headless mode +/// meaning there would not be a need for a window and the +/// renders would come as image files. +/// +/// If you so wish to have a window, you would need to start a window update loop. +/// The update loop of window runs a frame every few millisecond, +/// and gives you details of what is happening during this time, like input events. +/// You can also modify existing parts of the engine during +/// this update loop, such as changing camera to look differently, +/// or creating a new object on the scene, or even changing window details! +/// +/// The update loop is just a method of the Engine struct +/// that have one argument which is a callback function. +/// ``` +/// +/// ``` +/// [THE DATA HERE IS WORK IN PROGRESS!] +pub struct Engine { + /// The renderer does exactly what it is called. + /// It works with the GPU to render frames according to the data you gave it. + pub renderer: Renderer, + /// The event_loop handles the events of the window and inputs. + /// + /// #### USED INTERNALLY + pub event_loop_control_flow: crate::winit::event_loop::ControlFlow, + /// The window handles everything about window and inputs. + /// This includes ability to modify window and listen toinput devices for changes. + /// + /// ### The window is not available before update_loop. + pub window: Window, + /// The object system is a way to make it easier to work with the engine. + /// Obviously you can work without it, but it's for those who + /// do not have the know-how, or wish to handle all the work of rendering data manually. + pub objects: ObjectStorage, + /// The camera handles the way the scene looks when rendered. + /// You can modify everything there is to camera through this. + pub camera: CameraContainer, + /// Handles all engine plugins + pub signals: SignalStorage, + + /// holds the update_loop function + /// + /// #### USED INTERNALLY + #[allow(clippy::type_complexity)] + pub update_loop: Option< + Box< + dyn 'static + + FnMut( + // Core + &mut Renderer, + &mut Window, + &mut ObjectStorage, + &crate::utils::winit_input_helper::WinitInputHelper, + &mut CameraContainer, + &mut crate::SignalStorage, + ), + >, + >, + + /// input events + /// + /// #### USED INTERNALLY + pub input_events: crate::utils::winit_input_helper::WinitInputHelper, +} +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 + #[allow(clippy::too_many_arguments)] + fn init( + &mut self, + _renderer: &mut crate::Renderer, + _window: &crate::Window, + _objects: &mut ObjectStorage, + _camera: &mut crate::CameraContainer, + ) { + } + + /// This is ran at the device events when available + #[allow(clippy::too_many_arguments)] + fn device_events( + &mut self, + _renderer: &mut crate::Renderer, + _window: &crate::Window, + _objects: &mut ObjectStorage, + _events: &crate::DeviceEvent, + _input: &crate::InputHelper, + _camera: &mut crate::CameraContainer, + ) { + } + + /// This is ran at the window events when available + #[allow(clippy::too_many_arguments)] + fn window_events( + &mut self, + _renderer: &mut crate::Renderer, + _window: &crate::Window, + _objects: &mut ObjectStorage, + _events: &crate::WindowEvent, + _input: &crate::InputHelper, + _camera: &mut crate::CameraContainer, + ) { + } + + /// ran before the frame is rendered + #[allow(clippy::too_many_arguments)] + fn frame( + &mut self, + _renderer: &mut crate::Renderer, + _window: &crate::Window, + _objects: &mut ObjectStorage, + _camera: &mut crate::CameraContainer, + _input: &crate::InputHelper, + _encoder: &mut crate::CommandEncoder, + _view: &crate::TextureView, + ) { + } +} +// The engine needs to know the functions of Signal to do things internally, +// 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 + } +} + +/// A unified way to handle strings +pub trait StringBuffer: StringBufferTrait + Clone {} +/// A trait for [StringBuffer] +pub trait StringBufferTrait { + /// Returns the string as &[`str`] + fn as_str(&self) -> &str; + /// Returns the string as [`String`] + fn as_string(&self) -> String; + /// Returns Arc for ease of computation + fn as_arc(&self) -> std::sync::Arc; +} + +impl StringBufferTrait for String { + fn as_str(&self) -> &str { + self.as_ref() + } + fn as_string(&self) -> String { + self.clone() + } + fn as_arc(&self) -> std::sync::Arc { + self.as_str().into() + } +} +impl StringBuffer for String {} +impl StringBufferTrait for &str { + fn as_str(&self) -> &str { + self + } + fn as_string(&self) -> String { + self.to_string() + } + fn as_arc(&self) -> std::sync::Arc { + self.as_str().into() + } +} +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/lib.rs b/crates/blue_engine_core/src/lib.rs index 1be52d6..17d8610 100644 --- a/crates/blue_engine_core/src/lib.rs +++ b/crates/blue_engine_core/src/lib.rs @@ -1,84 +1,86 @@ -/* - * Blue Engine by Elham Aryanpur - * - * The license is same as the one on the root. -*/ - -#![warn(missing_docs)] -#![allow(clippy::needless_doctest_main)] - -//! -//! -//! # Blue Engine -//! -//! Blue Engine is an easy to use, portable, and extendable/customizable graphics engine. Here -//! lives the documentation for the engine. -//! -//! ## Setup -//! -//! The setup and installation details live in the project's [guide](https://aryanpurtech.github.io/BlueEngineDocs/). -//! A basic program in Blue Engine is as follow: -//! -//! ## Example -//! -//! ```rust -//! use blue_engine::{ -//! header::{ Engine, ObjectSettings }, -//! primitive_shapes::triangle -//! }; -//! -//! fn main() { -//! // initialize the engine -//! let mut engine = Engine::new(); -//! -//! // create a triangle -//! triangle("my triangle", ObjectSettings::default(), &mut engine.renderer, &mut engine.objects); -//! -//! // run the engine -//! engine -//! .update_loop(move |_, _, _, _, _, _| {}); -//! } -//! ``` -//! -//! ## Utilities -//! -//! This crate is the core of the engine, but there is also [utilities crate](https://github.com/AryanpurTech/BlueEngineUtilities) -//! which have a lot of utilities for the engine such as lighting, physics, etc. -//! -//! ## Guide for code navigation -//! -//! The code of the engine is organized in a rather different manner than traditional in the -//! language. There are inspirations from other languages to make it easier to navigate the -//! project. -//! -//! ## Older hardware -//! -//! The engine uses WGPU under the hood for rendering. WGPU by nature is designed for modern hardware, so if you have or want to -//! target older hardware, you might need to add a couple more things to WindowDescriptor during Engine::new_config: -//! -//! 1) set a backend that targets your older hardware, such as GL using the backends field: `backend: blue_engine::wgpu::Backends::GL` -//! 2) experiement with the limits field, which describes what features you need. `limits: blue_engine::wgpu::Limits::default()`. there -//! are three options for limits: `default` for normal hardware, `downlevel_defaults` which are compatible with GLES-3 and D3D-11, or -//! `downlevel_webgl2_defaults` which is also compatible with WebGL2, and the lowest level for limits and can support very old hardware. -//! -//! with these two changes, hopefully you can get Blue Engine to run on older hardware. If not, please let me know so I can help you further. - -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 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 creation of window and instance creation. -pub mod window; -#[doc(inline)] -pub use crate::header::*; +/* + * Blue Engine by Elham Aryanpur + * + * The license is same as the one on the root. +*/ + +#![warn(missing_docs)] +#![allow(clippy::needless_doctest_main)] + +//! +//! +//! # Blue Engine +//! +//! Blue Engine is an easy to use, portable, and extendable/customizable graphics engine. Here +//! lives the documentation for the engine. +//! +//! ## Setup +//! +//! The setup and installation details live in the project's [guide](https://aryanpurtech.github.io/BlueEngineDocs/). +//! A basic program in Blue Engine is as follow: +//! +//! ## Example +//! +//! ```rust +//! use blue_engine::{ +//! header::{ Engine, ObjectSettings }, +//! primitive_shapes::triangle +//! }; +//! +//! fn main() { +//! // initialize the engine +//! let mut engine = Engine::new(); +//! +//! // create a triangle +//! triangle("my triangle", ObjectSettings::default(), &mut engine.renderer, &mut engine.objects); +//! +//! // run the engine +//! engine +//! .update_loop(move |_, _, _, _, _, _| {}); +//! } +//! ``` +//! +//! ## Utilities +//! +//! This crate is the core of the engine, but there is also [utilities crate](https://github.com/AryanpurTech/BlueEngineUtilities) +//! which have a lot of utilities for the engine such as lighting, physics, etc. +//! +//! ## Guide for code navigation +//! +//! The code of the engine is organized in a rather different manner than traditional in the +//! language. There are inspirations from other languages to make it easier to navigate the +//! project. +//! +//! ## Older hardware +//! +//! The engine uses WGPU under the hood for rendering. WGPU by nature is designed for modern hardware, so if you have or want to +//! target older hardware, you might need to add a couple more things to WindowDescriptor during Engine::new_config: +//! +//! 1) set a backend that targets your older hardware, such as GL using the backends field: `backend: blue_engine::wgpu::Backends::GL` +//! 2) experiement with the limits field, which describes what features you need. `limits: blue_engine::wgpu::Limits::default()`. there +//! are three options for limits: `default` for normal hardware, `downlevel_defaults` which are compatible with GLES-3 and D3D-11, or +//! `downlevel_webgl2_defaults` which is also compatible with WebGL2, and the lowest level for limits and can support very old hardware. +//! +//! with these two changes, hopefully you can get Blue Engine to run on older hardware. If not, please let me know so I can help you further. + +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 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; +/// contains definition for creation of window and instance creation. +pub mod window; +#[doc(inline)] +pub use crate::header::*; diff --git a/crates/blue_engine_core/src/objects.rs b/crates/blue_engine_core/src/objects.rs index 022004d..782267a 100644 --- a/crates/blue_engine_core/src/objects.rs +++ b/crates/blue_engine_core/src/objects.rs @@ -1,711 +1,706 @@ -/* - * Blue Engine by Elham Aryanpur - * - * 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::uniform_type::{Array4, Matrix}; -use crate::utils::default_resources::{DEFAULT_MATRIX_4, DEFAULT_SHADER, DEFAULT_TEXTURE}; -use crate::{ObjectStorage, RotateAmount, StringBuffer, UnsignedIntType}; - -impl Renderer { - /// Creates a new object - /// - /// Is used to define a new object and add it to the storage. This offers full customizability - /// and a framework for in-engine shapes to be developed. - /// - /// # Arguments - /// * `name` - The name of the object. - /// * `vertices` - A list of vertices for the object to draw with - /// * `indices` - A list of indices that references the vertices, defining draw order - /// * `settings` - The settings of the object - pub fn build_object( - &mut self, - name: impl StringBuffer, - vertices: Vec, - indices: Vec, - settings: ObjectSettings, - ) -> Result { - let vertex_buffer = self.build_vertex_buffer(&vertices, &indices); - - let uniform = self.build_uniform_buffer(&vec![ - self.build_uniform_buffer_part("Transformation Matrix", DEFAULT_MATRIX_4), - self.build_uniform_buffer_part( - "Color", - crate::uniform_type::Array4 { - data: crate::utils::default_resources::DEFAULT_COLOR, - }, - ), - ]); - - let shader_source = - ShaderBuilder::new(DEFAULT_SHADER.to_string(), settings.camera_effect.clone()); - - let shader = self.build_shader( - name.as_str(), - shader_source.shader.clone(), - Some(&uniform.1), - settings.shader_settings, - ); - - let texture = self.build_texture( - "Default Texture", - TextureData::Bytes(DEFAULT_TEXTURE.to_vec()), - crate::header::TextureMode::Clamp, - //crate::header::TextureFormat::PNG - )?; - - let instance = Instance::new( - [0f32, 0f32, 0f32].into(), - [0f32, 0f32, 0f32].into(), - [1f32, 1f32, 1f32].into(), - ); - - let instance_buffer = self.build_instance(vec![instance.to_raw()]); - - Ok(Object { - name: name.as_arc(), - vertices, - indices, - pipeline: Pipeline { - vertex_buffer: PipelineData::Data(vertex_buffer), - shader: PipelineData::Data(shader), - texture: PipelineData::Data(texture), - uniform: PipelineData::Data(Some(uniform.0)), - }, - instances: vec![instance], - instance_buffer, - uniform_layout: uniform.1, - size: glm::vec3(1f32, 1f32, 1f32), - position: glm::vec3(0f32, 0f32, 0f32), - rotation: glm::vec3(0f32, 0f32, 0f32), - changed: false, - position_matrix: DEFAULT_MATRIX_4.to_im(), - scale_matrix: DEFAULT_MATRIX_4.to_im(), - rotation_matrix: DEFAULT_MATRIX_4.to_im(), - inverse_transformation_matrix: Matrix::from_im(nalgebra_glm::transpose( - &nalgebra_glm::inverse(&DEFAULT_MATRIX_4.to_im()), - )), - color: crate::uniform_type::Array4 { - data: crate::utils::default_resources::DEFAULT_COLOR, - }, - shader_builder: shader_source, - shader_settings: settings.shader_settings, - camera_effect: settings.camera_effect, - uniform_buffers: vec![ - self.build_uniform_buffer_part("Transformation Matrix", DEFAULT_MATRIX_4), - self.build_uniform_buffer_part( - "Color", - crate::uniform_type::Array4 { - data: crate::utils::default_resources::DEFAULT_COLOR, - }, - ), - ], - is_visible: true, - render_order: 0, - }) - } -} - -impl ObjectStorage { - /// Creates a new object - pub fn new_object( - &mut self, - name: impl StringBuffer, - vertices: Vec, - indices: Vec, - settings: ObjectSettings, - renderer: &mut Renderer, - ) { - match renderer.build_object(name.clone(), vertices, indices, settings) { - Ok(object) => self.add_object(name.clone(), object), - Err(e) => { - eprintln!("Could not create a new Object: {e:#?}"); - } - } - } - - /// Adds an object to the storage - pub fn add_object(&mut self, key: impl StringBuffer, object: Object) { - fn add_object_inner(object_storage: &mut ObjectStorage, key: String, object: Object) { - object_storage.insert(key, object); - } - add_object_inner(self, key.as_string(), object); - } - - /// Allows for safe update of objects - pub fn update_object(&mut self, key: impl StringBuffer, callback: T) { - fn update_object_inner( - object_storage: &mut ObjectStorage, - key: String, - callback: T, - ) { - let object = object_storage.get_mut(&key); - if let Some(object) = object { - callback(object); - } - } - update_object_inner(self, key.as_string(), callback); - } -} - -impl Object { - /// Sets the name of the object - pub fn set_name(&mut self, name: impl StringBuffer) -> &mut Self { - self.name = name.as_arc(); - - self - } - - /// Scales an object. e.g. 2.0 doubles the size and 0.5 halves - pub fn set_scale(&mut self, x: f32, y: f32, z: f32) -> &mut Self { - self.size.x *= x; - self.size.y *= y; - self.size.z *= z; - - let transformation_matrix = self.scale_matrix; - let result = nalgebra_glm::scale(&transformation_matrix, &nalgebra_glm::vec3(x, y, z)); - self.scale_matrix = result; - self.inverse_matrices(); - - self.changed = true; - self - } - - /// Resizes an object in pixels which are relative to the window - pub fn resize( - &mut self, - width: f32, - height: f32, - depth: f32, - window_size: winit::dpi::PhysicalSize, - ) -> &mut Self { - 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 - } - } else { - 0.0 - }; - - 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 - } - } 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 - } - } else { - 0.0 - }; - - self.set_scale( - difference_in_width, - difference_in_height, - difference_in_depth, - ); - self - } - - /// Rotates the object in the axis you specify - /// - /// THIS METHOD IS DEPRECATED, USE [crate::Object::set_rotation] or [crate::Object::rotate] - #[deprecated] - pub fn set_rotatation(&mut self, angle: f32, axis: RotateAxis) -> &mut Self { - let mut rotation_matrix = self.rotation_matrix; - let axis = match axis { - RotateAxis::X => { - self.rotation.x += angle; - nalgebra_glm::Vec3::x_axis() - } - RotateAxis::Y => { - self.rotation.y += angle; - nalgebra_glm::Vec3::y_axis() - } - RotateAxis::Z => { - self.rotation.z += angle; - nalgebra_glm::Vec3::z_axis() - } - }; - - rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, angle.to_radians(), &axis); - self.rotation_matrix = rotation_matrix; - self.inverse_matrices(); - - self.changed = true; - self - } - - /// Sets the rotation of the object in the axis you specify - pub fn set_rotation(&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; - nalgebra_glm::Vec3::x_axis() - } - RotateAxis::Y => { - self.rotation.y = amount_radians; - nalgebra_glm::Vec3::y_axis() - } - RotateAxis::Z => { - self.rotation.z = amount_radians; - nalgebra_glm::Vec3::z_axis() - } - }; - - rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, amount_radians, &axis); - self.rotation_matrix = rotation_matrix; - self.inverse_matrices(); - - self.changed = true; - self - } - - /// 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; - nalgebra_glm::Vec3::x_axis() - } - RotateAxis::Y => { - self.rotation.y += amount_radians; - nalgebra_glm::Vec3::y_axis() - } - RotateAxis::Z => { - self.rotation.z += amount_radians; - nalgebra_glm::Vec3::z_axis() - } - }; - - rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, amount_radians, &axis); - self.rotation_matrix = rotation_matrix; - self.inverse_matrices(); - - self.changed = true; - self - } - - /// Moves the object by the amount you specify in the axis you specify - pub fn set_translation(&mut self, x: f32, y: f32, z: f32) -> &mut Self { - self.position.x -= x; - self.position.y -= y; - self.position.z -= z; - - let mut position_matrix = self.position_matrix; - position_matrix = nalgebra_glm::translate(&position_matrix, &nalgebra_glm::vec3(x, y, z)); - self.position_matrix = position_matrix; - - self.inverse_matrices(); - self.changed = true; - self - } - - /// Sets the position of the object in 3D space relative to the window - pub fn set_position(&mut self, x: f32, y: f32, z: f32) -> &mut Self { - self.set_translation( - (self.position.x - x) * -1f32, - (self.position.y - y) * -1f32, - (self.position.z - z) * -1f32, - ); - - self.position.x = x; - self.position.y = y; - self.position.z = z; - self - } - - /// 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.changed = true; - self - } - - /// Changes the render order of the Object. - /// - /// Objects with higher number get rendered later and appear "on top" when occupying the same space - pub fn set_render_order(&mut self, render_order: usize) -> &mut Self { - self.render_order = render_order; - - self - } - - /// Replaces the object's texture with provided one - pub fn set_texture(&mut self, texture: Textures) -> &mut Self { - self.pipeline.texture = PipelineData::Data(texture); - self.changed = true; - - self - } - - /// This will flag object as changed and altered, leading to rebuilding parts, or entirety on next frame. - /// Best used if you directly altered fields of the object. The functions normally flag the object as - /// changed on every call anyways. But this function is to manually flag it yourself. - pub fn flag_as_changed(&mut self, is_changed: bool) { - self.changed = is_changed; - } - - /// Sets if the object will be rendered or not - pub fn set_visibility(&mut self, is_visible: bool) { - self.is_visible = is_visible; - } - - /// 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), - ))); - } - - /// Update and apply changes done to an object - pub fn update(&mut self, renderer: &mut Renderer) { - self.update_vertex_buffer(renderer); - self.update_uniform_buffer(renderer); - self.update_shader(renderer); - self.update_instance_buffer(renderer); - self.changed = false; - } - - /// Update and apply changes done to an object and returns a pipeline - pub fn update_and_return( - &mut self, - renderer: &mut Renderer, - ) -> (crate::VertexBuffers, crate::UniformBuffers, crate::Shaders) { - let vertex_buffer = self.update_vertex_buffer_and_return(renderer); - let uniform_buffer = self.update_uniform_buffer_and_return(renderer); - let shader = self.update_shader_and_return(renderer); - self.changed = false; - (vertex_buffer, uniform_buffer, shader) - } - - /// Update and apply changes done to the vertex buffer - pub fn update_vertex_buffer(&mut self, renderer: &mut Renderer) { - let updated_buffer = renderer.build_vertex_buffer(&self.vertices, &self.indices); - self.pipeline.vertex_buffer = PipelineData::Data(updated_buffer); - } - - /// Returns the buffer with ownership - pub fn update_vertex_buffer_and_return( - &mut self, - renderer: &mut Renderer, - ) -> crate::VertexBuffers { - let updated_buffer = renderer.build_vertex_buffer(&self.vertices, &self.indices); - let updated_buffer_2 = renderer.build_vertex_buffer(&self.vertices, &self.indices); - self.pipeline.vertex_buffer = PipelineData::Data(updated_buffer); - - updated_buffer_2 - } - - /// Update and apply changes done to the shader - pub fn update_shader(&mut self, renderer: &mut Renderer) { - let updated_shader = renderer.build_shader( - self.name.as_ref(), - self.shader_builder.shader.clone(), - Some(&self.uniform_layout), - self.shader_settings, - ); - self.pipeline.shader = PipelineData::Data(updated_shader); - } - - /// Returns the buffer with ownership - pub fn update_shader_and_return(&mut self, renderer: &mut Renderer) -> crate::Shaders { - let updated_shader = renderer.build_shader( - self.name.as_ref(), - self.shader_builder.shader.clone(), - Some(&self.uniform_layout), - self.shader_settings, - ); - let updated_shader2 = renderer.build_shader( - self.name.as_ref(), - self.shader_builder.shader.clone(), - Some(&self.uniform_layout), - self.shader_settings, - ); - self.pipeline.shader = PipelineData::Data(updated_shader); - - updated_shader2 - } - - /// Update and apply changes done to the uniform buffer - 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.uniform_buffers[1] = renderer.build_uniform_buffer_part("Color", self.color); - - let updated_buffer = renderer.build_uniform_buffer(&self.uniform_buffers); - - self.pipeline.uniform = PipelineData::Data(Some(updated_buffer.0)); - self.uniform_layout = updated_buffer.1; - } - - /// Returns the buffer with ownership - pub fn update_uniform_buffer_and_return( - &mut self, - renderer: &mut Renderer, - ) -> 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.uniform_buffers[1] = renderer.build_uniform_buffer_part("Color", self.color); - - let updated_buffer = renderer.build_uniform_buffer(&self.uniform_buffers); - let updated_buffer2 = renderer.build_uniform_buffer(&self.uniform_buffers); - - self.pipeline.uniform = PipelineData::Data(Some(updated_buffer.0)); - self.uniform_layout = updated_buffer.1; - - updated_buffer2.0 - } - - /// Updates the instance buffer - pub fn update_instance_buffer(&mut self, renderer: &mut Renderer) { - let instance_data = self - .instances - .iter() - .map(Instance::to_raw) - .collect::>(); - let instance_buffer = renderer.build_instance(instance_data); - self.instance_buffer = instance_buffer; - } - - /// Returns the buffer with ownership - pub fn update_instance_buffer_and_return(&mut self, renderer: &mut Renderer) -> wgpu::Buffer { - let instance_data = self - .instances - .iter() - .map(Instance::to_raw) - .collect::>(); - let instance_buffer = renderer.build_instance(instance_data.clone()); - let instance_buffer2 = renderer.build_instance(instance_data); - - self.instance_buffer = instance_buffer; - instance_buffer2 - } - - // ============================= FOR COPY OF PIPELINES ============================= - /// References another object's vertices - pub fn reference_vertices(&mut self, object_id: impl StringBuffer) -> &mut Self { - self.pipeline.vertex_buffer = PipelineData::Copy(object_id.as_string()); - self - } - - /// References another object's shader - pub fn reference_shader(&mut self, object_id: impl StringBuffer) -> &mut Self { - self.pipeline.shader = PipelineData::Copy(object_id.as_string()); - self - } - - /// References another object's texture - pub fn reference_texture(&mut self, object_id: impl StringBuffer) -> &mut Self { - self.pipeline.texture = PipelineData::Copy(object_id.as_string()); - self - } - - /// References another object's uniform buffer - pub fn reference_uniform_buffer(&mut self, object_id: impl StringBuffer) -> &mut Self { - self.pipeline.uniform = PipelineData::Copy(object_id.as_string()); - self - } - - // ============================= Instances ============================= - /// Add an instance to the object - pub fn add_instance(&mut self, instance: Instance) -> &mut Self { - self.instances.push(instance); - self.changed = true; - self - } -} - -/// Configuration type for ShaderBuilder -pub type ShaderConfigs = Vec<(String, Box>) -> String>)>; - -/// Helps with building and updating shader code -pub struct ShaderBuilder { - /// the shader itself - pub shader: String, - /// Should the camera effect be applied - pub camera_effect: Option>, - /// configurations to be applied to the shader - pub configs: ShaderConfigs, -} - -impl ShaderBuilder { - /// Creates a new shader builder - pub fn new(shader_source: String, camera_effect: Option>) -> Self { - let mut shader_builder = Self { - shader: shader_source, - camera_effect, - configs: vec![ - ( - "//@CAMERA_STRUCT".to_string(), - Box::new(|camera_effect| { - if camera_effect.is_some() { - r#"struct CameraUniforms { - camera_matrix: mat4x4, - }; - @group(1) @binding(0) - var camera_uniform: CameraUniforms;"# - .to_string() - } else { - "".to_string() - } - }), - ), - ( - "//@CAMERA_VERTEX".to_string(), - Box::new(|camera_effect| { - if camera_effect.is_some() { - r#"out.position = camera_uniform.camera_matrix * model_matrix * (transform_uniform.transform_matrix * vec4(input.position, 1.0));"# - .to_string() - } else { - r#"out.position = model_matrix * (transform_uniform.transform_matrix * vec4(input.position, 1.0));"#.to_string() - } - }), - ), - ], - }; - shader_builder.build(); - - shader_builder - } - - /// Sets the new shader - pub fn set_shader(&mut self, new_shader: String) { - self.shader = new_shader; - self.build(); - } - - /// Builds the shader with the configuration defined - pub fn build(&mut self) { - for i in &self.configs { - self.shader = self.shader.replace(&i.0, &i.1(self.camera_effect.clone())); - } - } -} - -impl Instance { - /// Creates a new instance - pub fn new(position: glm::Vec3, rotation: glm::Vec3, scale: glm::Vec3) -> Self { - Self { - position, - rotation, - scale, - } - } - - /// 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); - let rotation_matrix = nalgebra_glm::rotate(&DEFAULT_MATRIX_4.to_im(), 0f32, &self.rotation); - let scale_matrix = glm::scale(&DEFAULT_MATRIX_4.to_im(), &self.scale); - InstanceRaw { - model: Matrix::from_im(position_matrix * rotation_matrix * scale_matrix), - } - } - - /// Sets the position - pub fn set_position(&mut self, position: glm::Vec3) { - self.position = position; - } - - /// Sets the rotation - pub fn set_rotation(&mut self, rotation: glm::Vec3) { - self.rotation = rotation; - } - - /// Sets the scale - pub fn set_scale(&mut self, scale: glm::Vec3) { - self.scale = scale; - } -} - -impl Default for Instance { - fn default() -> Self { - Self { - position: glm::Vec3::new(0.0, 0.0, 0.0), - rotation: glm::Vec3::new(0.0, 0.0, 0.0), - scale: glm::Vec3::new(1.0, 1.0, 1.0), - } - } -} - -impl InstanceRaw { - /// Instance's layout description - pub fn desc() -> wgpu::VertexBufferLayout<'static> { - use std::mem; - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, - // We need to switch from using a step mode of Vertex to Instance - // This means that our shaders will only change to use the next - // instance when the shader starts processing a new instance - step_mode: wgpu::VertexStepMode::Instance, - attributes: &[ - // A mat4 takes up 4 vertex slots as it is technically 4 vec4s. We need to define a slot - // for each vec4. We'll have to reassemble the mat4 in the shader. - wgpu::VertexAttribute { - offset: 0, - shader_location: 3, - format: wgpu::VertexFormat::Float32x4, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, - shader_location: 4, - format: wgpu::VertexFormat::Float32x4, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, - shader_location: 5, - format: wgpu::VertexFormat::Float32x4, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress, - shader_location: 6, - format: wgpu::VertexFormat::Float32x4, - }, - ], - } - } -} +/* + * Blue Engine by Elham Aryanpur + * + * 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::uniform_type::{Array4, Matrix}; +use crate::utils::default_resources::{DEFAULT_MATRIX_4, DEFAULT_SHADER, DEFAULT_TEXTURE}; +use crate::{ObjectStorage, RotateAmount, StringBuffer, UnsignedIntType, Vector3}; + +impl Renderer { + /// Creates a new object + /// + /// Is used to define a new object and add it to the storage. This offers full customizability + /// and a framework for in-engine shapes to be developed. + /// + /// # Arguments + /// * `name` - The name of the object. + /// * `vertices` - A list of vertices for the object to draw with + /// * `indices` - A list of indices that references the vertices, defining draw order + /// * `settings` - The settings of the object + pub fn build_object( + &mut self, + name: impl StringBuffer, + vertices: Vec, + indices: Vec, + settings: ObjectSettings, + ) -> Result { + let vertex_buffer = self.build_vertex_buffer(&vertices, &indices); + + let uniform = self.build_uniform_buffer(&vec![ + self.build_uniform_buffer_part("Transformation Matrix", DEFAULT_MATRIX_4), + self.build_uniform_buffer_part( + "Color", + crate::uniform_type::Array4 { + data: crate::utils::default_resources::DEFAULT_COLOR, + }, + ), + ]); + + let shader_source = + ShaderBuilder::new(DEFAULT_SHADER.to_string(), settings.camera_effect.clone()); + + let shader = self.build_shader( + name.as_str(), + shader_source.shader.clone(), + Some(&uniform.1), + settings.shader_settings, + ); + + let texture = self.build_texture( + "Default Texture", + TextureData::Bytes(DEFAULT_TEXTURE.to_vec()), + crate::header::TextureMode::Clamp, + //crate::header::TextureFormat::PNG + )?; + + let instance = Instance::new([0f32, 0f32, 0f32], [0f32, 0f32, 0f32], [1f32, 1f32, 1f32]); + + let instance_buffer = self.build_instance(vec![instance.to_raw()]); + + Ok(Object { + name: name.as_arc(), + vertices, + indices, + pipeline: Pipeline { + vertex_buffer: PipelineData::Data(vertex_buffer), + shader: PipelineData::Data(shader), + texture: PipelineData::Data(texture), + uniform: PipelineData::Data(Some(uniform.0)), + }, + instances: vec![instance], + instance_buffer, + uniform_layout: uniform.1, + size: Vector3::new(1f32, 1f32, 1f32), + position: Vector3::default(), + rotation: Vector3::new(0f32, 0f32, 0f32), + changed: false, + position_matrix: DEFAULT_MATRIX_4.to_im(), + scale_matrix: DEFAULT_MATRIX_4.to_im(), + rotation_matrix: DEFAULT_MATRIX_4.to_im(), + inverse_transformation_matrix: Matrix::from_im(nalgebra_glm::transpose( + &nalgebra_glm::inverse(&DEFAULT_MATRIX_4.to_im()), + )), + color: crate::uniform_type::Array4 { + data: crate::utils::default_resources::DEFAULT_COLOR, + }, + shader_builder: shader_source, + shader_settings: settings.shader_settings, + camera_effect: settings.camera_effect, + uniform_buffers: vec![ + self.build_uniform_buffer_part("Transformation Matrix", DEFAULT_MATRIX_4), + self.build_uniform_buffer_part( + "Color", + crate::uniform_type::Array4 { + data: crate::utils::default_resources::DEFAULT_COLOR, + }, + ), + ], + is_visible: true, + render_order: 0, + }) + } +} + +impl ObjectStorage { + /// Creates a new object + pub fn new_object( + &mut self, + name: impl StringBuffer, + vertices: Vec, + indices: Vec, + settings: ObjectSettings, + renderer: &mut Renderer, + ) { + match renderer.build_object(name.clone(), vertices, indices, settings) { + Ok(object) => self.add_object(name.clone(), object), + Err(e) => { + eprintln!("Could not create a new Object: {e:#?}"); + } + } + } + + /// Adds an object to the storage + pub fn add_object(&mut self, key: impl StringBuffer, object: Object) { + fn add_object_inner(object_storage: &mut ObjectStorage, key: String, object: Object) { + object_storage.insert(key, object); + } + add_object_inner(self, key.as_string(), object); + } + + /// Allows for safe update of objects + pub fn update_object(&mut self, key: impl StringBuffer, callback: T) { + fn update_object_inner( + object_storage: &mut ObjectStorage, + key: String, + callback: T, + ) { + let object = object_storage.get_mut(&key); + if let Some(object) = object { + callback(object); + } + } + update_object_inner(self, key.as_string(), callback); + } +} + +impl Object { + /// Sets the name of the object + pub fn set_name(&mut self, name: impl StringBuffer) -> &mut Self { + self.name = name.as_arc(); + + self + } + + /// Scales an object. e.g. 2.0 doubles the size and 0.5 halves + pub fn set_scale(&mut self, scale: impl Into) -> &mut Self { + let scale = scale.into(); + self.size *= scale; + + let transformation_matrix = self.scale_matrix; + let result = nalgebra_glm::scale(&transformation_matrix, &scale.into()); + self.scale_matrix = result; + self.inverse_matrices(); + + self.changed = true; + self + } + + /// Resizes an object in pixels which are relative to the window + pub fn resize( + &mut self, + width: f32, + height: f32, + depth: f32, + window_size: winit::dpi::PhysicalSize, + ) -> &mut Self { + 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 + } + } else { + 0.0 + }; + + 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 + } + } 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 + } + } else { + 0.0 + }; + + self.set_scale(Vector3::new( + difference_in_width, + difference_in_height, + difference_in_depth, + )); + self + } + + /// Rotates the object in the axis you specify + /// + /// THIS METHOD IS DEPRECATED, USE [crate::Object::set_rotation] or [crate::Object::rotate] + #[deprecated] + pub fn set_rotatation(&mut self, angle: f32, axis: RotateAxis) -> &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 + } + + /// Sets the rotation of the object in the axis you specify + pub fn set_rotation(&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() + } + RotateAxis::Y => { + self.rotation.y = amount_radians; + Vector3::y_axis() + } + RotateAxis::Z => { + self.rotation.z = amount_radians; + Vector3::z_axis() + } + }; + + rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, amount_radians, &axis.into()); + self.rotation_matrix = rotation_matrix; + self.inverse_matrices(); + + self.changed = true; + self + } + + /// 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() + } + RotateAxis::Y => { + self.rotation.y += amount_radians; + Vector3::y_axis() + } + RotateAxis::Z => { + self.rotation.z += amount_radians; + Vector3::z_axis() + } + }; + + rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, amount_radians, &axis.into()); + self.rotation_matrix = rotation_matrix; + self.inverse_matrices(); + + self.changed = true; + self + } + + /// 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.inverse_matrices(); + self.changed = true; + self + } + + /// 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.set_translation((self.position - new_pos) * -1f32); + + self.position.x = new_pos.x; + self.position.y = new_pos.y; + self.position.z = new_pos.z; + self + } + + /// 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.changed = true; + self + } + + /// Changes the render order of the Object. + /// + /// Objects with higher number get rendered later and appear "on top" when occupying the same space + pub fn set_render_order(&mut self, render_order: usize) -> &mut Self { + self.render_order = render_order; + + self + } + + /// Replaces the object's texture with provided one + pub fn set_texture(&mut self, texture: Textures) -> &mut Self { + self.pipeline.texture = PipelineData::Data(texture); + self.changed = true; + + self + } + + /// This will flag object as changed and altered, leading to rebuilding parts, or entirety on next frame. + /// Best used if you directly altered fields of the object. The functions normally flag the object as + /// changed on every call anyways. But this function is to manually flag it yourself. + pub fn flag_as_changed(&mut self, is_changed: bool) { + self.changed = is_changed; + } + + /// Sets if the object will be rendered or not + pub fn set_visibility(&mut self, is_visible: bool) { + self.is_visible = is_visible; + } + + /// 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), + ))); + } + + /// Update and apply changes done to an object + pub fn update(&mut self, renderer: &mut Renderer) { + self.update_vertex_buffer(renderer); + self.update_uniform_buffer(renderer); + self.update_shader(renderer); + self.update_instance_buffer(renderer); + self.changed = false; + } + + /// Update and apply changes done to an object and returns a pipeline + pub fn update_and_return( + &mut self, + renderer: &mut Renderer, + ) -> (crate::VertexBuffers, crate::UniformBuffers, crate::Shaders) { + let vertex_buffer = self.update_vertex_buffer_and_return(renderer); + let uniform_buffer = self.update_uniform_buffer_and_return(renderer); + let shader = self.update_shader_and_return(renderer); + self.changed = false; + (vertex_buffer, uniform_buffer, shader) + } + + /// Update and apply changes done to the vertex buffer + pub fn update_vertex_buffer(&mut self, renderer: &mut Renderer) { + let updated_buffer = renderer.build_vertex_buffer(&self.vertices, &self.indices); + self.pipeline.vertex_buffer = PipelineData::Data(updated_buffer); + } + + /// Returns the buffer with ownership + pub fn update_vertex_buffer_and_return( + &mut self, + renderer: &mut Renderer, + ) -> crate::VertexBuffers { + let updated_buffer = renderer.build_vertex_buffer(&self.vertices, &self.indices); + let updated_buffer_2 = renderer.build_vertex_buffer(&self.vertices, &self.indices); + self.pipeline.vertex_buffer = PipelineData::Data(updated_buffer); + + updated_buffer_2 + } + + /// Update and apply changes done to the shader + pub fn update_shader(&mut self, renderer: &mut Renderer) { + let updated_shader = renderer.build_shader( + self.name.as_ref(), + self.shader_builder.shader.clone(), + Some(&self.uniform_layout), + self.shader_settings, + ); + self.pipeline.shader = PipelineData::Data(updated_shader); + } + + /// Returns the buffer with ownership + pub fn update_shader_and_return(&mut self, renderer: &mut Renderer) -> crate::Shaders { + let updated_shader = renderer.build_shader( + self.name.as_ref(), + self.shader_builder.shader.clone(), + Some(&self.uniform_layout), + self.shader_settings, + ); + let updated_shader2 = renderer.build_shader( + self.name.as_ref(), + self.shader_builder.shader.clone(), + Some(&self.uniform_layout), + self.shader_settings, + ); + self.pipeline.shader = PipelineData::Data(updated_shader); + + updated_shader2 + } + + /// Update and apply changes done to the uniform buffer + 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.uniform_buffers[1] = renderer.build_uniform_buffer_part("Color", self.color); + + let updated_buffer = renderer.build_uniform_buffer(&self.uniform_buffers); + + self.pipeline.uniform = PipelineData::Data(Some(updated_buffer.0)); + self.uniform_layout = updated_buffer.1; + } + + /// Returns the buffer with ownership + pub fn update_uniform_buffer_and_return( + &mut self, + renderer: &mut Renderer, + ) -> 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.uniform_buffers[1] = renderer.build_uniform_buffer_part("Color", self.color); + + let updated_buffer = renderer.build_uniform_buffer(&self.uniform_buffers); + let updated_buffer2 = renderer.build_uniform_buffer(&self.uniform_buffers); + + self.pipeline.uniform = PipelineData::Data(Some(updated_buffer.0)); + self.uniform_layout = updated_buffer.1; + + updated_buffer2.0 + } + + /// Updates the instance buffer + pub fn update_instance_buffer(&mut self, renderer: &mut Renderer) { + let instance_data = self + .instances + .iter() + .map(Instance::to_raw) + .collect::>(); + let instance_buffer = renderer.build_instance(instance_data); + self.instance_buffer = instance_buffer; + } + + /// Returns the buffer with ownership + pub fn update_instance_buffer_and_return(&mut self, renderer: &mut Renderer) -> wgpu::Buffer { + let instance_data = self + .instances + .iter() + .map(Instance::to_raw) + .collect::>(); + let instance_buffer = renderer.build_instance(instance_data.clone()); + let instance_buffer2 = renderer.build_instance(instance_data); + + self.instance_buffer = instance_buffer; + instance_buffer2 + } + + // ============================= FOR COPY OF PIPELINES ============================= + /// References another object's vertices + pub fn reference_vertices(&mut self, object_id: impl StringBuffer) -> &mut Self { + self.pipeline.vertex_buffer = PipelineData::Copy(object_id.as_string()); + self + } + + /// References another object's shader + pub fn reference_shader(&mut self, object_id: impl StringBuffer) -> &mut Self { + self.pipeline.shader = PipelineData::Copy(object_id.as_string()); + self + } + + /// References another object's texture + pub fn reference_texture(&mut self, object_id: impl StringBuffer) -> &mut Self { + self.pipeline.texture = PipelineData::Copy(object_id.as_string()); + self + } + + /// References another object's uniform buffer + pub fn reference_uniform_buffer(&mut self, object_id: impl StringBuffer) -> &mut Self { + self.pipeline.uniform = PipelineData::Copy(object_id.as_string()); + self + } + + // ============================= Instances ============================= + /// Add an instance to the object + pub fn add_instance(&mut self, instance: Instance) -> &mut Self { + self.instances.push(instance); + self.changed = true; + self + } +} + +/// Configuration type for ShaderBuilder +pub type ShaderConfigs = Vec<(String, Box>) -> String>)>; + +/// Helps with building and updating shader code +pub struct ShaderBuilder { + /// the shader itself + pub shader: String, + /// Should the camera effect be applied + pub camera_effect: Option>, + /// configurations to be applied to the shader + pub configs: ShaderConfigs, +} + +impl ShaderBuilder { + /// Creates a new shader builder + pub fn new(shader_source: String, camera_effect: Option>) -> Self { + let mut shader_builder = Self { + shader: shader_source, + camera_effect, + configs: vec![ + ( + "//@CAMERA_STRUCT".to_string(), + Box::new(|camera_effect| { + if camera_effect.is_some() { + r#"struct CameraUniforms { + camera_matrix: mat4x4, + }; + @group(1) @binding(0) + var camera_uniform: CameraUniforms;"# + .to_string() + } else { + "".to_string() + } + }), + ), + ( + "//@CAMERA_VERTEX".to_string(), + Box::new(|camera_effect| { + if camera_effect.is_some() { + r#"out.position = camera_uniform.camera_matrix * model_matrix * (transform_uniform.transform_matrix * vec4(input.position, 1.0));"# + .to_string() + } else { + r#"out.position = model_matrix * (transform_uniform.transform_matrix * vec4(input.position, 1.0));"#.to_string() + } + }), + ), + ], + }; + shader_builder.build(); + + shader_builder + } + + /// Sets the new shader + pub fn set_shader(&mut self, new_shader: String) { + self.shader = new_shader; + self.build(); + } + + /// Builds the shader with the configuration defined + pub fn build(&mut self) { + for i in &self.configs { + self.shader = self.shader.replace(&i.0, &i.1(self.camera_effect.clone())); + } + } +} + +impl Instance { + /// Creates a new instance + pub fn new( + position: impl Into, + rotation: impl Into, + scale: impl Into, + ) -> Self { + Self { + position: position.into(), + rotation: rotation.into(), + scale: scale.into(), + } + } + + /// 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()); + InstanceRaw { + model: Matrix::from_im(position_matrix * rotation_matrix * scale_matrix), + } + } + + /// Sets the position + pub fn set_position(&mut self, position: impl Into) { + self.position = position.into(); + } + + /// Sets the rotation + pub fn set_rotation(&mut self, rotation: impl Into) { + self.rotation = rotation.into(); + } + + /// Sets the scale + pub fn set_scale(&mut self, scale: impl Into) { + self.scale = scale.into(); + } +} + +impl Default for Instance { + fn default() -> Self { + Self { + position: Vector3::default(), + rotation: Vector3::default(), + scale: Vector3::new(1.0, 1.0, 1.0), + } + } +} + +impl InstanceRaw { + /// Instance's layout description + pub fn desc() -> wgpu::VertexBufferLayout<'static> { + use std::mem; + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + // We need to switch from using a step mode of Vertex to Instance + // This means that our shaders will only change to use the next + // instance when the shader starts processing a new instance + step_mode: wgpu::VertexStepMode::Instance, + attributes: &[ + // A mat4 takes up 4 vertex slots as it is technically 4 vec4s. We need to define a slot + // for each vec4. We'll have to reassemble the mat4 in the shader. + wgpu::VertexAttribute { + offset: 0, + shader_location: 3, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, + shader_location: 4, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, + shader_location: 5, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress, + shader_location: 6, + format: wgpu::VertexFormat::Float32x4, + }, + ], + } + } +} diff --git a/crates/blue_engine_core/src/primitive_shapes/three_dimensions.rs b/crates/blue_engine_core/src/primitive_shapes/three_dimensions.rs index 4e4d26a..638b4be 100644 --- a/crates/blue_engine_core/src/primitive_shapes/three_dimensions.rs +++ b/crates/blue_engine_core/src/primitive_shapes/three_dimensions.rs @@ -1,4 +1,7 @@ -use crate::{ObjectSettings, ObjectStorage, Renderer, StringBuffer, UnsignedIntType, Vertex}; +use crate::{ + ObjectSettings, ObjectStorage, Renderer, StringBuffer, UnsignedIntType, Vector2, Vector3, + Vertex, +}; use std::f32::consts::PI; /// Creates a 3D cube @@ -8,129 +11,129 @@ pub fn cube(name: impl StringBuffer, renderer: &mut Renderer, objects: &mut Obje vec![ // Front Face Vertex { - position: [-1.0, -1.0, 1.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [1.0, -1.0, 1.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [1.0, 1.0, 1.0], - uv: [1.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [-1.0, 1.0, 1.0], - uv: [0.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, // Back Face Vertex { - position: [-1.0, 1.0, -1.0], - uv: [1.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [1.0, 1.0, -1.0], - uv: [0.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [1.0, -1.0, -1.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [-1.0, -1.0, -1.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, // Right face Vertex { - position: [1.0, -1.0, -1.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [1.0, 1.0, -1.0], - uv: [1.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [1.0, 1.0, 1.0], - uv: [0.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [1.0, -1.0, 1.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, - // Left Face + // Left face Vertex { - position: [-1.0, -1.0, 1.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [-1.0, 1.0, 1.0], - uv: [1.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [-1.0, 1.0, -1.0], - uv: [0.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [-1.0, -1.0, -1.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, - // Top Face + // Top face Vertex { - position: [1.0, 1.0, -1.0], - uv: [1.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [-1.0, 1.0, -1.0], - uv: [0.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [-1.0, 1.0, 1.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [1.0, 1.0, 1.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, - // Bottom Face + // Bottom face Vertex { - position: [1.0, -1.0, 1.0], - uv: [1.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [-1.0, -1.0, 1.0], - uv: [0.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [-1.0, -1.0, -1.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, Vertex { - position: [1.0, -1.0, -1.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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), }, ], vec![ @@ -178,9 +181,9 @@ pub fn uv_sphere( let y: f32 = xy * sector_angle.sin(); vertices.push(Vertex { - position: [x, y, z], - uv: [(j as f32) / sectors, (i as f32) / stacks], - normal: [x * length_inv, y * length_inv, z * length_inv], + 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), }); } } 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 f65af9c..7f0ee0c 100644 --- a/crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs +++ b/crates/blue_engine_core/src/primitive_shapes/two_dimensions.rs @@ -2,11 +2,15 @@ * 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::{ header::{ObjectSettings, Vertex}, - ObjectStorage, Renderer, StringBuffer, + ObjectStorage, Renderer, StringBuffer, Vector2, Vector3, }; /// Creates a 2D triangle @@ -20,19 +24,19 @@ pub fn triangle( name.clone(), vec![ Vertex { - position: [0.0, 1.0, 0.0], - uv: [0.5, 0.0], - normal: [0.0, 0.0, 0.0], + 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: [-1.0, -1.0, 0.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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: [1.0, -1.0, 0.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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], @@ -52,24 +56,24 @@ pub fn square( name.clone(), vec![ Vertex { - position: [1.0, 1.0, 0.0], - uv: [1.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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: [1.0, -1.0, 0.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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: [-1.0, -1.0, 0.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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: [-1.0, 1.0, 0.0], - uv: [0.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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], @@ -91,24 +95,24 @@ pub fn rectangle( name.clone(), vec![ Vertex { - position: [width / 2.0, height / 2.0, 0.0], - uv: [1.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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: [width / 2.0, -height / 2.0, 0.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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: [-width / 2.0, -height / 2.0, 0.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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: [-width / 2.0, height / 2.0, 0.0], - uv: [0.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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], diff --git a/crates/blue_engine_core/src/utils/camera.rs b/crates/blue_engine_core/src/utils/camera.rs index 055cb78..fa5a02e 100644 --- a/crates/blue_engine_core/src/utils/camera.rs +++ b/crates/blue_engine_core/src/utils/camera.rs @@ -1,289 +1,289 @@ -/* - * Blue Engine by Elham Aryanpur - * - * The license is same as the one on the root. -*/ - -use crate::{ - header::{uniform_type::Matrix, Camera, Renderer}, - CameraContainer, Projection, -}; -use winit::dpi::PhysicalSize; - -use super::default_resources::{DEFAULT_MATRIX_4, OPENGL_TO_WGPU_MATRIX}; - -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) - ]); - - let mut camera = Self { - position: nalgebra_glm::vec3(0.0, 0.0, 3.0), - target: nalgebra_glm::vec3(0.0, 0.0, -1.0), - up: nalgebra_glm::vec3(0.0, 1.0, 0.0), - resolution: (window_size.width as f32, window_size.height as f32), - projection: crate::Projection::Perspective { - fov: 70f32 * (std::f32::consts::PI / 180f32), - }, - near: 0.1, - far: 100.0, - view_data: DEFAULT_MATRIX_4.to_im(), - changed: true, - uniform_data: camera_uniform.0, - add_position_and_target: false, - }; - camera.build_view_projection_matrix(); - - camera - } - - /// Updates the view uniform matrix that decides how camera works - pub fn build_view_projection_matrix(&mut self) { - let view = self.build_view_matrix(); - let proj = self.build_projection_matrix(); - self.view_data = OPENGL_TO_WGPU_MATRIX * proj * view; - self.changed = true; - } - - /// 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( - 0f32, - self.resolution.0, - 0f32, - self.resolution.1, - self.near, - self.far, - ); - self.view_data = ortho * view; - self.changed = true; - } - - /// 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) - } - - /// Sets the position of camera - pub fn set_position(&mut self, x: f32, y: f32, z: f32) { - self.position = nalgebra_glm::vec3(x, y, z); - self.build_view_projection_matrix(); - } - - /// Sets the target of camera - pub fn set_target(&mut self, x: f32, y: f32, z: f32) { - self.target = nalgebra_glm::vec3(x, y, z); - self.build_view_projection_matrix(); - } - - /// Sets the up of camera - pub fn set_up(&mut self, x: f32, y: f32, z: f32) { - self.up = nalgebra_glm::vec3(x, y, z); - self.build_view_projection_matrix(); - } - - /// Sets how far camera can look - pub fn set_far(&mut self, new_far: f32) { - self.far = new_far; - self.build_view_projection_matrix(); - } - - /// Sets how near the camera can look - pub fn set_near(&mut self, new_near: f32) { - self.near = new_near; - self.build_view_projection_matrix(); - } - - /// Sets the aspect ratio of the camera - pub fn set_resolution(&mut self, window_size: PhysicalSize) { - self.resolution = (window_size.width as f32, window_size.height as f32); - self.build_view_projection_matrix(); - } - - /// Sets the projection of the camera - pub fn set_projection(&mut self, projection: Projection) { - self.projection = projection; - self.build_view_projection_matrix(); - } - - /// Enables adding position and target for the view target - pub fn add_position_and_target(&mut self, enable: bool) { - self.add_position_and_target = enable; - } - - /// This builds a uniform buffer data from camera view data that is sent to the GPU in next frame - pub fn update_view_projection(&mut self, renderer: &mut Renderer) { - if self.changed { - let updated_buffer = renderer - .build_uniform_buffer(&[renderer - .build_uniform_buffer_part("Camera Uniform", self.camera_uniform_buffer())]) - .0; - self.uniform_data = updated_buffer; - self.changed = false; - } - } - - /// This builds a uniform buffer data from camera view data that is sent to the GPU in next frame, and returns the bindgroup - pub fn update_view_projection_and_return( - &mut self, - renderer: &mut Renderer, - ) -> crate::UniformBuffers { - let updated_buffer = renderer - .build_uniform_buffer(&[ - renderer.build_uniform_buffer_part("Camera Uniform", self.camera_uniform_buffer()) - ]) - .0; - - updated_buffer - } - - /// Builds a view matrix for camera projection - pub fn build_view_matrix(&self) -> nalgebra_glm::Mat4 { - nalgebra_glm::look_at_rh( - &self.position, - &if self.add_position_and_target { - self.position + self.target - } else { - self.target - }, - &self.up, - ) - } - - /// Builds a projection matrix for camera - pub fn build_projection_matrix(&self) -> nalgebra_glm::Mat4 { - let aspect = self.resolution.0 / self.resolution.1; - - match self.projection { - crate::Projection::Perspective { fov } => { - nalgebra_glm::perspective(aspect, fov, self.near, self.far) - } - crate::Projection::Orthographic { zoom } => { - let width = zoom; - let height = width / aspect; - - let left = width * -0.5; - let right = width * 0.5; - let bottom = height * -0.5; - let top = height * 0.5; - - nalgebra_glm::ortho(left, right, bottom, top, self.near, self.far) - } - } - } -} - -impl CameraContainer { - /// Creates new CameraContainer with one main camera - pub fn new(window_size: PhysicalSize, renderer: &mut Renderer) -> Self { - let mut cameras = std::collections::HashMap::new(); - let main_camera = Camera::new(window_size, renderer); - cameras.insert("main".into(), main_camera); - - CameraContainer { cameras } - } - - /// Updates the view uniform matrix that decides how camera works - pub fn build_view_projection_matrix(&mut self) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.build_view_projection_matrix(); - } - } - /// Updates the view uniform matrix that decides how camera works - pub fn build_view_orthographic_matrix(&mut self) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.build_view_orthographic_matrix(); - } - } - /// Returns a matrix uniform buffer from camera data that can be sent to GPU - pub fn camera_uniform_buffer(&self) -> Option { - if let Some(main_camera) = self.cameras.get("main") { - Some(Matrix::from_im(main_camera.view_data)) - } else { - None - } - } - /// Sets the position of camera - pub fn set_position(&mut self, x: f32, y: f32, z: f32) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.set_position(x, y, z); - } - } - /// Sets the target of camera - pub fn set_target(&mut self, x: f32, y: f32, z: f32) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.set_target(x, y, z); - } - } - /// Sets the up of camera - pub fn set_up(&mut self, x: f32, y: f32, z: f32) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.set_up(x, y, z); - } - } - /// Sets how far camera can look - pub fn set_far(&mut self, new_far: f32) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.set_far(new_far); - } - } - /// Sets how near the camera can look - pub fn set_near(&mut self, new_near: f32) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.set_near(new_near); - } - } - /// Sets the aspect ratio of the camera - pub fn set_resolution(&mut self, window_size: PhysicalSize) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.set_resolution(window_size); - } - } - /// Sets the projection of the camera - pub fn set_projection(&mut self, projection: Projection) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.set_projection(projection); - } - } - /// Enables adding position and target for the view target - pub fn add_position_and_target(&mut self, enable: bool) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.add_position_and_target(enable); - } - } - /// This builds a uniform buffer data from camera view data that is sent to the GPU in next frame - pub fn update_view_projection(&mut self, renderer: &mut Renderer) { - if let Some(main_camera) = self.cameras.get_mut("main") { - main_camera.update_view_projection(renderer); - } - } - /// This builds a uniform buffer data from camera view data that is sent to the GPU in next frame, and returns the bindgroup - pub fn update_view_projection_and_return( - &mut self, - renderer: &mut Renderer, - ) -> Option { - match self.cameras.get_mut("main") { - Some(main_camera) => Some(main_camera.update_view_projection_and_return(renderer)), - None => None, - } - } - /// Builds a view matrix for camera projection - pub fn build_view_matrix(&self) -> Option { - if let Some(main_camera) = self.cameras.get("main") { - Some(main_camera.build_view_matrix()) - } else { - None - } - } - /// Builds a projection matrix for camera - pub fn build_projection_matrix(&self) -> Option { - if let Some(main_camera) = self.cameras.get("main") { - Some(main_camera.build_projection_matrix()) - } else { - None - } - } -} +/* + * Blue Engine by Elham Aryanpur + * + * The license is same as the one on the root. +*/ + +use crate::{ + header::{uniform_type::Matrix, Camera, Renderer, Vector3}, + CameraContainer, Projection, +}; +use winit::dpi::PhysicalSize; + +use super::default_resources::{DEFAULT_MATRIX_4, OPENGL_TO_WGPU_MATRIX}; + +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) + ]); + + let mut camera = Self { + position: Vector3::new(0.0, 0.0, 3.0), + target: Vector3::new(0.0, 0.0, -1.0), + up: Vector3::new(0.0, 1.0, 0.0), + resolution: (window_size.width as f32, window_size.height as f32), + projection: crate::Projection::Perspective { + fov: 70f32 * (std::f32::consts::PI / 180f32), + }, + near: 0.1, + far: 100.0, + view_data: DEFAULT_MATRIX_4.to_im(), + changed: true, + uniform_data: camera_uniform.0, + add_position_and_target: false, + }; + camera.build_view_projection_matrix(); + + camera + } + + /// Updates the view uniform matrix that decides how camera works + pub fn build_view_projection_matrix(&mut self) { + let view = self.build_view_matrix(); + let proj = self.build_projection_matrix(); + self.view_data = OPENGL_TO_WGPU_MATRIX * proj * view; + self.changed = true; + } + + /// 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( + 0f32, + self.resolution.0, + 0f32, + self.resolution.1, + self.near, + self.far, + ); + self.view_data = ortho * view; + self.changed = true; + } + + /// 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) + } + + /// Sets the position of camera + pub fn set_position(&mut self, new_pos: impl Into) { + self.position = new_pos.into(); + self.build_view_projection_matrix(); + } + + /// Sets the target of camera + pub fn set_target(&mut self, target_pos: impl Into) { + self.target = target_pos.into(); + self.build_view_projection_matrix(); + } + + /// Sets the up of camera + pub fn set_up(&mut self, pos: impl Into) { + self.up = pos.into(); + self.build_view_projection_matrix(); + } + + /// Sets how far camera can look + pub fn set_far(&mut self, new_far: f32) { + self.far = new_far; + self.build_view_projection_matrix(); + } + + /// Sets how near the camera can look + pub fn set_near(&mut self, new_near: f32) { + self.near = new_near; + self.build_view_projection_matrix(); + } + + /// Sets the aspect ratio of the camera + pub fn set_resolution(&mut self, window_size: PhysicalSize) { + self.resolution = (window_size.width as f32, window_size.height as f32); + self.build_view_projection_matrix(); + } + + /// Sets the projection of the camera + pub fn set_projection(&mut self, projection: Projection) { + self.projection = projection; + self.build_view_projection_matrix(); + } + + /// Enables adding position and target for the view target + pub fn add_position_and_target(&mut self, enable: bool) { + self.add_position_and_target = enable; + } + + /// This builds a uniform buffer data from camera view data that is sent to the GPU in next frame + pub fn update_view_projection(&mut self, renderer: &mut Renderer) { + if self.changed { + let updated_buffer = renderer + .build_uniform_buffer(&[renderer + .build_uniform_buffer_part("Camera Uniform", self.camera_uniform_buffer())]) + .0; + self.uniform_data = updated_buffer; + self.changed = false; + } + } + + /// This builds a uniform buffer data from camera view data that is sent to the GPU in next frame, and returns the bindgroup + pub fn update_view_projection_and_return( + &mut self, + renderer: &mut Renderer, + ) -> crate::UniformBuffers { + let updated_buffer = renderer + .build_uniform_buffer(&[ + renderer.build_uniform_buffer_part("Camera Uniform", self.camera_uniform_buffer()) + ]) + .0; + + updated_buffer + } + + /// 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 { + (self.position + self.target).into() + } else { + self.target.into() + }, + &self.up.into(), + ) + } + + /// Builds a projection matrix for camera + pub fn build_projection_matrix(&self) -> nalgebra_glm::Mat4 { + let aspect = self.resolution.0 / self.resolution.1; + + match self.projection { + crate::Projection::Perspective { fov } => { + nalgebra_glm::perspective(aspect, fov, self.near, self.far) + } + crate::Projection::Orthographic { zoom } => { + let width = zoom; + let height = width / aspect; + + let left = width * -0.5; + let right = width * 0.5; + let bottom = height * -0.5; + let top = height * 0.5; + + nalgebra_glm::ortho(left, right, bottom, top, self.near, self.far) + } + } + } +} + +impl CameraContainer { + /// Creates new CameraContainer with one main camera + pub fn new(window_size: PhysicalSize, renderer: &mut Renderer) -> Self { + let mut cameras = std::collections::HashMap::new(); + let main_camera = Camera::new(window_size, renderer); + cameras.insert("main".into(), main_camera); + + CameraContainer { cameras } + } + + /// Updates the view uniform matrix that decides how camera works + pub fn build_view_projection_matrix(&mut self) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.build_view_projection_matrix(); + } + } + /// Updates the view uniform matrix that decides how camera works + pub fn build_view_orthographic_matrix(&mut self) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.build_view_orthographic_matrix(); + } + } + /// Returns a matrix uniform buffer from camera data that can be sent to GPU + pub fn camera_uniform_buffer(&self) -> Option { + if let Some(main_camera) = self.cameras.get("main") { + Some(Matrix::from_im(main_camera.view_data)) + } else { + None + } + } + /// Sets the position of camera + pub fn set_position(&mut self, new_pos: impl Into) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.set_position(new_pos.into()); + } + } + /// Sets the target of camera + pub fn set_target(&mut self, pos: impl Into) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.set_target(pos.into()); + } + } + /// Sets the up of camera + pub fn set_up(&mut self, pos: impl Into) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.set_up(pos.into()); + } + } + /// Sets how far camera can look + pub fn set_far(&mut self, new_far: f32) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.set_far(new_far); + } + } + /// Sets how near the camera can look + pub fn set_near(&mut self, new_near: f32) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.set_near(new_near); + } + } + /// Sets the aspect ratio of the camera + pub fn set_resolution(&mut self, window_size: PhysicalSize) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.set_resolution(window_size); + } + } + /// Sets the projection of the camera + pub fn set_projection(&mut self, projection: Projection) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.set_projection(projection); + } + } + /// Enables adding position and target for the view target + pub fn add_position_and_target(&mut self, enable: bool) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.add_position_and_target(enable); + } + } + /// This builds a uniform buffer data from camera view data that is sent to the GPU in next frame + pub fn update_view_projection(&mut self, renderer: &mut Renderer) { + if let Some(main_camera) = self.cameras.get_mut("main") { + main_camera.update_view_projection(renderer); + } + } + /// This builds a uniform buffer data from camera view data that is sent to the GPU in next frame, and returns the bindgroup + pub fn update_view_projection_and_return( + &mut self, + renderer: &mut Renderer, + ) -> Option { + match self.cameras.get_mut("main") { + Some(main_camera) => Some(main_camera.update_view_projection_and_return(renderer)), + None => None, + } + } + /// Builds a view matrix for camera projection + pub fn build_view_matrix(&self) -> Option { + if let Some(main_camera) = self.cameras.get("main") { + Some(main_camera.build_view_matrix()) + } else { + None + } + } + /// Builds a projection matrix for camera + pub fn build_projection_matrix(&self) -> Option { + if let Some(main_camera) = self.cameras.get("main") { + Some(main_camera.build_projection_matrix()) + } else { + None + } + } +} diff --git a/crates/blue_engine_core/src/vector.rs b/crates/blue_engine_core/src/vector.rs new file mode 100644 index 0000000..d909842 --- /dev/null +++ b/crates/blue_engine_core/src/vector.rs @@ -0,0 +1,983 @@ +use std::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; + +use crate::{ + header::{Vector2, Vector3}, + RotateAmount, RotateAxis, +}; +use bytemuck::Pod; + +// 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); + + /// 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) + } + /// 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); + + /// 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) + } + /// 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/examples/camera/rotate_around.rs b/examples/camera/rotate_around.rs index 390af12..1e7cc83 100644 --- a/examples/camera/rotate_around.rs +++ b/examples/camera/rotate_around.rs @@ -7,6 +7,7 @@ use blue_engine::{ header::{Engine, ObjectSettings, ShaderSettings}, primitive_shapes::square, + Vector3, }; fn main() { @@ -38,7 +39,7 @@ fn main() { .update_loop(move |_, _, _, _, camera, _| { let camx = start.elapsed().unwrap().as_secs_f32().sin() * radius; let camz = start.elapsed().unwrap().as_secs_f32().cos() * radius; - camera.set_position(camx, 0.0, camz); + camera.set_position(Vector3::new(camx, 0.0, camz)); }) .expect("Error during update loop"); } diff --git a/examples/dev/dev.rs b/examples/dev/dev.rs index c51eb5b..3d9982d 100644 --- a/examples/dev/dev.rs +++ b/examples/dev/dev.rs @@ -5,7 +5,7 @@ use blue_engine::{ uniform_type::Matrix, utils::default_resources::DEFAULT_MATRIX_4, Engine, Instance, ObjectSettings, PolygonMode, PowerPreference, RotateAxis, ShaderSettings, - TextureData, Vertex, WindowDescriptor, + TextureData, Vector3, Vertex, WindowDescriptor, }; fn main() { @@ -57,7 +57,7 @@ fn main() { .objects .get_mut("main") .unwrap() - .set_position(-1f32, 0f32, 0f32); + .set_position(Vector3::new(-1f32, 0f32, 0f32)); square( "alt", @@ -70,7 +70,7 @@ fn main() { .objects .get_mut("alt") .unwrap() - .set_position(0.2f32, 0f32, 0.001f32); + .set_position(Vector3::new(0.2f32, 0f32, 0.001f32)); square( "alt2", @@ -87,7 +87,7 @@ fn main() { .objects .get_mut("alt2") .unwrap() - .set_position(-0.2f32, 0f32, 0.001f32); + .set_position(Vector3::new(-0.2f32, 0f32, 0.001f32)); engine.window.set_fullscreen_borderless(true); @@ -112,50 +112,50 @@ fn main() { _window.close_engine(); } if input.key_held(blue_engine::KeyCode::ArrowUp) { - sprite.set_position( + 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.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.set_position(( sprite.position.x + speed, sprite.position.y, sprite.position.z, - ); + )); } if input.key_held(blue_engine::KeyCode::ArrowRight) { - sprite.set_position( + 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.set_position(( sprite.position.x, sprite.position.y, sprite.position.z + speed, - ); + )); } if input.key_held(blue_engine::KeyCode::KeyQ) { - sprite.set_position( + sprite.set_position(( sprite.position.x, sprite.position.y, sprite.position.z - speed, - ); + )); } }) .expect("Error during update loop"); diff --git a/examples/shapes/cube.rs b/examples/shapes/cube.rs index 548d064..3f2029e 100644 --- a/examples/shapes/cube.rs +++ b/examples/shapes/cube.rs @@ -23,7 +23,7 @@ fn main() { let camx = start.elapsed().unwrap().as_secs_f32().sin() * radius; let camy = start.elapsed().unwrap().as_secs_f32().sin() * radius; let camz = start.elapsed().unwrap().as_secs_f32().cos() * radius; - camera.set_position(camx, camy, camz); + camera.set_position((camx, camy, camz)); }) .expect("Error during update loop"); } diff --git a/examples/shapes/square.rs b/examples/shapes/square.rs index b5fbe6f..899d0a7 100644 --- a/examples/shapes/square.rs +++ b/examples/shapes/square.rs @@ -8,7 +8,7 @@ use blue_engine::{ header::{Engine, ObjectSettings, Vertex}, - StringBuffer, + StringBuffer, Vector2, Vector3, }; pub fn square(name: impl StringBuffer, engine: &mut Engine) { @@ -16,24 +16,24 @@ pub fn square(name: impl StringBuffer, engine: &mut Engine) { name, vec![ Vertex { - position: [1.0, 1.0, 0.0], - uv: [1.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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: [1.0, -1.0, 0.0], - uv: [1.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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: [-1.0, -1.0, 0.0], - uv: [0.0, 1.0], - normal: [0.0, 0.0, 0.0], + 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: [-1.0, 1.0, 0.0], - uv: [0.0, 0.0], - normal: [0.0, 0.0, 0.0], + 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], diff --git a/examples/utils/instancing.rs b/examples/utils/instancing.rs index 0f08598..4eead37 100644 --- a/examples/utils/instancing.rs +++ b/examples/utils/instancing.rs @@ -9,7 +9,7 @@ use blue_engine::{ header::{Engine, ObjectSettings}, primitive_shapes::triangle, - Instance, + Instance, Vector3, }; pub fn main() { @@ -27,15 +27,11 @@ pub fn main() { // update the triangle engine.objects.update_object("Triangle", |object| { // set the position of the main triangle - object.set_position(0f32, 0f32, -3f32); + object.set_position(Vector3::new(0f32, 0f32, -3f32)); // a function to make instance creation easier let create_instance = |x: f32, y: f32, z: f32| { - Instance::new( - [x, y, z].into(), - [0f32, 0f32, 0f32].into(), - [1f32, 1f32, 1f32].into(), - ) + Instance::new([x, y, z], [0f32, 0f32, 0f32], [1f32, 1f32, 1f32]) }; // add an instance diff --git a/examples/utils/render_order.rs b/examples/utils/render_order.rs index dd59448..f6a07ab 100644 --- a/examples/utils/render_order.rs +++ b/examples/utils/render_order.rs @@ -5,7 +5,7 @@ */ // imports needed -use blue_engine::{primitive_shapes::square, Engine, ObjectSettings}; +use blue_engine::{primitive_shapes::square, Engine, ObjectSettings, Vector3}; fn main() { // initialize the engine @@ -33,7 +33,7 @@ fn main() { .get_mut("layer1") .expect("failed to gete object") .set_color(1f32, 0.5, 0f32, 1f32) // set a color to differentiate it - .set_position(-0.5, 0f32, 0f32) // move it to left a bit + .set_position(Vector3::new(-0.5, 0f32, 0f32)) // move it to left a bit .set_render_order(0); // set render order to 0th // Get layer 2 object @@ -42,7 +42,7 @@ fn main() { .get_mut("layer2") .expect("failed to gete object") .set_color(0f32, 0f32, 1f32, 1f32) // set a color to differentiate it - .set_position(0.5, 0f32, 0f32) // move it to right a bit + .set_position(Vector3::new(0.5, 0f32, 0f32)) // move it to right a bit .set_render_order(1); // set render order to 1st // get a timer for order change diff --git a/examples/utils/resource_sharing.rs b/examples/utils/resource_sharing.rs index 606fecd..bbf2dc0 100644 --- a/examples/utils/resource_sharing.rs +++ b/examples/utils/resource_sharing.rs @@ -5,7 +5,7 @@ * * The license is same as the one on the root. */ -use blue_engine::{primitive_shapes::square, Engine, ObjectSettings, TextureData}; +use blue_engine::{primitive_shapes::square, Engine, ObjectSettings, TextureData, Vector3}; fn main() { // Start the engine @@ -35,7 +35,7 @@ fn main() { ) .unwrap(), ) - .set_position(-1.5f32, 0f32, 0f32); // set position to make it visible + .set_position(Vector3::new(-1.5f32, 0f32, 0f32)); // set position to make it visible // create another object where you want to get resources shared with square( @@ -51,7 +51,7 @@ fn main() { .get_mut("alt") .expect("Error during copying texture of the main square") // setting position again to make it visible - .set_position(1.5f32, 0f32, 0f32) + .set_position(Vector3::new(1.5f32, 0f32, 0f32)) .reference_texture("main"); engine