#![deny(clippy::all, clippy::pedantic)] use std::collections::BTreeMap; use std::time::{Duration, Instant}; use glfw::window::KeyState; use glfw::{Window, WindowBuilder}; use crate::camera::Camera; use crate::lighting::LightSource; use crate::object::{Id as ObjectId, Object}; use crate::renderer::Renderer; use crate::vector::Vec2; mod opengl; mod projection; mod renderer; mod shader_preprocessor; mod transform; pub mod camera; pub mod data_types; pub mod lighting; pub mod material; pub mod math; pub mod mesh; pub mod object; pub mod texture; pub mod vertex; pub use glfw::window::Key; pub use glfw::WindowSize; pub(crate) use crate::data_types::matrix; pub use crate::data_types::{color, vector}; #[derive(Debug)] pub struct Engine { /// Objects have to be dropped before window. Otherwise, UB. objects: BTreeMap, light_source: Option, window: Window, renderer: Renderer, delta_time: Duration, } impl Engine where CameraT: Camera, { /// Creates and initializes a new engine. /// /// # Errors /// Will return `Err` if window creation or window configuration fails. pub fn new(window_settings: &WindowSettings, camera: CameraT) -> Result { let window_builder = WindowBuilder::new(); #[cfg(feature = "debug")] let window_builder = window_builder.hint(glfw::window::Hint::OpenGLDebugContext, 1); let window = window_builder .create(&window_settings.size, &window_settings.title) .map_err(Error::CreateWindowFailed)?; window .make_context_current() .map_err(Error::ConfigureWindowFailed)?; window.set_framebuffer_size_callback(move |new_window_size| { Renderer::::set_viewport(&Vec2::ZERO, &new_window_size); }); Self::set_window_input_modes(&window, window_settings)?; window .set_cursor_mode(window_settings.cursor_mode) .map_err(|err| Error::SetWindowCursorModeFailed { source: err, cursor_mode: window_settings.cursor_mode, })?; let renderer = Renderer::new(&window, camera).map_err(Error::InitializeRendererFailed)?; Ok(Self { window, renderer, objects: BTreeMap::new(), light_source: None, delta_time: Duration::ZERO, }) } pub fn add_object(&mut self, object: Object) { self.objects.insert(object.id(), object); } pub fn set_objecs(&mut self, objects: impl IntoIterator) { self.objects .extend(objects.into_iter().map(|object| (object.id(), object))); } pub fn set_light_source(&mut self, light_source: LightSource) { self.light_source = Some(light_source); } #[must_use] pub fn light_source(&self) -> Option<&LightSource> { self.light_source.as_ref() } #[must_use] pub fn light_source_mut(&mut self) -> Option<&mut LightSource> { self.light_source.as_mut() } #[must_use] pub fn get_object_by_id(&self, id: ObjectId) -> Option<&Object> { self.objects.get(&id) } #[must_use] pub fn get_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> { self.objects.get_mut(&id) } /// Starts the engine. /// /// # Errors /// Will return `Err` if updating the window fails. pub fn start(&mut self, mut func: impl FnMut(&mut Self)) -> Result<(), Error> { let mut prev_frame_start: Option = None; while !self.window.should_close() { self.update_delta_time(&mut prev_frame_start); func(self); let window_size = self.window.size().map_err(Error::GetWindowSizeFailed)?; self.renderer .render( self.objects.values(), self.light_source.as_ref(), &window_size, ) .map_err(Error::RenderingFailed)?; self.window .swap_buffers() .map_err(Error::UpdateWindowFailed)?; self.window .poll_events() .map_err(Error::UpdateWindowFailed)?; } Ok(()) } #[must_use] pub fn camera(&self) -> &CameraT { self.renderer.camera() } pub fn camera_mut(&mut self) -> &mut CameraT { self.renderer.camera_mut() } /// Returns the current delta time. Will be 0 if not called from the function /// passed to [`start`]. #[must_use] pub fn delta_time(&self) -> &Duration { &self.delta_time } /// Returns whether or not a keyboard key is currently pressed. /// /// # Errors /// Will return `Err` if getting the key state fails. pub fn is_key_pressed(&self, key: Key) -> Result { self.window .get_key(key) .map(|key_state| matches!(key_state, KeyState::Pressed)) .map_err(Error::GetKeyStateFailed) } /// Returns the cursor position. /// /// # Errors /// Will return `Err` if unable to get the cursor position from GLFW. pub fn get_cursor_pos(&self) -> Result, Error> { let pos = self .window .get_cursor_position() .map_err(Error::GetCursorPosFailed)?; Ok(Vec2 { x: pos.x, y: pos.y }) } fn update_delta_time(&mut self, prev_frame_start: &mut Option) { let frame_start_time = Instant::now(); if let Some(last_frame_start) = prev_frame_start { self.delta_time = frame_start_time.duration_since(*last_frame_start); } *prev_frame_start = Some(frame_start_time); } fn set_window_input_modes( window: &Window, window_settings: &WindowSettings, ) -> Result<(), Error> { for (input_mode, enabled) in &window_settings.input_modes { window .set_input_mode(*input_mode, *enabled) .map_err(|err| Error::SetWindowInputModeFailed { source: err, input_mode: *input_mode, enabled: *enabled, })?; } Ok(()) } } /// Engine Error #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Failed to create window")] CreateWindowFailed(#[source] glfw::Error), #[error("Failed to configure window")] ConfigureWindowFailed(#[source] glfw::Error), #[error("Failed to initialize renderer")] InitializeRendererFailed(#[source] renderer::Error), #[error("Failed to update window")] UpdateWindowFailed(#[source] glfw::Error), #[error("Failed to get window size")] GetWindowSizeFailed(#[source] glfw::Error), #[error("Failed to set window input mode {input_mode:?} to {enabled}")] SetWindowInputModeFailed { #[source] source: glfw::Error, input_mode: InputMode, enabled: bool, }, #[error("Failed to set window cursor mode to {cursor_mode:?}")] SetWindowCursorModeFailed { #[source] source: glfw::Error, cursor_mode: CursorMode, }, #[error("Failed to get key state")] GetKeyStateFailed(#[source] glfw::Error), #[error("Failed to get cursor position")] GetCursorPosFailed(#[source] glfw::Error), #[error("Rendering failed")] RenderingFailed(#[source] renderer::Error), } #[derive(Debug)] pub struct WindowSettings { size: WindowSize, title: Box, input_modes: BTreeMap, cursor_mode: CursorMode, } #[derive(Debug)] pub struct WindowSettingsBuilder { input_modes: BTreeMap, cursor_mode: CursorMode, } impl WindowSettingsBuilder { #[must_use] pub fn new() -> Self { Self::default() } #[must_use] pub fn input_mode(mut self, input_mode: InputMode, enabled: bool) -> Self { self.input_modes.insert(input_mode, enabled); self } #[must_use] pub fn input_modes( mut self, input_modes: impl IntoIterator, ) -> Self { self.input_modes.extend(input_modes); self } #[must_use] pub fn cursor_mode(mut self, cursor_mode: CursorMode) -> Self { self.cursor_mode = cursor_mode; self } pub fn build(&self, size: WindowSize, title: impl Into>) -> WindowSettings { WindowSettings { size, title: title.into(), input_modes: self.input_modes.clone(), cursor_mode: self.cursor_mode, } } } impl Default for WindowSettingsBuilder { fn default() -> Self { Self { input_modes: BTreeMap::new(), cursor_mode: CursorMode::Normal, } } } pub use glfw::window::{CursorMode, InputMode};