diff options
Diffstat (limited to 'engine/src')
31 files changed, 2194 insertions, 1514 deletions
diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs index b6ba7aa..087f727 100644 --- a/engine/src/camera/fly.rs +++ b/engine/src/camera/fly.rs @@ -1,13 +1,12 @@ use ecs::component::local::Local; +use ecs::phase::UPDATE as UPDATE_PHASE; use ecs::sole::Single; use ecs::system::{Into, System}; use ecs::{Component, Query}; -use glfw::window::{Key, KeyState}; use crate::camera::{Active as ActiveCamera, Camera}; use crate::delta_time::DeltaTime; -use crate::event::Update as UpdateEvent; -use crate::input::{Cursor, CursorFlags, Keys}; +use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys}; use crate::transform::Position; use crate::util::builder; use crate::vector::{Vec2, Vec3}; @@ -61,7 +60,7 @@ impl ecs::extension::Extension for Extension fn collect(self, mut collector: ecs::extension::Collector<'_>) { collector.add_system( - UpdateEvent, + *UPDATE_PHASE, update .into_system() .initialize((CursorState::default(), self.0)), @@ -76,7 +75,7 @@ pub struct Options } fn update( - camera_query: Query<(Camera, Position, Fly, ActiveCamera)>, + camera_query: Query<(&mut Camera, &mut Position, &mut Fly, &ActiveCamera)>, keys: Single<Keys>, cursor: Single<Cursor>, cursor_flags: Single<CursorFlags>, @@ -87,7 +86,6 @@ fn update( { for (mut camera, mut camera_pos, mut fly_camera, _) in &camera_query { if cursor.has_moved && cursor_flags.is_first_move.flag { - #[cfg(feature = "debug")] tracing::debug!("First cursor move"); cursor_state.last_pos = cursor.position; @@ -123,23 +121,23 @@ fn update( camera.global_up = cam_right.cross(&direction).normalize(); - if matches!(keys.get_key_state(Key::W), KeyState::Pressed) { + if keys.get_key_state(Key::W) == KeyState::Pressed { camera_pos.position += direction * fly_camera.speed * delta_time.as_secs_f32(); } - if matches!(keys.get_key_state(Key::S), KeyState::Pressed) { + if keys.get_key_state(Key::S) == KeyState::Pressed { camera_pos.position -= direction * fly_camera.speed * delta_time.as_secs_f32(); } - if matches!(keys.get_key_state(Key::A), KeyState::Pressed) { + if keys.get_key_state(Key::A) == KeyState::Pressed { let cam_left = -direction.cross(&Vec3::UP).normalize(); camera_pos.position += cam_left * fly_camera.speed * delta_time.as_secs_f32(); } - if matches!(keys.get_key_state(Key::D), KeyState::Pressed) { + if keys.get_key_state(Key::D) == KeyState::Pressed { let cam_right = direction.cross(&Vec3::UP).normalize(); camera_pos.position += diff --git a/engine/src/collision.rs b/engine/src/collision.rs new file mode 100644 index 0000000..aefd9b6 --- /dev/null +++ b/engine/src/collision.rs @@ -0,0 +1,142 @@ +use ecs::Component; + +use crate::mesh::Mesh; +use crate::vector::Vec3; + +pub trait Collider<Other> +{ + fn intersects(&self, other: &Other) -> bool; +} + +#[derive(Debug, Default, Clone, Component)] +#[non_exhaustive] +pub struct BoxCollider +{ + pub min: Vec3<f32>, + pub max: Vec3<f32>, +} + +impl BoxCollider +{ + pub fn for_mesh(mesh: &Mesh) -> Self + { + let furthest_dir_points = mesh.find_furthest_vertex_positions(); + + Self { + min: Vec3 { + x: furthest_dir_points.left.x, + y: furthest_dir_points.down.y, + z: furthest_dir_points.back.z, + }, + max: Vec3 { + x: furthest_dir_points.right.x, + y: furthest_dir_points.up.y, + z: furthest_dir_points.front.z, + }, + } + } + + pub fn offset(self, offset: Vec3<f32>) -> Self + { + Self { + min: self.min + offset, + max: self.max + offset, + } + } +} + +impl Collider<BoxCollider> for BoxCollider +{ + fn intersects(&self, other: &BoxCollider) -> bool + { + self.min.x <= other.max.x + && self.max.x >= other.min.x + && self.min.y <= other.max.y + && self.max.y >= other.min.y + && self.min.z <= other.max.z + && self.max.z >= other.min.z + } +} + +impl Collider<SphereCollider> for BoxCollider +{ + fn intersects(&self, other: &SphereCollider) -> bool + { + other.intersects(self) + } +} + +impl Collider<Vec3<f32>> for BoxCollider +{ + fn intersects(&self, other: &Vec3<f32>) -> bool + { + other.x >= self.min.x + && other.y >= self.min.y + && other.z >= self.min.z + && other.x <= self.max.x + && other.y <= self.max.y + && other.z <= self.max.z + } +} + +#[derive(Debug, Default, Clone, Component)] +pub struct SphereCollider +{ + pub center: Vec3<f32>, + pub radius: f32, +} + +impl SphereCollider +{ + pub fn offset(self, offset: Vec3<f32>) -> Self + { + Self { + center: self.center + offset, + radius: self.radius, + } + } +} + +impl Collider<SphereCollider> for SphereCollider +{ + fn intersects(&self, other: &SphereCollider) -> bool + { + (&self.center - &other.center).length() <= self.radius + other.radius + } +} + +impl Collider<BoxCollider> for SphereCollider +{ + fn intersects(&self, other: &BoxCollider) -> bool + { + let mut min_distance = 0.0; + + if self.center.x < other.min.x { + min_distance += (self.center.x - other.min.x).powf(2.0); + } else if self.center.x > other.max.x { + min_distance += (self.center.x - other.max.x).powf(2.0); + } + + if self.center.y < other.min.y { + min_distance += (self.center.y - other.min.y).powf(2.0); + } else if self.center.y > other.max.y { + min_distance += (self.center.y - other.max.y).powf(2.0); + } + + if self.center.z < other.min.z { + min_distance += (self.center.z - other.min.z).powf(2.0); + } else if self.center.z > other.max.z { + min_distance += (self.center.z - other.max.z).powf(2.0); + } + + min_distance <= self.radius.powf(2.0) + } +} + +impl Collider<Vec3<f32>> for SphereCollider +{ + fn intersects(&self, other: &Vec3<f32>) -> bool + { + (&self.center - other).length() <= self.radius + } +} diff --git a/engine/src/data_types/dimens.rs b/engine/src/data_types/dimens.rs index b395627..5002436 100644 --- a/engine/src/data_types/dimens.rs +++ b/engine/src/data_types/dimens.rs @@ -1,7 +1,16 @@ -/// Dimensions. +/// 2D dimensions. #[derive(Debug, Clone, Copy)] pub struct Dimens<Value> { pub width: Value, pub height: Value, } + +/// 3D dimensions. +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub struct Dimens3<Value> +{ + pub width: Value, + pub height: Value, + pub depth: Value, +} diff --git a/engine/src/data_types/vector.rs b/engine/src/data_types/vector.rs index 17953f4..802a4a7 100644 --- a/engine/src/data_types/vector.rs +++ b/engine/src/data_types/vector.rs @@ -2,7 +2,7 @@ use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use crate::color::Color; -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct Vec2<Value> { pub x: Value, @@ -74,7 +74,7 @@ where } } -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, PartialEq)] #[repr(C)] pub struct Vec3<Value> { @@ -85,6 +85,11 @@ pub struct Vec3<Value> impl Vec3<f32> { + pub const BACK: Self = Self { x: 0.0, y: 0.0, z: -1.0 }; + pub const DOWN: Self = Self { x: 0.0, y: -1.0, z: 0.0 }; + pub const FRONT: Self = Self { x: 0.0, y: 0.0, z: 1.0 }; + pub const LEFT: Self = Self { x: -1.0, y: 0.0, z: 0.0 }; + pub const RIGHT: Self = Self { x: 1.0, y: 0.0, z: 0.0 }; pub const UP: Self = Self { x: 0.0, y: 1.0, z: 0.0 }; /// Returns the length of the vector. @@ -209,6 +214,22 @@ where } } +impl<Value> Mul for Vec3<Value> +where + Value: Mul<Value, Output = Value>, +{ + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output + { + Self::Output { + x: self.x * rhs.x, + y: self.y * rhs.y, + z: self.z * rhs.z, + } + } +} + impl<Value> Neg for Vec3<Value> where Value: Neg<Output = Value>, diff --git a/engine/src/event.rs b/engine/src/event.rs deleted file mode 100644 index e5ae486..0000000 --- a/engine/src/event.rs +++ /dev/null @@ -1,27 +0,0 @@ -pub use ecs::event::start::Start; -use ecs::event::Event; - -#[derive(Debug)] -pub struct Update; - -impl Event for Update {} - -#[derive(Debug)] -pub struct PreUpdate; - -impl Event for PreUpdate {} - -#[derive(Debug)] -pub struct Present; - -impl Event for Present {} - -#[derive(Debug)] -pub struct PostPresent; - -impl Event for PostPresent {} - -#[derive(Debug)] -pub struct Conclude; - -impl Event for Conclude {} diff --git a/engine/src/file_format/wavefront/mtl.rs b/engine/src/file_format/wavefront/mtl.rs index ef6e894..d90dbcf 100644 --- a/engine/src/file_format/wavefront/mtl.rs +++ b/engine/src/file_format/wavefront/mtl.rs @@ -44,7 +44,6 @@ pub fn parse(obj_content: &str) -> Result<Vec<NamedMaterial>, Error> .filter(|(_, statement)| matches!(statement.keyword, Keyword::Newmtl)) .count(); - #[cfg(feature = "debug")] tracing::debug!("Material count: {material_cnt}"); statements_to_materials(statements, material_cnt) @@ -93,7 +92,7 @@ pub enum Error }, } -#[cfg_attr(feature = "debug", tracing::instrument(skip_all))] +#[tracing::instrument(skip_all)] fn statements_to_materials( statements: impl IntoIterator<Item = (usize, Statement<Keyword>)>, material_cnt: usize, @@ -110,7 +109,6 @@ fn statements_to_materials( for (line_no, statement) in statements { if statement.keyword == Keyword::Newmtl { if curr_material.ready { - #[cfg(feature = "debug")] tracing::debug!("Building material"); let material = curr_material.material_builder.clone().build(); @@ -135,7 +133,6 @@ fn statements_to_materials( Keyword::Ka => { let color = get_color_from_statement(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding ambient color"); curr_material.material_builder = @@ -144,7 +141,6 @@ fn statements_to_materials( Keyword::Kd => { let color = get_color_from_statement(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding diffuse color"); curr_material.material_builder = @@ -153,7 +149,6 @@ fn statements_to_materials( Keyword::Ks => { let color = get_color_from_statement(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding specular color"); curr_material.material_builder = @@ -172,7 +167,6 @@ fn statements_to_materials( let texture = Texture::open(Path::new(texture_file_path))?; - #[cfg(feature = "debug")] tracing::debug!("Adding ambient map"); let texture_id = texture.id(); @@ -185,7 +179,6 @@ fn statements_to_materials( Keyword::MapKd => { let texture = get_map_from_texture(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding diffuse map"); let texture_id = texture.id(); @@ -198,7 +191,6 @@ fn statements_to_materials( Keyword::MapKs => { let texture = get_map_from_texture(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding specular map"); let texture_id = texture.id(); @@ -213,7 +205,6 @@ fn statements_to_materials( } if curr_material.ready { - #[cfg(feature = "debug")] tracing::debug!("Building last material"); let material = curr_material.material_builder.build(); diff --git a/engine/src/file_format/wavefront/obj.rs b/engine/src/file_format/wavefront/obj.rs index 88e6580..6ca11c2 100644 --- a/engine/src/file_format/wavefront/obj.rs +++ b/engine/src/file_format/wavefront/obj.rs @@ -2,6 +2,7 @@ //! //! File format documentation: <https://paulbourke.net/dataformats/obj> +use std::collections::HashMap; use std::fs::read_to_string; use std::path::PathBuf; @@ -82,26 +83,32 @@ impl Obj /// - A face index does not fit in a [`u32`] pub fn to_mesh(&self) -> Result<Mesh, Error> { - let vertices = self - .faces - .iter() - .flat_map(|face| face.vertices.clone()) - .map(|face_vertex| face_vertex.to_vertex(self)) - .collect::<Result<Vec<_>, Error>>()?; - - Ok(Mesh::new( - vertices, - Some( - self.faces - .iter() - .flat_map(|face| face.vertices.clone()) - .enumerate() - .map(|(index, _)| { - u32::try_from(index).map_err(|_| Error::FaceIndexTooBig(index)) - }) - .collect::<Result<Vec<_>, _>>()?, - ), - )) + let mut vertices = Vec::<Vertex>::with_capacity(self.faces.len() * 3); + let mut indices = Vec::<u32>::with_capacity(self.faces.len() * 3); + + let mut added_face_vertices = + HashMap::<FaceVertex, u32>::with_capacity(self.faces.len() * 3); + + for face in &self.faces { + for face_vertex in &face.vertices { + if let Some(index) = added_face_vertices.get(&face_vertex) { + indices.push(*index); + + continue; + } + + vertices.push(face_vertex.to_vertex(self)?); + + let vertex_index = u32::try_from(vertices.len() - 1) + .map_err(|_| Error::FaceIndexTooBig(vertices.len() - 1))?; + + indices.push(vertex_index); + + added_face_vertices.insert(face_vertex.clone(), vertex_index); + } + } + + Ok(Mesh::new(vertices, Some(indices))) } /// Reads and parses the material libraries of this `Obj`. @@ -142,7 +149,7 @@ pub struct Face pub material_name: Option<String>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FaceVertex { pub position: u32, diff --git a/engine/src/input.rs b/engine/src/input.rs index f4166f6..95de048 100644 --- a/engine/src/input.rs +++ b/engine/src/input.rs @@ -1,16 +1,13 @@ use std::collections::HashMap; use ecs::extension::Collector as ExtensionCollector; +use ecs::phase::{Phase, PRE_UPDATE as PRE_UPDATE_PHASE, START as START_PHASE}; +use ecs::relationship::{ChildOf, Relationship}; use ecs::sole::Single; -use ecs::Sole; +use ecs::{static_entity, Sole}; -use crate::event::{ - PostPresent as PostPresentEvent, - PreUpdate as PreUpdateEvent, - Start as StartEvent, -}; use crate::vector::Vec2; -use crate::window::Window; +use crate::window::{Window, UPDATE_PHASE as WINDOW_UPDATE_PHASE}; mod reexports { @@ -19,10 +16,19 @@ mod reexports pub use reexports::*; +static_entity!( + SET_PREV_KEY_STATE_PHASE, + ( + Phase, + <Relationship<ChildOf, Phase>>::new(*WINDOW_UPDATE_PHASE) + ) +); + #[derive(Debug, Sole)] pub struct Keys { map: HashMap<Key, KeyData>, + pending: Vec<(Key, KeyState)>, } impl Keys @@ -43,6 +49,7 @@ impl Keys ) }) .collect(), + pending: Vec::with_capacity(Key::KEYS.len()), } } @@ -68,10 +75,6 @@ impl Keys pub fn set_key_state(&mut self, key: Key, new_key_state: KeyState) { - if matches!(new_key_state, KeyState::Repeat) { - return; - } - let Some(key_data) = self.map.get_mut(&key) else { unreachable!(); }; @@ -149,9 +152,9 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ExtensionCollector<'_>) { - collector.add_system(StartEvent, initialize); - collector.add_system(PreUpdateEvent, maybe_clear_cursor_is_first_move); - collector.add_system(PostPresentEvent, set_keys_prev_tick_state); + collector.add_system(*START_PHASE, initialize); + collector.add_system(*PRE_UPDATE_PHASE, maybe_clear_cursor_is_first_move); + collector.add_system(*SET_PREV_KEY_STATE_PHASE, set_pending_key_states); collector.add_sole(Keys::default()).ok(); collector.add_sole(Cursor::default()).ok(); @@ -173,7 +176,7 @@ fn initialize( let mut keys = keys_ref.to_single(); - keys.set_key_state(key, key_state); + keys.pending.push((key, key_state)); }); let cursor_weak_ref = cursor.to_weak_ref(); @@ -194,7 +197,6 @@ fn initialize( let cursor_flags_weak_ref = cursor_flags.to_weak_ref(); window.set_focus_callback(move |is_focused| { - #[cfg(feature = "debug")] tracing::trace!("Window is focused: {is_focused}"); let cursor_flags_ref = cursor_flags_weak_ref.access().expect("No world"); @@ -209,7 +211,6 @@ fn maybe_clear_cursor_is_first_move( ) { if cursor_flags.is_first_move.pending_clear { - #[cfg(feature = "debug")] tracing::trace!("Clearing is_first_move"); // This flag was set for the whole previous tick so it can be cleared now @@ -219,7 +220,6 @@ fn maybe_clear_cursor_is_first_move( } if cursor.has_moved && cursor_flags.is_first_move.flag { - #[cfg(feature = "debug")] tracing::trace!("Setting flag to clear is_first_move next tick"); // Make this system clear is_first_move the next time it runs @@ -227,11 +227,21 @@ fn maybe_clear_cursor_is_first_move( } } -fn set_keys_prev_tick_state(mut keys: Single<Keys>) +fn set_pending_key_states(mut keys: Single<Keys>) { - for key_data in keys.map.values_mut() { + let Keys { map, pending } = &mut *keys; + + for key_data in map.values_mut() { key_data.prev_tick_state = key_data.state; } + + for (key, key_state) in pending { + let Some(key_data) = map.get_mut(key) else { + unreachable!(); + }; + + key_data.state = *key_state; + } } #[derive(Debug)] diff --git a/engine/src/lib.rs b/engine/src/lib.rs index abf26f5..a9a5a97 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,44 +1,32 @@ #![deny(clippy::all, clippy::pedantic)] #![allow(clippy::needless_pass_by_value)] -use ecs::component::Sequence as ComponentSequence; -use ecs::event::component::TypeTransformComponentsToAddedEvents; -use ecs::event::{Event, Sequence as EventSequence}; +use ecs::component::{Component, Sequence as ComponentSequence}; use ecs::extension::Extension; +use ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE; use ecs::sole::Sole; use ecs::system::{Into, System}; -use ecs::tuple::Reduce as TupleReduce; use ecs::uid::Uid; use ecs::{SoleAlreadyExistsError, World}; use crate::delta_time::{update as update_delta_time, DeltaTime, LastUpdate}; -use crate::event::{ - Conclude as ConcludeEvent, - PostPresent as PostPresentEvent, - PreUpdate as PreUpdateEvent, - Present as PresentEvent, - Update as UpdateEvent, -}; mod opengl; -mod shader_preprocessor; mod util; pub mod camera; +pub mod collision; pub mod data_types; pub mod delta_time; pub mod draw_flags; -pub mod event; pub mod file_format; pub mod input; pub mod lighting; pub mod material; pub mod math; pub mod mesh; -pub mod performance; pub mod projection; pub mod renderer; -pub mod shader; pub mod texture; pub mod transform; pub mod vertex; @@ -49,14 +37,6 @@ pub extern crate ecs; pub(crate) use crate::data_types::matrix; pub use crate::data_types::{color, vector}; -type EventOrder = ( - PreUpdateEvent, - UpdateEvent, - PresentEvent, - PostPresentEvent, - ConcludeEvent, -); - #[derive(Debug)] pub struct Engine { @@ -74,7 +54,7 @@ impl Engine world.add_sole(DeltaTime::default()).ok(); world.register_system( - PreUpdateEvent, + *PRE_UPDATE_PHASE, update_delta_time .into_system() .initialize((LastUpdate::default(),)), @@ -85,19 +65,28 @@ impl Engine pub fn spawn<Comps>(&mut self, components: Comps) -> Uid where - Comps: ComponentSequence + TupleReduce<TypeTransformComponentsToAddedEvents>, - Comps::Out: EventSequence, + Comps: ComponentSequence, { self.world.create_entity(components) } pub fn register_system<'this, SystemImpl>( &'this mut self, - event: impl Event, + phase_euid: Uid, system: impl System<'this, SystemImpl>, ) { - self.world.register_system(event, system); + self.world.register_system(phase_euid, system); + } + + pub fn register_observer_system<'this, SystemImpl, Event>( + &'this mut self, + system: impl System<'this, SystemImpl>, + event: Event, + ) where + Event: Component, + { + self.world.register_observer_system(system, event); } /// Adds a globally shared singleton value. @@ -117,7 +106,7 @@ impl Engine /// Runs the event loop. pub fn start(&self) { - self.world.event_loop::<EventOrder>(); + self.world.start_loop(); } } diff --git a/engine/src/material.rs b/engine/src/material.rs index aae6003..e368519 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -19,6 +19,14 @@ pub struct Material pub shininess: f32, } +impl Material +{ + pub fn builder() -> Builder + { + Builder::default() + } +} + /// [`Material`] builder. #[derive(Debug, Clone)] pub struct Builder diff --git a/engine/src/math.rs b/engine/src/math.rs index b86e760..0340de8 100644 --- a/engine/src/math.rs +++ b/engine/src/math.rs @@ -13,5 +13,5 @@ pub fn calc_triangle_surface_normal( let v1 = edge_b - egde_a; let v2 = edge_c - egde_a; - v1.cross(&v2) + v1.cross(&v2).normalize() } diff --git a/engine/src/mesh.rs b/engine/src/mesh.rs index de0af70..917e7f7 100644 --- a/engine/src/mesh.rs +++ b/engine/src/mesh.rs @@ -1,5 +1,6 @@ use ecs::Component; +use crate::vector::Vec3; use crate::vertex::Vertex; pub mod cube; @@ -26,8 +27,119 @@ impl Mesh } #[must_use] + pub fn vertices_mut(&mut self) -> &mut [Vertex] + { + &mut self.vertices + } + + #[must_use] pub fn indices(&self) -> Option<&[u32]> { self.indices.as_deref() } + + #[must_use] + pub fn indices_mut(&mut self) -> Option<&mut [u32]> + { + self.indices.as_deref_mut() + } + + /// Finds the vertex positions that are furthest in every 3D direction. Keep in mind + /// that this can be quite time-expensive if the mesh has many vertices. + pub fn find_furthest_vertex_positions(&self) -> DirectionPositions<'_> + { + let mut point_iter = self.vertices().iter().map(|vertex| &vertex.pos).into_iter(); + + let first_point = point_iter.next().unwrap(); + + point_iter + .fold( + FurthestPosAcc { + up: FurthestPos::new(&first_point, &Vec3::UP), + down: FurthestPos::new(&first_point, &Vec3::DOWN), + left: FurthestPos::new(&first_point, &Vec3::LEFT), + right: FurthestPos::new(&first_point, &Vec3::RIGHT), + back: FurthestPos::new(&first_point, &Vec3::BACK), + front: FurthestPos::new(&first_point, &Vec3::FRONT), + }, + |mut furthest_pos_acc, pos| { + furthest_pos_acc.up.update_if_further(pos); + furthest_pos_acc.down.update_if_further(pos); + furthest_pos_acc.left.update_if_further(pos); + furthest_pos_acc.right.update_if_further(pos); + furthest_pos_acc.back.update_if_further(pos); + furthest_pos_acc.front.update_if_further(pos); + + furthest_pos_acc + }, + ) + .into() + } +} + +#[derive(Debug, Clone)] +pub struct DirectionPositions<'mesh> +{ + pub up: &'mesh Vec3<f32>, + pub down: &'mesh Vec3<f32>, + pub left: &'mesh Vec3<f32>, + pub right: &'mesh Vec3<f32>, + pub back: &'mesh Vec3<f32>, + pub front: &'mesh Vec3<f32>, +} + +impl<'mesh> From<FurthestPosAcc<'mesh>> for DirectionPositions<'mesh> +{ + fn from(acc: FurthestPosAcc<'mesh>) -> Self + { + Self { + up: acc.up.pos, + down: acc.down.pos, + left: acc.left.pos, + right: acc.right.pos, + back: acc.back.pos, + front: acc.front.pos, + } + } +} + +#[derive(Debug)] +struct FurthestPosAcc<'mesh> +{ + up: FurthestPos<'mesh>, + down: FurthestPos<'mesh>, + left: FurthestPos<'mesh>, + right: FurthestPos<'mesh>, + back: FurthestPos<'mesh>, + front: FurthestPos<'mesh>, +} + +#[derive(Debug)] +struct FurthestPos<'mesh> +{ + pos: &'mesh Vec3<f32>, + dot_prod: f32, + direction: &'mesh Vec3<f32>, +} + +impl<'mesh> FurthestPos<'mesh> +{ + fn new(pos: &'mesh Vec3<f32>, direction: &'mesh Vec3<f32>) -> Self + { + Self { + pos, + dot_prod: direction.dot(&pos), + direction, + } + } + + fn update_if_further(&mut self, point: &'mesh Vec3<f32>) + { + let point_dot_prod = self.direction.dot(point); + + if point_dot_prod > self.dot_prod { + self.pos = point; + self.dot_prod = point_dot_prod; + } + } } diff --git a/engine/src/mesh/cube.rs b/engine/src/mesh/cube.rs index 7cdf885..c29ce0b 100644 --- a/engine/src/mesh/cube.rs +++ b/engine/src/mesh/cube.rs @@ -1,7 +1,7 @@ use crate::math::calc_triangle_surface_normal; use crate::mesh::Mesh; use crate::util::builder; -use crate::vector::Vec3; +use crate::vector::{Vec2, Vec3}; use crate::vertex::{Builder as VertexBuilder, Vertex}; builder! { @@ -27,676 +27,455 @@ impl CreationSpec } } -#[derive(Debug)] +/// Describes a single side of a cube (obviously). +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Side { + /// +Z Front, + + /// -Z Back, + + /// -X Left, + + /// +X Right, + + /// +Y Top, + + /// -Y Bottom, } -#[derive(Debug)] -pub enum Corner +/// Describes what location on a side of a cube a face is. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum FaceLocation { - TopRight, - TopLeft, - BottomRight, - BottomLeft, + /// 🮝 + RightUp, + + /// 🮟 + LeftDown, } /// Creates a cube mesh. +/// +/// By default, the texture coordinates are arranged so that the full texture is visible +/// on every side. This can be changed inside of the `face_cb` function. pub fn create( creation_spec: CreationSpec, - vertex_builder_cb: impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, + face_cb: impl FnMut(FaceVertices, Side, FaceLocation) -> FaceVertices, ) -> Mesh { - let mut vertices = [const { None }; VertexIndex::VARIANT_CNT]; - - create_front(&creation_spec, &mut vertices, &vertex_builder_cb); - create_back(&creation_spec, &mut vertices, &vertex_builder_cb); - create_right(&creation_spec, &mut vertices, &vertex_builder_cb); - create_left(&creation_spec, &mut vertices, &vertex_builder_cb); - create_top(&creation_spec, &mut vertices, &vertex_builder_cb); - create_bottom(&creation_spec, &mut vertices, &vertex_builder_cb); - - Mesh::new( - vertices.map(Option::unwrap).to_vec(), - Some( - VERTEX_INDICES - .into_iter() - .flatten() - .map(|index| index as u32) - .collect(), - ), - ) -} + let mut data = Data::default(); -macro_rules! one { - ($tt: tt) => { - 1 - }; -} + create_side(&SidePositions::new_top(&creation_spec), &mut data); + create_side(&SidePositions::new_bottom(&creation_spec), &mut data); + create_side(&SidePositions::new_left(&creation_spec), &mut data); + create_side(&SidePositions::new_right(&creation_spec), &mut data); + create_side(&SidePositions::new_back(&creation_spec), &mut data); + create_side(&SidePositions::new_front(&creation_spec), &mut data); -macro_rules! enum_with_variant_cnt { - ( - $(#[$attr: meta])* - enum $name: ident { - $($variant: ident,)* - } - ) => { - $(#[$attr])* - enum $name { - $($variant,)* - } - - impl $name { - const VARIANT_CNT: usize = 0 $(+ one!($variant))*; - } - }; + data.into_mesh(face_cb) } -enum_with_variant_cnt! { -#[repr(u32)] -enum VertexIndex +#[derive(Debug, Default)] +struct Data { - FrontTopRight, - FrontBottomRight, - FrontBottomLeft, - FrontTopLeft, - - BackTopRight, - BackBottomRight, - BackBottomLeft, - BackTopLeft, - - RightBackTop, - RightBackBottom, - RightFrontTop, - RightFrontBottom, - - LeftBackTop, - LeftBackBottom, - LeftFrontTop, - LeftFrontBottom, - - TopBackRight, - TopBackLeft, - TopFrontRight, - TopFrontLeft, - - BottomBackRight, - BottomBackLeft, - BottomFrontRight, - BottomFrontLeft, -} + faces: Vec<Face>, + vertex_data: VertexData, } -fn create_front( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) +#[derive(Debug, Default)] +struct VertexData { - let front_top_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; - - let front_bottom_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; + vertex_positions: Vec<Vec3<f32>>, + vertex_normals: Vec<Vec3<f32>>, +} - let front_bottom_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; +impl Data +{ + fn into_mesh( + self, + mut face_cb: impl FnMut(FaceVertices, Side, FaceLocation) -> FaceVertices, + ) -> Mesh + { + let mut vertices = Vec::<Vertex>::with_capacity(self.faces.len() * 3); + let mut indices = Vec::<u32>::with_capacity(self.faces.len() * 3); - let front_top_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; + let mut face_location = FaceLocation::RightUp; - let front_normal = calc_triangle_surface_normal( - &front_top_right_pos, - &front_bottom_right_pos, - &front_top_left_pos, - ); - - vertices[VertexIndex::FrontTopRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(front_top_right_pos) - .normal(front_normal), - Side::Front, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::FrontBottomRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(front_bottom_right_pos) - .normal(front_normal), - Side::Front, - Corner::BottomRight, - ) - .build(), - ); - - vertices[VertexIndex::FrontBottomLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(front_bottom_left_pos) - .normal(front_normal), - Side::Front, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::FrontTopLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(front_top_left_pos) - .normal(front_normal), - Side::Front, - Corner::TopLeft, - ) - .build(), - ); -} + let Self { faces, vertex_data } = self; -fn create_back( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) -{ - let back_top_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + for face in faces { + let side = face.side; - let back_bottom_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, - }; + let face_vertices = face_cb( + FaceVertices::new(face, &vertex_data) + .with_full_per_side_tex_coords(face_location), + side, + face_location, + ); - let back_bottom_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, - }; + for vertex in face_vertices.vertices { + if let Some((prev_vertex_index, _)) = vertices + .iter() + .enumerate() + .find(|(_, prev_vertex)| *prev_vertex == &vertex) + { + indices + .push(u32::try_from(prev_vertex_index).expect( + "Vertex index does not fit into 32-bit unsigned int", + )); - let back_top_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + continue; + } - let back_normal = -calc_triangle_surface_normal( - &back_top_right_pos, - &back_bottom_right_pos, - &back_top_left_pos, - ); - - vertices[VertexIndex::BackTopRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(back_top_right_pos) - .normal(back_normal), - Side::Back, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::BackBottomRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(back_bottom_right_pos) - .normal(back_normal), - Side::Back, - Corner::BottomRight, - ) - .build(), - ); - - vertices[VertexIndex::BackBottomLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(back_bottom_left_pos) - .normal(back_normal), - Side::Back, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::BackTopLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(back_top_left_pos) - .normal(back_normal), - Side::Back, - Corner::TopLeft, - ) - .build(), - ); -} + vertices.push(vertex); -fn create_right( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) -{ - let right_back_top_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + let vertex_index = u32::try_from(vertices.len() - 1) + .expect("Vertex index does not fit into 32-bit unsigned int"); - let right_back_bottom_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, - }; + indices.push(vertex_index); + } - let right_front_top_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; + match face_location { + FaceLocation::RightUp => face_location = FaceLocation::LeftDown, + FaceLocation::LeftDown => face_location = FaceLocation::RightUp, + } + } - let right_front_bottom_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; + Mesh::new(vertices, Some(indices)) + } +} - let right_normal = calc_triangle_surface_normal( - &right_back_top_pos, - &right_back_bottom_pos, - &right_front_top_pos, - ); - - vertices[VertexIndex::RightBackTop as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(right_back_top_pos) - .normal(right_normal), - Side::Right, - Corner::TopLeft, - ) - .build(), - ); - - vertices[VertexIndex::RightBackBottom as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(right_back_bottom_pos) - .normal(right_normal), - Side::Right, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::RightFrontTop as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(right_front_top_pos) - .normal(right_normal), - Side::Right, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::RightFrontBottom as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(right_front_bottom_pos) - .normal(right_normal), - Side::Right, - Corner::BottomRight, - ) - .build(), - ); +/// The vertices of a single face of a cube. +#[derive(Debug, Default, Clone)] +pub struct FaceVertices +{ + /// The three vertices of a face in counter-clockwise order. + /// + /// Order when [`FaceLocation::RightUp`]: + /// ```text + /// ₂ ₁ + /// 🮝 + /// ³ + /// ``` + /// + /// Order when [`FaceLocation::LeftDown`]: + /// ```text + /// ₁ + /// 🮟 + /// ² ³ + /// ``` + pub vertices: [Vertex; 3], } -fn create_left( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) +impl FaceVertices { - let left_back_top_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + fn new(face: Face, vertex_data: &VertexData) -> Self + { + Self { + vertices: face.vertices.map(|face_vertex| { + let vertex_pos = vertex_data + .vertex_positions + .get(face_vertex.pos_index as usize) + .expect("Vertex position index is out of bounds") + .clone(); + + let vertex_normal = vertex_data + .vertex_normals + .get(face_vertex.normal_index as usize) + .expect("Vertex normal index is out of bounds") + .clone(); + + VertexBuilder::default() + .pos(vertex_pos) + .normal(vertex_normal) + .build() + }), + } + } - let left_back_bottom_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, - }; + fn with_full_per_side_tex_coords(mut self, face_location: FaceLocation) -> Self + { + match face_location { + FaceLocation::RightUp => { + self.vertices[0].texture_coords = Vec2 { x: 1.0, y: 1.0 }; + self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 1.0 }; + self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }; + } + FaceLocation::LeftDown => { + self.vertices[0].texture_coords = Vec2 { x: 0.0, y: 1.0 }; + self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 0.0 }; + self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }; + } + }; + + self + } +} - let left_front_top_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; +#[derive(Debug)] +struct Face +{ + vertices: [FaceVertex; 3], + side: Side, +} - let left_front_bottom_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +struct FaceVertex +{ + pos_index: u32, + normal_index: u32, +} - let left_normal = -calc_triangle_surface_normal( - &left_back_top_pos, - &left_back_bottom_pos, - &left_front_top_pos, - ); - - vertices[VertexIndex::LeftBackTop as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(left_back_top_pos) - .normal(left_normal), - Side::Left, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::LeftBackBottom as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(left_back_bottom_pos) - .normal(left_normal), - Side::Left, - Corner::BottomRight, - ) - .build(), - ); - - vertices[VertexIndex::LeftFrontTop as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(left_front_top_pos) - .normal(left_normal), - Side::Left, - Corner::TopLeft, - ) - .build(), - ); - - vertices[VertexIndex::LeftFrontBottom as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(left_front_bottom_pos) - .normal(left_normal), - Side::Left, - Corner::BottomLeft, - ) - .build(), - ); +#[derive(Debug)] +struct SidePositions +{ + up_left: Vec3<f32>, + up_right: Vec3<f32>, + down_left: Vec3<f32>, + down_right: Vec3<f32>, + normal_calc_order: NormalCalcOrder, + side: Side, } -fn create_top( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) +impl SidePositions { - let top_back_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + fn new_top(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: creation_spec.depth / 2.0, + }; + + let down_right = Vec3 { + x: creation_spec.width / 2.0, + y: creation_spec.height / 2.0, + z: -(creation_spec.depth / 2.0), + }; + + Self { + up_left, + up_right: Vec3 { x: down_right.x, ..up_left.clone() }, + down_left: Vec3 { x: up_left.x, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::Clockwise, + side: Side::Top, + } + } - let top_back_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + fn new_bottom(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: -creation_spec.height / 2.0, + z: creation_spec.depth / 2.0, + }; + + let down_right = Vec3 { + x: creation_spec.width / 2.0, + y: -creation_spec.height / 2.0, + z: -(creation_spec.depth / 2.0), + }; + + Self { + up_left, + up_right: Vec3 { x: down_right.x, ..up_left.clone() }, + down_left: Vec3 { x: up_left.x, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::CounterClockwise, + side: Side::Bottom, + } + } - let top_front_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; + fn new_left(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: -(creation_spec.depth / 2.0), + }; + + let down_right = Vec3 { + x: -(creation_spec.width / 2.0), + y: -(creation_spec.height / 2.0), + z: creation_spec.depth / 2.0, + }; + + Self { + up_left, + up_right: Vec3 { z: down_right.z, ..up_left.clone() }, + down_left: Vec3 { z: up_left.z, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::CounterClockwise, + side: Side::Left, + } + } - let top_front_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; + fn new_right(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: (creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: -(creation_spec.depth / 2.0), + }; + + let down_right = Vec3 { + x: (creation_spec.width / 2.0), + y: -(creation_spec.height / 2.0), + z: creation_spec.depth / 2.0, + }; + + Self { + up_left, + up_right: Vec3 { z: down_right.z, ..up_left.clone() }, + down_left: Vec3 { z: up_left.z, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::Clockwise, + side: Side::Right, + } + } + + fn new_back(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: -creation_spec.depth / 2.0, + }; + + let down_right = Vec3 { + x: creation_spec.width / 2.0, + y: -(creation_spec.height / 2.0), + z: -creation_spec.depth / 2.0, + }; + + Self { + up_left, + up_right: Vec3 { x: down_right.x, ..up_left.clone() }, + down_left: Vec3 { x: up_left.x, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::Clockwise, + side: Side::Back, + } + } - let top_normal = -calc_triangle_surface_normal( - &top_back_right_pos, - &top_back_left_pos, - &top_front_right_pos, - ); - - vertices[VertexIndex::TopBackRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(top_back_right_pos) - .normal(top_normal), - Side::Top, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::TopBackLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(top_back_left_pos) - .normal(top_normal), - Side::Top, - Corner::TopLeft, - ) - .build(), - ); - - vertices[VertexIndex::TopFrontLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(top_front_left_pos) - .normal(top_normal), - Side::Top, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::TopFrontRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(top_front_right_pos) - .normal(top_normal), - Side::Top, - Corner::BottomRight, - ) - .build(), - ); + fn new_front(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: creation_spec.depth / 2.0, + }; + + let down_right = Vec3 { + x: creation_spec.width / 2.0, + y: -(creation_spec.height / 2.0), + z: creation_spec.depth / 2.0, + }; + + Self { + up_left, + up_right: Vec3 { x: down_right.x, ..up_left.clone() }, + down_left: Vec3 { x: up_left.x, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::CounterClockwise, + side: Side::Front, + } + } } -fn create_bottom( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) +#[derive(Debug)] +enum NormalCalcOrder { - let bottom_back_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: (creation_spec.depth / 2.0), - }; + Clockwise, + CounterClockwise, +} - let bottom_back_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, +fn create_side(side_positions: &SidePositions, data: &mut Data) +{ + let normal = match side_positions.normal_calc_order { + NormalCalcOrder::Clockwise => calc_triangle_surface_normal( + &side_positions.up_left, + &side_positions.up_right, + &side_positions.down_left, + ), + NormalCalcOrder::CounterClockwise => calc_triangle_surface_normal( + &side_positions.up_left, + &side_positions.down_left, + &side_positions.up_right, + ), }; - let bottom_front_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; + data.vertex_data.vertex_normals.push(normal); - let bottom_front_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; + let top_normal_index = data.vertex_data.vertex_normals.len() - 1; - let bottom_normal = calc_triangle_surface_normal( - &bottom_back_right_pos, - &bottom_back_left_pos, - &bottom_front_right_pos, - ); - - vertices[VertexIndex::BottomBackRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(bottom_back_right_pos) - .normal(bottom_normal), - Side::Bottom, - Corner::BottomRight, - ) - .build(), - ); - - vertices[VertexIndex::BottomBackLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(bottom_back_left_pos) - .normal(bottom_normal), - Side::Bottom, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::BottomFrontRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(bottom_front_right_pos) - .normal(bottom_normal), - Side::Bottom, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::BottomFrontLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(bottom_front_left_pos) - .normal(bottom_normal), - Side::Bottom, - Corner::TopLeft, - ) - .build(), - ); -} + data.vertex_data + .vertex_positions + .push(side_positions.up_right); -const VERTEX_INDICES_FRONT: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::FrontTopRight, - VertexIndex::FrontBottomRight, - VertexIndex::FrontTopLeft, - // - // 🮟 - VertexIndex::FrontBottomRight, - VertexIndex::FrontBottomLeft, - VertexIndex::FrontTopLeft, -]; + let up_right_pos_index = data.vertex_data.vertex_positions.len() - 1; -const VERTEX_INDICES_BACK: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::BackTopRight, - VertexIndex::BackBottomRight, - VertexIndex::BackTopLeft, - // - // 🮟 - VertexIndex::BackBottomRight, - VertexIndex::BackBottomLeft, - VertexIndex::BackTopLeft, -]; + data.vertex_data + .vertex_positions + .push(side_positions.up_left); -const VERTEX_INDICES_RIGHT: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::RightBackTop, - VertexIndex::RightBackBottom, - VertexIndex::RightFrontTop, - // - // 🮟 - VertexIndex::RightBackBottom, - VertexIndex::RightFrontBottom, - VertexIndex::RightFrontTop, -]; + let up_left_pos_index = data.vertex_data.vertex_positions.len() - 1; -const VERTEX_INDICES_LEFT: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::LeftBackTop, - VertexIndex::LeftBackBottom, - VertexIndex::LeftFrontTop, - // - // 🮟 - VertexIndex::LeftBackBottom, - VertexIndex::LeftFrontBottom, - VertexIndex::LeftFrontTop, -]; + data.vertex_data + .vertex_positions + .push(side_positions.down_left); -const VERTEX_INDICES_TOP: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::TopBackRight, - VertexIndex::TopBackLeft, - VertexIndex::TopFrontRight, - // - // 🮟 - VertexIndex::TopBackLeft, - VertexIndex::TopFrontLeft, - VertexIndex::TopFrontRight, -]; + let down_left_pos_index = data.vertex_data.vertex_positions.len() - 1; + + data.vertex_data + .vertex_positions + .push(side_positions.down_right); + + let down_right_pos_index = data.vertex_data.vertex_positions.len() - 1; -const VERTEX_INDICES_BOTTOM: [VertexIndex; 6] = [ // 🮝 - VertexIndex::BottomBackRight, - VertexIndex::BottomBackLeft, - VertexIndex::BottomFrontRight, - // + data.faces.push(Face { + vertices: [ + FaceVertex { + pos_index: up_right_pos_index as u32, + normal_index: top_normal_index as u32, + }, + FaceVertex { + pos_index: up_left_pos_index as u32, + normal_index: top_normal_index as u32, + }, + FaceVertex { + pos_index: down_right_pos_index as u32, + normal_index: top_normal_index as u32, + }, + ], + side: side_positions.side, + }); + // 🮟 - VertexIndex::BottomBackLeft, - VertexIndex::BottomFrontLeft, - VertexIndex::BottomFrontRight, -]; - -const VERTEX_INDICES: [[VertexIndex; 6]; 6] = [ - VERTEX_INDICES_FRONT, - VERTEX_INDICES_BACK, - VERTEX_INDICES_RIGHT, - VERTEX_INDICES_LEFT, - VERTEX_INDICES_TOP, - VERTEX_INDICES_BOTTOM, -]; + data.faces.push(Face { + vertices: [ + FaceVertex { + pos_index: up_left_pos_index as u32, + normal_index: top_normal_index as u32, + }, + FaceVertex { + pos_index: down_left_pos_index as u32, + normal_index: top_normal_index as u32, + }, + FaceVertex { + pos_index: down_right_pos_index as u32, + normal_index: top_normal_index as u32, + }, + ], + side: side_positions.side, + }); +} diff --git a/engine/src/opengl/buffer.rs b/engine/src/opengl/buffer.rs index 2be7f12..68a75fb 100644 --- a/engine/src/opengl/buffer.rs +++ b/engine/src/opengl/buffer.rs @@ -39,19 +39,6 @@ impl<Item> Buffer<Item> { self.buf } - - /// Does a weak clone of this buffer. The buffer itself is NOT copied in any way this - /// function only copies the internal buffer ID. - /// - /// # Safety - /// The returned `Buffer` must not be dropped if another `Buffer` referencing the - /// same buffer ID is used later or if a [`VertexArray`] is used later. - /// - /// [`VertexArray`]: crate::opengl::vertex_array::VertexArray - pub unsafe fn clone_weak(&self) -> Self - { - Self { buf: self.buf, _pd: PhantomData } - } } impl<Item> Drop for Buffer<Item> diff --git a/engine/src/shader_preprocessor.rs b/engine/src/opengl/glsl.rs index 70696ac..6fd5638 100644 --- a/engine/src/shader_preprocessor.rs +++ b/engine/src/opengl/glsl.rs @@ -1,183 +1,181 @@ +use std::borrow::Cow; +use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; use std::string::FromUtf8Error; const PREINCLUDE_DIRECTIVE: &str = "#preinclude"; -/// Preprocessor for shaders written in the OpenGL Shading Language. -pub struct ShaderPreprocessor +pub fn preprocess<'content>( + shader_content: impl Into<Cow<'content, str>>, + read_file: &impl Fn(&Path) -> Result<Vec<u8>, std::io::Error>, +) -> Result<Cow<'content, str>, PreprocessingError> { - read_file: fn(&Path) -> Result<Vec<u8>, std::io::Error>, - base_dir: PathBuf, + do_preprocess(shader_content, SpanPath::Original, read_file) } -impl ShaderPreprocessor +fn do_preprocess<'content>( + shader_content: impl Into<Cow<'content, str>>, + shader_path: SpanPath<'_>, + read_file: &impl Fn(&Path) -> Result<Vec<u8>, std::io::Error>, +) -> Result<Cow<'content, str>, PreprocessingError> { - pub fn new(base_dir: PathBuf) -> Self - { - Self { - read_file: |path| std::fs::read(path), - base_dir, - } - } + let shader_content = shader_content.into(); - pub fn preprocess( - &self, - shader_content: String, - shader_file_path: &Path, - ) -> Result<String, Error> - { - let mut preincludes = shader_content - .match_indices(PREINCLUDE_DIRECTIVE) - .peekable(); + let mut preincludes = shader_content + .match_indices(PREINCLUDE_DIRECTIVE) + .peekable(); - if preincludes.peek().is_none() { - // Shader content contains no preincludes - return Ok(shader_content); - }; + if preincludes.peek().is_none() { + // Shader content contains no preincludes + return Ok(shader_content.into()); + }; - let mut preprocessed = shader_content.clone(); + let mut preprocessed = shader_content.to_string(); - let mut curr = shader_content.find(PREINCLUDE_DIRECTIVE); + let mut curr = shader_content.find(PREINCLUDE_DIRECTIVE); - let mut last_start = 0; - let mut span_line_offset = 0; + let mut last_start = 0; + let mut span_line_offset = 0; - while let Some(preinclude_start) = curr { - let replacement_job = self.handle_preinclude( - &preprocessed, - shader_file_path, - preinclude_start, - span_line_offset, - )?; + while let Some(preinclude_start) = curr { + let replacement_job = handle_preinclude( + &preprocessed, + &shader_path, + preinclude_start, + span_line_offset, + )?; - let path = replacement_job.path.clone(); + let path = replacement_job.path.clone(); - let mut included = String::from_utf8( - (self.read_file)(&self.base_dir.join(replacement_job.path.clone())) - .map_err(|err| Error::ReadIncludedShaderFailed { - source: err, - path: replacement_job.path.clone(), - })?, - ) - .map_err(|err| Error::IncludedShaderInvalidUtf8 { - source: err, - path: path.clone(), + let mut included = + String::from_utf8(read_file(&replacement_job.path).map_err(|err| { + PreprocessingError::ReadIncludedShaderFailed { + source: err, + path: replacement_job.path.clone(), + } + })?) + .map_err(|err| { + PreprocessingError::IncludedShaderInvalidUtf8 { + source: err, + path: path.clone(), + } })?; - if let Some(first_line) = included.lines().next() { - if first_line.starts_with("#version") { - included = included - .chars() - .skip_while(|character| *character != '\n') - .collect(); - } + if let Some(first_line) = included.lines().next() { + if first_line.starts_with("#version") { + included = included + .chars() + .skip_while(|character| *character != '\n') + .collect(); } - - let included_preprocessed = - self.preprocess(included, &replacement_job.path)?; - - let start = replacement_job.start_index; - let end = replacement_job.end_index; - - preprocessed.replace_range(start..end, &included_preprocessed); - - curr = preprocessed[last_start + 1..] - .find(PREINCLUDE_DIRECTIVE) - .map(|index| index + 1); - - last_start = preinclude_start + included_preprocessed.len(); - - span_line_offset += included_preprocessed.lines().count(); } - Ok(preprocessed) - } + let included_preprocessed = do_preprocess( + &included, + SpanPath::Path(replacement_job.path.as_path().into()), + read_file, + )?; - fn handle_preinclude( - &self, - shader_content: &str, - shader_file_path: &Path, - preinclude_start_index: usize, - span_line_offset: usize, - ) -> Result<ReplacementJob, Error> - { - let expect_token = |token: char, index: usize| { - let token_found = shader_content.chars().nth(index).ok_or_else(|| { - Error::ExpectedToken { - expected: token, - span: Span::new( - shader_content, - &self.base_dir.join(shader_file_path), - index, - span_line_offset, - preinclude_start_index, - ), - } - })?; + let start = replacement_job.start_index; + let end = replacement_job.end_index; - if token_found != token { - return Err(Error::InvalidToken { - expected: token, - found: token_found, - span: Span::new( - shader_content, - &self.base_dir.join(shader_file_path), - index, - span_line_offset, - preinclude_start_index, - ), - }); - } + preprocessed.replace_range(start..end, &included_preprocessed); - Ok(()) - }; + curr = preprocessed[last_start + 1..] + .find(PREINCLUDE_DIRECTIVE) + .map(|index| index + 1); - let space_index = preinclude_start_index + PREINCLUDE_DIRECTIVE.len(); - let quote_open_index = space_index + 1; + last_start = preinclude_start + included_preprocessed.len(); - expect_token(' ', space_index)?; - expect_token('"', quote_open_index)?; + span_line_offset += included_preprocessed.lines().count(); + } - let buf = shader_content[quote_open_index + 1..] - .chars() - .take_while(|character| *character != '"') - .map(|character| character as u8) - .collect::<Vec<_>>(); + Ok(preprocessed.into()) +} - if buf.is_empty() { - return Err(Error::ExpectedToken { - expected: '"', +fn handle_preinclude( + shader_content: &str, + shader_path: &SpanPath<'_>, + preinclude_start_index: usize, + span_line_offset: usize, +) -> Result<ReplacementJob, PreprocessingError> +{ + let expect_token = |token: char, index: usize| { + let token_found = shader_content.chars().nth(index).ok_or_else(|| { + PreprocessingError::ExpectedToken { + expected: token, span: Span::new( shader_content, - &self.base_dir.join(shader_file_path), - shader_content.len() - 1, + shader_path.to_owned(), + index, span_line_offset, preinclude_start_index, ), - }); - } - - let path_len = buf.len(); + } + })?; - let path = PathBuf::from(String::from_utf8(buf).map_err(|err| { - Error::PreincludePathInvalidUtf8 { - source: err, + if token_found != token { + return Err(PreprocessingError::InvalidToken { + expected: token, + found: token_found, span: Span::new( shader_content, - &self.base_dir.join(shader_file_path), - quote_open_index + 1, + shader_path.to_owned(), + index, span_line_offset, preinclude_start_index, ), - } - })?); + }); + } - Ok(ReplacementJob { - start_index: preinclude_start_index, - end_index: quote_open_index + 1 + path_len + 1, - path, - }) + Ok(()) + }; + + let space_index = preinclude_start_index + PREINCLUDE_DIRECTIVE.len(); + let quote_open_index = space_index + 1; + + expect_token(' ', space_index)?; + expect_token('"', quote_open_index)?; + + let buf = shader_content[quote_open_index + 1..] + .chars() + .take_while(|character| *character != '"') + .map(|character| character as u8) + .collect::<Vec<_>>(); + + if buf.is_empty() { + return Err(PreprocessingError::ExpectedToken { + expected: '"', + span: Span::new( + shader_content, + shader_path.to_owned(), + shader_content.len() - 1, + span_line_offset, + preinclude_start_index, + ), + }); } + + let path_len = buf.len(); + + let path = PathBuf::from(String::from_utf8(buf).map_err(|err| { + PreprocessingError::PreincludePathInvalidUtf8 { + source: err, + span: Span::new( + shader_content, + shader_path.to_owned(), + quote_open_index + 1, + span_line_offset, + preinclude_start_index, + ), + } + })?); + + Ok(ReplacementJob { + start_index: preinclude_start_index, + end_index: quote_open_index + 1 + path_len + 1, + path, + }) } struct ReplacementJob @@ -187,15 +185,14 @@ struct ReplacementJob path: PathBuf, } -/// Shader preprocessing error. #[derive(Debug, thiserror::Error)] -pub enum Error +pub enum PreprocessingError { #[error( "Invalid token at line {}, column {} of {}. Expected '{}', found '{}'", span.line, span.column, - span.file.display(), + span.path, expected, found )] @@ -211,7 +208,7 @@ pub enum Error expected, span.line, span.column, - span.file.display(), + span.path )] ExpectedToken { @@ -222,7 +219,7 @@ pub enum Error "Preinclude path at line {}, column {} of {} is invalid UTF-8", span.line, span.column, - span.file.display(), + span.path )] PreincludePathInvalidUtf8 { @@ -249,18 +246,19 @@ pub enum Error } #[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] pub struct Span { - line: usize, - column: usize, - file: PathBuf, + pub line: usize, + pub column: usize, + pub path: SpanPath<'static>, } impl Span { fn new( file_content: &str, - file_path: &Path, + path: SpanPath<'static>, char_index: usize, line_offset: usize, line_start_index: usize, @@ -272,7 +270,49 @@ impl Span Self { line, column: char_index - line_start_index + 1, - file: file_path.to_path_buf(), + path, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SpanPath<'a> +{ + Original, + Path(Cow<'a, Path>), +} + +impl<'a> SpanPath<'a> +{ + fn to_owned(&self) -> SpanPath<'static> + { + match self { + Self::Original => SpanPath::Original, + Self::Path(path) => SpanPath::Path(Cow::Owned(path.to_path_buf().into())), + } + } +} + +impl<'a> Display for SpanPath<'a> +{ + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result + { + match self { + Self::Original => write!(formatter, "original file"), + Self::Path(path) => write!(formatter, "file {}", path.display()), + } + } +} + +impl<'a, PathLike> PartialEq<PathLike> for SpanPath<'a> +where + PathLike: AsRef<Path>, +{ + fn eq(&self, other: &PathLike) -> bool + { + match self { + Self::Original => false, + Self::Path(path) => path == other.as_ref(), } } } @@ -290,21 +330,17 @@ fn find_line_of_index(text: &str, index: usize) -> usize mod tests { use std::ffi::OsStr; - use std::path::{Path, PathBuf}; + use std::path::Path; - use super::{Error, ShaderPreprocessor}; + use super::{preprocess, PreprocessingError}; + use crate::opengl::glsl::SpanPath; #[test] fn preprocess_no_directives_is_same() { assert_eq!( - ShaderPreprocessor { - read_file: |_| { unreachable!() }, - base_dir: PathBuf::new() - } - .preprocess("#version 330 core\n".to_string(), Path::new("foo.glsl"),) - .unwrap(), - "#version 330 core\n".to_string() + preprocess("#version 330 core\n", &|_| { unreachable!() }).unwrap(), + "#version 330 core\n" ); } @@ -312,20 +348,15 @@ mod tests fn preprocess_with_directives_works() { assert_eq!( - ShaderPreprocessor { - read_file: |_| { Ok(b"out vec4 FragColor;".to_vec()) }, - base_dir: PathBuf::new() - } - .preprocess( + preprocess( concat!( "#version 330 core\n", "\n", "#preinclude \"foo.glsl\"\n", "\n", "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), + ), + &|_| { Ok(b"out vec4 FragColor;".to_vec()) } ) .unwrap(), concat!( @@ -338,11 +369,7 @@ mod tests ); assert_eq!( - ShaderPreprocessor { - read_file: |_| { Ok(b"out vec4 FragColor;".to_vec()) }, - base_dir: PathBuf::new() - } - .preprocess( + preprocess( concat!( "#version 330 core\n", "\n", @@ -351,9 +378,8 @@ mod tests "in vec3 in_frag_color;\n", "\n", "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), + ), + &|_| { Ok(b"out vec4 FragColor;".to_vec()) } ) .unwrap(), concat!( @@ -368,8 +394,19 @@ mod tests ); assert_eq!( - ShaderPreprocessor { - read_file: |path| { + preprocess( + concat!( + "#version 330 core\n", + "\n", + "#preinclude \"bar.glsl\"\n", + "\n", + "in vec3 in_frag_color;\n", + "\n", + "#preinclude \"foo.glsl\"\n", + "\n", + "void main() {}", + ), + &|path| { if path == OsStr::new("bar.glsl") { Ok(b"out vec4 FragColor;".to_vec()) } else { @@ -381,22 +418,6 @@ mod tests .to_vec()) } }, - base_dir: PathBuf::new() - } - .preprocess( - concat!( - "#version 330 core\n", - "\n", - "#preinclude \"bar.glsl\"\n", - "\n", - "in vec3 in_frag_color;\n", - "\n", - "#preinclude \"foo.glsl\"\n", - "\n", - "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), ) .unwrap(), concat!( @@ -415,15 +436,9 @@ mod tests } #[test] - fn preprocess_invalid_shader_does_not_work() + fn preprocess_invalid_directive_does_not_work() { - let path = Path::new("foo.glsl"); - - let res = ShaderPreprocessor { - read_file: |_| Ok(b"out vec4 FragColor;".to_vec()), - base_dir: PathBuf::new(), - } - .preprocess( + let res = preprocess( concat!( "#version 330 core\n", "\n", @@ -431,12 +446,11 @@ mod tests "#preinclude foo.glsl\"\n", "\n", "void main() {}", - ) - .to_string(), - path, + ), + &|_| Ok(b"out vec4 FragColor;".to_vec()), ); - let Err(Error::InvalidToken { expected, found, span }) = res else { + let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else { panic!( "Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}" ); @@ -446,16 +460,25 @@ mod tests assert_eq!(found, 'f'); assert_eq!(span.line, 3); assert_eq!(span.column, 13); - assert_eq!(span.file, path); + assert_eq!(span.path, SpanPath::Original); } #[test] fn preprocess_error_has_correct_span() { - let path = Path::new("foo.glsl"); - - let res = ShaderPreprocessor { - read_file: |path| { + let res = preprocess( + concat!( + "#version 330 core\n", + "\n", + "#preinclude \"bar.glsl\"\n", + "\n", + "#preinclude \"foo.glsl\"\n", + "\n", + "in vec3 in_frag_color;\n", + "\n", + "void main() {}", + ), + &|path| { if path == OsStr::new("bar.glsl") { Ok(concat!( "out vec4 FragColor;\n", @@ -464,29 +487,25 @@ mod tests ) .as_bytes() .to_vec()) + } else if path == OsStr::new("foo.glsl") { + Ok(concat!( + "uniform sampler2D input_texture;\n", + "\n", + // Missing space before first " + "#preinclude\"shared_types.glsl\"\n", + ) + .as_bytes() + .to_vec()) } else { - Ok(b"uniform sampler2D input_texture;".to_vec()) + panic!(concat!( + "Expected read function to be called with ", + "either path bar.glsl or foo.glsl" + )); } }, - base_dir: PathBuf::new(), - } - .preprocess( - concat!( - "#version 330 core\n", - "\n", - "#preinclude \"bar.glsl\"\n", - "\n", - "in vec3 in_frag_color;\n", - "\n", - "#preinclude\"foo.glsl\"\n", - "\n", - "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), ); - let Err(Error::InvalidToken { expected, found, span }) = res else { + let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else { panic!( "Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}" ); @@ -494,17 +513,26 @@ mod tests assert_eq!(expected, ' '); assert_eq!(found, '"'); - assert_eq!(span.line, 7); + assert_eq!(span.line, 3); assert_eq!(span.column, 12); - assert_eq!(span.file, path); + assert_eq!(span.path, SpanPath::Path(Path::new("foo.glsl").into())); } #[test] fn preprocess_included_shader_with_include_works() { assert_eq!( - ShaderPreprocessor { - read_file: |path| { + preprocess( + concat!( + "#version 330 core\n", + "\n", + "#preinclude \"bar.glsl\"\n", + "\n", + "in vec3 in_frag_color;\n", + "\n", + "void main() {}", + ), + &|path| { if path == OsStr::new("bar.glsl") { Ok(concat!( "#preinclude \"foo.glsl\"\n", @@ -521,21 +549,7 @@ mod tests .as_bytes() .to_vec()) } - }, - base_dir: PathBuf::new() - } - .preprocess( - concat!( - "#version 330 core\n", - "\n", - "#preinclude \"bar.glsl\"\n", - "\n", - "in vec3 in_frag_color;\n", - "\n", - "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), + } ) .unwrap(), concat!( @@ -556,8 +570,17 @@ mod tests #[test] fn preprocess_included_shader_with_include_error_span_is_correct() { - let res = ShaderPreprocessor { - read_file: |path| { + let res = preprocess( + concat!( + "#version 330 core\n", + "\n", + "#preinclude \"bar.glsl\"\n", + "\n", + "in vec3 in_frag_color;\n", + "\n", + "void main() {}", + ), + &|path| { if path == OsStr::new("bar.glsl") { Ok(concat!( // ' instead of " @@ -576,23 +599,9 @@ mod tests .to_vec()) } }, - base_dir: PathBuf::new(), - } - .preprocess( - concat!( - "#version 330 core\n", - "\n", - "#preinclude \"bar.glsl\"\n", - "\n", - "in vec3 in_frag_color;\n", - "\n", - "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), ); - let Err(Error::InvalidToken { expected, found, span }) = res else { + let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else { panic!( "Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}" ); @@ -602,6 +611,6 @@ mod tests assert_eq!(found, '\''); assert_eq!(span.line, 1); assert_eq!(span.column, 13); - assert_eq!(span.file, Path::new("bar.glsl")); + assert_eq!(span.path, Path::new("bar.glsl")); } } diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs index 0b1bb8a..53e0120 100644 --- a/engine/src/opengl/mod.rs +++ b/engine/src/opengl/mod.rs @@ -1,16 +1,17 @@ use bitflags::bitflags; +use gl::types::GLint; use crate::data_types::dimens::Dimens; use crate::vector::Vec2; pub mod buffer; +pub mod glsl; pub mod shader; pub mod texture; pub mod vertex_array; mod util; -#[cfg(feature = "debug")] pub mod debug; pub fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) @@ -47,6 +48,17 @@ pub fn enable(capacity: Capability) } } +pub fn get_context_flags() -> ContextFlags +{ + let mut context_flags: GLint = 0; + + unsafe { + gl::GetIntegerv(gl::CONTEXT_FLAGS as u32, &mut context_flags); + } + + ContextFlags::from_bits_truncate(context_flags as u32) +} + bitflags! { #[derive(Debug, Clone, Copy)] pub struct BufferClearMask: u32 { @@ -105,3 +117,12 @@ impl From<crate::draw_flags::PolygonModeFace> for PolygonModeFace } } } + +bitflags! { +#[derive(Debug, Clone, Copy)] +pub struct ContextFlags: u32 { + const FORWARD_COMPATIBLE = gl::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT; + const DEBUG = gl::CONTEXT_FLAG_DEBUG_BIT; + const ROBUST_ACCESS = gl::CONTEXT_FLAG_ROBUST_ACCESS_BIT; +} +} diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs index 070897e..36dc1a4 100644 --- a/engine/src/opengl/shader.rs +++ b/engine/src/opengl/shader.rs @@ -2,7 +2,6 @@ use std::ffi::CStr; use std::ptr::null_mut; use crate::matrix::Matrix; -use crate::shader::Kind; use crate::vector::Vec3; #[derive(Debug)] @@ -20,7 +19,7 @@ impl Shader Self { shader } } - pub fn set_source(&self, source: &str) -> Result<(), Error> + pub fn set_source(&mut self, source: &str) -> Result<(), Error> { if !source.is_ascii() { return Err(Error::SourceNotAscii); @@ -39,7 +38,7 @@ impl Shader Ok(()) } - pub fn compile(&self) -> Result<(), Error> + pub fn compile(&mut self) -> Result<(), Error> { unsafe { gl::CompileShader(self.shader); @@ -90,6 +89,14 @@ impl Drop for Shader } } +/// Shader kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Kind +{ + Vertex, + Fragment, +} + impl Kind { fn into_gl(self) -> gl::types::GLenum @@ -117,14 +124,14 @@ impl Program Self { program } } - pub fn attach(&self, shader: &Shader) + pub fn attach(&mut self, shader: &Shader) { unsafe { gl::AttachShader(self.program, shader.shader); } } - pub fn link(&self) -> Result<(), Error> + pub fn link(&mut self) -> Result<(), Error> { unsafe { gl::LinkProgram(self.program); diff --git a/engine/src/opengl/texture.rs b/engine/src/opengl/texture.rs index 074ade7..52c8554 100644 --- a/engine/src/opengl/texture.rs +++ b/engine/src/opengl/texture.rs @@ -1,5 +1,5 @@ use crate::data_types::dimens::Dimens; -use crate::texture::{Id, Properties}; +use crate::texture::Properties; #[derive(Debug)] pub struct Texture @@ -224,8 +224,8 @@ macro_rules! texture_unit_enum { } } - pub fn from_texture_id(texture_id: Id) -> Option<Self> { - match texture_id.into_inner() { + pub fn from_num(num: usize) -> Option<Self> { + match num { #( N => Some(Self::No~N), )* diff --git a/engine/src/opengl/vertex_array.rs b/engine/src/opengl/vertex_array.rs index da5d91e..e1e1a15 100644 --- a/engine/src/opengl/vertex_array.rs +++ b/engine/src/opengl/vertex_array.rs @@ -125,17 +125,6 @@ impl VertexArray { unsafe { gl::BindVertexArray(self.array) } } - - /// Does a weak clone of this vertex array. The vertex array itself is NOT copied in - /// any way this function only copies the internal vertex array ID. - /// - /// # Safety - /// The returned `VertexArray` must not be dropped if another `VertexArray` - /// referencing the same vertex array ID is used later. - pub unsafe fn clone_unsafe(&self) -> Self - { - Self { array: self.array } - } } impl Drop for VertexArray diff --git a/engine/src/performance.rs b/engine/src/performance.rs deleted file mode 100644 index ffc5c27..0000000 --- a/engine/src/performance.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::time::Instant; - -use ecs::component::local::Local; -use ecs::system::{Into, System}; -use ecs::Component; - -use crate::event::PostPresent as PostPresentEvent; - -#[derive(Debug, Default)] -#[non_exhaustive] -pub struct Extension {} - -impl ecs::extension::Extension for Extension -{ - fn collect(self, mut collector: ecs::extension::Collector<'_>) - { - collector.add_system( - PostPresentEvent, - log_perf.into_system().initialize((State::default(),)), - ); - } -} - -#[cfg(feature = "debug")] -macro_rules! log_perf { - ($($tt: tt)*) => { - tracing::info!($($tt)*); - }; -} - -#[cfg(not(feature = "debug"))] -macro_rules! log_perf { - ($($tt: tt)*) => { - println!($($tt)*); - }; -} - -fn log_perf(mut state: Local<State>) -{ - let Some(last_time) = state.last_time else { - state.last_time = Some(Instant::now()); - return; - }; - - let time_now = Instant::now(); - - state.last_time = Some(time_now); - - log_perf!( - "Frame time: {}us", - time_now.duration_since(last_time).as_micros() - ); -} - -#[derive(Debug, Default, Component)] -struct State -{ - last_time: Option<Instant>, -} diff --git a/engine/src/projection.rs b/engine/src/projection.rs index aa84a9f..faa741f 100644 --- a/engine/src/projection.rs +++ b/engine/src/projection.rs @@ -1,10 +1,14 @@ +use crate::data_types::dimens::Dimens3; use crate::matrix::Matrix; +use crate::util::builder; +use crate::vector::Vec3; #[derive(Debug)] #[non_exhaustive] pub enum Projection { Perspective(Perspective), + Orthographic(Orthographic), } /// Perspective projection parameters. @@ -16,6 +20,29 @@ pub struct Perspective pub near: f32, } +impl Perspective +{ + /// Creates a perspective projection matrix using right-handed coordinates. + #[inline] + pub fn to_matrix_rh(&self, aspect: f32, clip_volume: ClipVolume) + -> Matrix<f32, 4, 4> + { + let mut out = Matrix::new(); + + match clip_volume { + ClipVolume::NegOneToOne => { + out.set_cell(0, 0, (1.0 / (self.fov_radians / 2.0).tan()) / aspect); + out.set_cell(1, 1, 1.0 / (self.fov_radians / 2.0).tan()); + out.set_cell(2, 2, (self.near + self.far) / (self.near - self.far)); + out.set_cell(2, 3, (2.0 * self.near * self.far) / (self.near - self.far)); + out.set_cell(3, 2, -1.0); + } + } + + out + } +} + impl Default for Perspective { fn default() -> Self @@ -28,30 +55,83 @@ impl Default for Perspective } } -pub(crate) fn new_perspective_matrix( - perspective: &Perspective, - aspect: f32, -) -> Matrix<f32, 4, 4> +builder! { +#[builder(name = OrthographicBuilder, derives=(Debug, Clone))] +#[derive(Debug, Clone, PartialEq, PartialOrd)] +#[non_exhaustive] +pub struct Orthographic { - let mut out = Matrix::new(); + pub size: Dimens3<f32>, +} +} - out.set_cell(0, 0, (1.0 / (perspective.fov_radians / 2.0).tan()) / aspect); +impl Orthographic +{ + pub fn builder() -> OrthographicBuilder + { + OrthographicBuilder::default() + } - out.set_cell(1, 1, 1.0 / (perspective.fov_radians / 2.0).tan()); + /// Creates a orthographic projection matrix using right-handed coordinates. + pub fn to_matrix_rh( + &self, + center_pos: &Vec3<f32>, + clip_volume: ClipVolume, + ) -> Matrix<f32, 4, 4> + { + let mut result = Matrix::<f32, 4, 4>::new(); - out.set_cell( - 2, - 2, - (perspective.near + perspective.far) / (perspective.near - perspective.far), - ); + let left = center_pos.x - (self.size.width / 2.0); + let right = center_pos.x + (self.size.width / 2.0); + let bottom = center_pos.y - (self.size.height / 2.0); + let top = center_pos.y + (self.size.height / 2.0); + let near = center_pos.z - (self.size.depth / 2.0); + let far = center_pos.z + (self.size.depth / 2.0); - out.set_cell( - 2, - 3, - (2.0 * perspective.near * perspective.far) / (perspective.near - perspective.far), - ); + match clip_volume { + ClipVolume::NegOneToOne => { + result.set_cell(0, 0, 2.0 / (right - left)); + result.set_cell(1, 1, 2.0 / (top - bottom)); + result.set_cell(2, 2, -2.0 / (far - near)); + result.set_cell(0, 3, -(right + left) / (right - left)); + result.set_cell(1, 3, -(top + bottom) / (top - bottom)); + result.set_cell(2, 3, -(far + near) / (far - near)); + result.set_cell(3, 3, 1.0); + } + } - out.set_cell(3, 2, -1.0); + result + } +} - out +impl Default for Orthographic +{ + fn default() -> Self + { + Self { + size: Dimens3 { + width: 10.0, + height: 7.0, + depth: 10.0, + }, + } + } +} + +impl Default for OrthographicBuilder +{ + fn default() -> Self + { + let orthographic = Orthographic::default(); + + OrthographicBuilder { size: orthographic.size } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum ClipVolume +{ + /// -1 to +1. This is the OpenGL clip volume definition. + NegOneToOne, } diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index a353c6a..c44a479 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -2,11 +2,14 @@ use std::collections::HashMap; use std::ffi::{c_void, CString}; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::ops::Deref; +use std::path::Path; use std::process::abort; use ecs::actions::Actions; use ecs::component::local::Local; +use ecs::phase::{PRESENT as PRESENT_PHASE, START as START_PHASE}; use ecs::query::options::{Not, With}; use ecs::sole::Single; use ecs::system::{Into as _, System}; @@ -16,16 +19,27 @@ use crate::camera::{Active as ActiveCamera, Camera}; use crate::color::Color; use crate::data_types::dimens::Dimens; use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; -use crate::event::{Present as PresentEvent, Start as StartEvent}; use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; use crate::material::{Flags as MaterialFlags, Material}; use crate::matrix::Matrix; use crate::mesh::Mesh; use crate::opengl::buffer::{Buffer, Usage as BufferUsage}; -#[cfg(feature = "debug")] -use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType}; +use crate::opengl::debug::{ + enable_debug_output, + set_debug_message_callback, + set_debug_message_control, + MessageIdsAction, + MessageSeverity, + MessageSource, + MessageType, +}; +use crate::opengl::glsl::{ + preprocess as glsl_preprocess, + PreprocessingError as GlslPreprocessingError, +}; use crate::opengl::shader::{ Error as GlShaderError, + Kind as ShaderKind, Program as GlShaderProgram, Shader as GlShader, }; @@ -39,38 +53,44 @@ use crate::opengl::vertex_array::{ PrimitiveKind, VertexArray, }; -use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability}; -use crate::projection::{new_perspective_matrix, Projection}; -use crate::shader::Program as ShaderProgram; +use crate::opengl::{ + clear_buffers, + enable, + get_context_flags as get_opengl_context_flags, + BufferClearMask, + Capability, + ContextFlags, +}; +use crate::projection::{ClipVolume, Projection}; use crate::texture::{Id as TextureId, Texture}; use crate::transform::{Position, Scale}; -use crate::util::NeverDrop; +use crate::util::{defer, Defer, RefOrValue}; use crate::vector::{Vec2, Vec3}; use crate::vertex::{AttributeComponentType, Vertex}; use crate::window::Window; -type RenderableEntity = ( - Mesh, - ShaderProgram, - Material, - Option<MaterialFlags>, - Option<Position>, - Option<Scale>, - Option<DrawFlags>, - Option<GlObjects>, +type RenderableEntity<'a> = ( + &'a Mesh, + &'a Material, + &'a Option<MaterialFlags>, + &'a Option<Position>, + &'a Option<Scale>, + &'a Option<DrawFlags>, + &'a Option<GlObjects>, ); #[derive(Debug, Default)] +#[non_exhaustive] pub struct Extension {} impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { - collector.add_system(StartEvent, initialize); + collector.add_system(*START_PHASE, initialize); collector.add_system( - PresentEvent, + *PRESENT_PHASE, render .into_system() .initialize((GlobalGlObjects::default(),)), @@ -95,8 +115,9 @@ fn initialize(window: Single<Window>) } }); - #[cfg(feature = "debug")] - initialize_debug(); + if get_opengl_context_flags().contains(ContextFlags::DEBUG) { + initialize_debug(); + } let window_size = window.size().expect("Failed to get window size"); @@ -112,10 +133,10 @@ fn initialize(window: Single<Window>) #[allow(clippy::too_many_arguments)] fn render( - query: Query<RenderableEntity, Not<With<NoDraw>>>, - point_light_query: Query<(PointLight,)>, - directional_lights: Query<(DirectionalLight,)>, - camera_query: Query<(Camera, Position, ActiveCamera)>, + query: Query<RenderableEntity<'_>, Not<With<NoDraw>>>, + point_light_query: Query<(&PointLight,)>, + directional_lights: Query<(&DirectionalLight,)>, + camera_query: Query<(&Camera, &Position, &ActiveCamera)>, window: Single<Window>, global_light: Single<GlobalLight>, mut gl_objects: Local<GlobalGlObjects>, @@ -123,7 +144,6 @@ fn render( ) { let Some((camera, camera_pos, _)) = camera_query.iter().next() else { - #[cfg(feature = "debug")] tracing::warn!("No current camera. Nothing will be rendered"); return; }; @@ -136,52 +156,35 @@ fn render( let directional_lights = directional_lights.iter().collect::<Vec<_>>(); let GlobalGlObjects { - shader_programs: gl_shader_programs, + shader_program, textures: gl_textures, } = &mut *gl_objects; + let shader_program = + shader_program.get_or_insert_with(|| create_default_shader_program().unwrap()); + clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); for ( - entity_index, - ( - mesh, - shader_program, - material, - material_flags, - position, - scale, - draw_flags, - gl_objects, - ), - ) in query.iter().enumerate() + euid, + (mesh, material, material_flags, position, scale, draw_flags, gl_objects), + ) in query.iter_with_euids() { let material_flags = material_flags .map(|material_flags| material_flags.clone()) .unwrap_or_default(); - let shader_program = gl_shader_programs - .entry(shader_program.u64_hash()) - .or_insert_with(|| create_gl_shader_program(&shader_program).unwrap()); - - let new_gl_objects; - - let gl_objects = if let Some(gl_objects) = gl_objects.as_deref() { - gl_objects - } else { - // TODO: Account for when meshes are changed - let gl_objects = GlObjects::new(&mesh); - - new_gl_objects = Some(gl_objects.clone()); - - actions.add_components( - query.get_entity_uid(entity_index).unwrap(), - (gl_objects,), - ); - - &*new_gl_objects.unwrap() + let gl_objs = match gl_objects.as_deref() { + Some(gl_objs) => RefOrValue::Ref(gl_objs), + None => RefOrValue::Value(Some(GlObjects::new(&mesh))), }; + defer!(|gl_objs| { + if let RefOrValue::Value(opt_gl_objs) = gl_objs { + actions.add_components(euid, (opt_gl_objs.take().unwrap(),)); + }; + }); + apply_transformation_matrices( Transformation { position: position.map(|pos| *pos).unwrap_or_default().position, @@ -207,15 +210,12 @@ fn render( &camera_pos, ); - for texture in &material.textures { + for (index, texture) in material.textures.iter().enumerate() { let gl_texture = gl_textures .entry(texture.id()) .or_insert_with(|| create_gl_texture(texture)); - let texture_unit = - TextureUnit::from_texture_id(texture.id()).unwrap_or_else(|| { - panic!("Texture id {} is a invalid texture unit", texture.id()); - }); + let texture_unit = TextureUnit::from_num(index).expect("Too many textures"); set_active_texture_unit(texture_unit); @@ -231,7 +231,7 @@ fn render( ); } - draw_mesh(gl_objects); + draw_mesh(gl_objs.get().unwrap()); if draw_flags.is_some() { let default_polygon_mode_config = PolygonModeConfig::default(); @@ -247,7 +247,7 @@ fn render( #[derive(Debug, Default, Component)] struct GlobalGlObjects { - shader_programs: HashMap<u64, GlShaderProgram>, + shader_program: Option<GlShaderProgram>, textures: HashMap<TextureId, GlTexture>, } @@ -256,20 +256,11 @@ fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) crate::opengl::set_viewport(position, size); } -#[cfg(feature = "debug")] fn initialize_debug() { - use crate::opengl::debug::{ - enable_debug_output, - set_debug_message_callback, - set_debug_message_control, - MessageIdsAction, - }; - enable_debug_output(); set_debug_message_callback(opengl_debug_message_cb); - set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); } @@ -278,9 +269,9 @@ fn draw_mesh(gl_objects: &GlObjects) gl_objects.vertex_arr.bind(); if gl_objects.index_buffer.is_some() { - VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.index_cnt); + VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); } else { - VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, 3); + VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); } } @@ -299,51 +290,84 @@ fn create_gl_texture(texture: &Texture) -> GlTexture gl_texture } -fn create_gl_shader_program( - shader_program: &ShaderProgram, -) -> Result<GlShaderProgram, GlShaderError> +const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl"); +const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl"); + +const VERTEX_DATA_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex_data.glsl"); +const LIGHT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/light.glsl"); + +fn create_default_shader_program() -> Result<GlShaderProgram, CreateShaderError> { - let gl_shaders = shader_program - .shaders() - .iter() - .map(|shader| { - let gl_shader = GlShader::new(shader.kind()); + let mut vertex_shader = GlShader::new(ShaderKind::Vertex); - gl_shader.set_source(shader.source())?; - gl_shader.compile()?; + vertex_shader.set_source(&*glsl_preprocess( + VERTEX_GLSL_SHADER_SRC, + &get_glsl_shader_content, + )?)?; - Ok(gl_shader) - }) - .collect::<Result<Vec<_>, _>>()?; + vertex_shader.compile()?; - let gl_shader_program = GlShaderProgram::new(); + let mut fragment_shader = GlShader::new(ShaderKind::Fragment); - for gl_shader in &gl_shaders { - gl_shader_program.attach(gl_shader); - } + fragment_shader.set_source(&*glsl_preprocess( + FRAGMENT_GLSL_SHADER_SRC, + &get_glsl_shader_content, + )?)?; + + fragment_shader.compile()?; + + let mut gl_shader_program = GlShaderProgram::new(); + + gl_shader_program.attach(&vertex_shader); + gl_shader_program.attach(&fragment_shader); gl_shader_program.link()?; Ok(gl_shader_program) } +#[derive(Debug, thiserror::Error)] +enum CreateShaderError +{ + #[error(transparent)] + ShaderError(#[from] GlShaderError), + + #[error(transparent)] + PreprocessingError(#[from] GlslPreprocessingError), +} + +fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error> +{ + if path == Path::new("vertex_data.glsl") { + return Ok(VERTEX_DATA_GLSL_SHADER_SRC.as_bytes().to_vec()); + } + + if path == Path::new("light.glsl") { + return Ok(LIGHT_GLSL_SHADER_SRC.as_bytes().to_vec()); + } + + Err(IoError::new( + IoErrorKind::NotFound, + format!("Content for shader file {} not found", path.display()), + )) +} + #[derive(Debug, Component)] struct GlObjects { /// Vertex and index buffer has to live as long as the vertex array - vertex_buffer: Buffer<Vertex>, + _vertex_buffer: Buffer<Vertex>, index_buffer: Option<Buffer<u32>>, - index_cnt: u32, + element_cnt: u32, vertex_arr: VertexArray, } impl GlObjects { - #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] + #[tracing::instrument(skip_all)] fn new(mesh: &Mesh) -> Self { - #[cfg(feature = "debug")] tracing::trace!( "Creating vertex array, vertex buffer{}", if mesh.indices().is_some() { @@ -387,37 +411,27 @@ impl GlObjects vertex_arr.bind_element_buffer(&index_buffer); return Self { - vertex_buffer, + _vertex_buffer: vertex_buffer, index_buffer: Some(index_buffer), - index_cnt: indices.len().try_into().unwrap(), + element_cnt: indices + .len() + .try_into() + .expect("Mesh index count does not fit into a 32-bit unsigned int"), vertex_arr, }; } Self { - vertex_buffer, + _vertex_buffer: vertex_buffer, index_buffer: None, - index_cnt: 0, + element_cnt: mesh + .vertices() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), vertex_arr, } } - - pub fn clone(&self) -> NeverDrop<Self> - { - NeverDrop::new(Self { - // SAFETY: The vertex buffer will never become dropped (NeverDrop ensures it) - vertex_buffer: unsafe { self.vertex_buffer.clone_weak() }, - index_buffer: self - .index_buffer - .as_ref() - // SAFETY: The index buffer will never become dropped (NeverDrop ensures - // it) - .map(|index_buffer| unsafe { index_buffer.clone_weak() }), - index_cnt: self.index_cnt, - // SAFETY: The vertex array will never become dropped (NeverDrop ensures it) - vertex_arr: unsafe { self.vertex_arr.clone_unsafe() }, - }) - } } fn apply_transformation_matrices( @@ -431,19 +445,22 @@ fn apply_transformation_matrices( gl_shader_program .set_uniform_matrix_4fv(c"model", &create_transformation_matrix(transformation)); - let view = create_view(camera, camera_pos); + let view_matrix = create_view_matrix(camera, &camera_pos.position); - gl_shader_program.set_uniform_matrix_4fv(c"view", &view); + gl_shader_program.set_uniform_matrix_4fv(c"view", &view_matrix); #[allow(clippy::cast_precision_loss)] - let projection = match &camera.projection { - Projection::Perspective(perspective) => new_perspective_matrix( - perspective, + let proj_matrix = match &camera.projection { + Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh( window_size.width as f32 / window_size.height as f32, + ClipVolume::NegOneToOne, ), + Projection::Orthographic(orthographic_proj) => { + orthographic_proj.to_matrix_rh(&camera_pos.position, ClipVolume::NegOneToOne) + } }; - gl_shader_program.set_uniform_matrix_4fv(c"projection", &projection); + gl_shader_program.set_uniform_matrix_4fv(c"projection", &proj_matrix); } fn apply_light<PointLightHolder>( @@ -532,22 +549,29 @@ fn apply_light<PointLightHolder>( gl_shader_program .set_uniform_vec_3fv(c"material.specular", &material.specular.clone().into()); + let texture_map = material + .textures + .iter() + .enumerate() + .map(|(index, texture)| (texture.id(), index)) + .collect::<HashMap<_, _>>(); + #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform_1i( c"material.ambient_map", - material.ambient_map.into_inner() as i32, + *texture_map.get(&material.ambient_map).unwrap() as i32, ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform_1i( c"material.diffuse_map", - material.diffuse_map.into_inner() as i32, + *texture_map.get(&material.diffuse_map).unwrap() as i32, ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform_1i( c"material.specular_map", - material.specular_map.into_inner() as i32, + *texture_map.get(&material.specular_map).unwrap() as i32, ); gl_shader_program.set_uniform_1fv(c"material.shininess", material.shininess); @@ -649,16 +673,15 @@ fn create_light_uniform_name( } } -fn create_view(camera: &Camera, camera_pos: &Position) -> Matrix<f32, 4, 4> +fn create_view_matrix(camera: &Camera, camera_pos: &Vec3<f32>) -> Matrix<f32, 4, 4> { let mut view = Matrix::new(); - view.look_at(&camera_pos.position, &camera.target, &camera.global_up); + view.look_at(&camera_pos, &camera.target, &camera.global_up); view } -#[cfg(feature = "debug")] #[tracing::instrument(skip_all)] fn opengl_debug_message_cb( source: MessageSource, diff --git a/engine/src/renderer/opengl/glsl/fragment.glsl b/engine/src/renderer/opengl/glsl/fragment.glsl new file mode 100644 index 0000000..5bf5ff2 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/fragment.glsl @@ -0,0 +1,73 @@ +#version 330 core + +#preinclude "light.glsl" +#preinclude "vertex_data.glsl" + +#define MAX_LIGHT_CNT 64 + +out vec4 FragColor; + +in VertexData vertex_data; + +uniform vec3 view_pos; +uniform sampler2D input_texture; +uniform Material material; + +uniform PointLight point_lights[MAX_LIGHT_CNT]; +uniform int point_light_cnt; + +uniform DirectionalLight directional_lights[MAX_LIGHT_CNT]; +uniform int directional_light_cnt; + +void main() +{ + vec3 ambient_light = calc_ambient_light(material, vertex_data.texture_coords); + + vec3 directional_light_sum = vec3(0.0, 0.0, 0.0); + + for (int dl_index = 0; dl_index < directional_light_cnt; dl_index++) { + CalculatedLight calculated_dir_light; + + calc_light( + // Negated since we want the light to point from the light direction + normalize(-directional_lights[dl_index].direction), + directional_lights[dl_index].phong, + vertex_data, + view_pos, + material, + calculated_dir_light + ); + + directional_light_sum += + calculated_dir_light.diffuse + calculated_dir_light.specular; + } + + vec3 point_light_sum = vec3(0.0, 0.0, 0.0); + + for (int pl_index = 0; pl_index < point_light_cnt; pl_index++) { + vec3 light_direction = + normalize(point_lights[pl_index].position - vertex_data.world_space_pos); + + CalculatedLight calculated_point_light; + + calc_light( + light_direction, + point_lights[pl_index].phong, + vertex_data, + view_pos, + material, + calculated_point_light + ); + + float attenuation = + calc_attenuation(point_lights[pl_index], vertex_data.world_space_pos); + + calculated_point_light.diffuse *= attenuation; + calculated_point_light.specular *= attenuation; + + point_light_sum += + calculated_point_light.diffuse + calculated_point_light.specular; + } + + FragColor = vec4((ambient_light + directional_light_sum + point_light_sum), 1.0); +} diff --git a/engine/src/renderer/opengl/glsl/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl new file mode 100644 index 0000000..f12b5fe --- /dev/null +++ b/engine/src/renderer/opengl/glsl/light.glsl @@ -0,0 +1,133 @@ +#version 330 core + +#ifndef LIGHT_GLSL +#define LIGHT_GLSL + +#preinclude "vertex_data.glsl" + +struct Material +{ + vec3 ambient; + vec3 diffuse; + vec3 specular; + sampler2D ambient_map; + sampler2D diffuse_map; + sampler2D specular_map; + float shininess; +}; + +struct LightPhong +{ + vec3 diffuse; + vec3 specular; +}; + +struct AttenuationProperties +{ + float constant; + float linear; + float quadratic; +}; + +struct PointLight +{ + LightPhong phong; + vec3 position; + AttenuationProperties attenuation_props; +}; + +struct DirectionalLight +{ + LightPhong phong; + vec3 direction; +}; + +struct CalculatedLight +{ + vec3 diffuse; + vec3 specular; +}; + +vec3 calc_ambient_light(in Material material, in vec2 texture_coords) +{ + return vec3(texture(material.ambient_map, texture_coords)) * material.ambient; +} + +vec3 calc_diffuse_light( + in Material material, + in LightPhong light_phong, + in vec3 light_dir, + in vec3 norm, + in vec2 texture_coords +) +{ + float diff = max(dot(norm, light_dir), 0.0); + + return light_phong.diffuse * ( + diff * (vec3(texture(material.diffuse_map, texture_coords)) * material.diffuse) + ); +} + +vec3 calc_specular_light( + in Material material, + in LightPhong light_phong, + in vec3 light_dir, + in vec3 norm, + in vec3 view_pos, + in vec3 frag_pos, + in vec2 texture_coords +) +{ + vec3 view_direction = normalize(view_pos - frag_pos); + + vec3 halfway_direction = normalize(light_dir + view_direction); + + float spec = + pow(max(dot(norm, halfway_direction), 0.0), material.shininess); + + return light_phong.specular * ( + spec * (vec3(texture(material.specular_map, texture_coords)) * material.specular) + ); +} + +float calc_attenuation(in PointLight point_light, in vec3 position) +{ + float light_distance = length(point_light.position - position); + + return 1.0 / (point_light.attenuation_props.constant + + point_light.attenuation_props.linear + * light_distance + point_light.attenuation_props.quadratic + * pow(light_distance, 2)); +} + +void calc_light( + in vec3 light_direction, + in LightPhong light_phong, + in VertexData vertex_data, + in vec3 view_pos, + in Material material, + out CalculatedLight calculated_light +) +{ + vec3 norm = normalize(vertex_data.world_space_normal); + + calculated_light.diffuse = calc_diffuse_light( + material, + light_phong, + light_direction, + norm, + vertex_data.texture_coords + ); + + calculated_light.specular = calc_specular_light( + material, + light_phong, + light_direction, + norm, + view_pos, + vertex_data.world_space_pos, + vertex_data.texture_coords + ); +} + +#endif diff --git a/engine/src/renderer/opengl/glsl/vertex.glsl b/engine/src/renderer/opengl/glsl/vertex.glsl new file mode 100644 index 0000000..b57caa6 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/vertex.glsl @@ -0,0 +1,24 @@ +#version 330 core + +#preinclude "vertex_data.glsl" + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texture_coords; +layout (location = 2) in vec3 normal; + +out VertexData vertex_data; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + gl_Position = projection * view * model * vec4(pos, 1.0); + + vertex_data.world_space_pos = vec3(model * vec4(pos, 1.0)); + vertex_data.texture_coords = texture_coords; + + // TODO: Do this using CPU for performance increase + vertex_data.world_space_normal = mat3(transpose(inverse(model))) * normal; +} diff --git a/engine/src/renderer/opengl/glsl/vertex_data.glsl b/engine/src/renderer/opengl/glsl/vertex_data.glsl new file mode 100644 index 0000000..486d445 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/vertex_data.glsl @@ -0,0 +1,11 @@ +#ifndef VERTEX_DATA_GLSL +#define VERTEX_DATA_GLSL + +struct VertexData +{ + vec2 texture_coords; + vec3 world_space_pos; + vec3 world_space_normal; +}; + +#endif diff --git a/engine/src/shader.rs b/engine/src/shader.rs deleted file mode 100644 index 89f7b7c..0000000 --- a/engine/src/shader.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::collections::hash_map::DefaultHasher; -use std::fs::read_to_string; -use std::hash::{Hash, Hasher}; -use std::path::{Path, PathBuf}; - -use ecs::Component; - -use crate::shader_preprocessor::{Error as ShaderPreprocessorError, ShaderPreprocessor}; - -const VERTEX_SHADER_FILE: &str = "vertex.glsl"; -const FRAGMENT_SHADER_FILE: &str = "fragment.glsl"; - -const SHADER_DIR: &str = "engine"; - -/// Shader program -#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Component)] -pub struct Program -{ - shaders: Vec<Shader>, -} - -impl Program -{ - /// Creates a new shader program with the default shaders. - /// - /// # Errors - /// Returns `Err` if: - /// - Reading a default shader file Fails - /// - Preprocessing a shader fails. - pub fn new() -> Result<Self, Error> - { - let mut program = Self { shaders: Vec::new() }; - - program.push_shader( - Shader::read_shader_file( - Kind::Vertex, - &Path::new(SHADER_DIR).join(VERTEX_SHADER_FILE), - )? - .preprocess()?, - ); - - program.push_shader( - Shader::read_shader_file( - Kind::Fragment, - &Path::new(SHADER_DIR).join(FRAGMENT_SHADER_FILE), - )? - .preprocess()?, - ); - - Ok(program) - } - - pub fn push_shader(&mut self, shader: Shader) - { - self.shaders.push(shader); - } - - pub fn append_shaders(&mut self, shaders: impl IntoIterator<Item = Shader>) - { - self.shaders.extend(shaders); - } - - #[must_use] - pub fn shaders(&self) -> &[Shader] - { - &self.shaders - } - - pub(crate) fn u64_hash(&self) -> u64 - { - let mut hasher = DefaultHasher::new(); - - self.hash(&mut hasher); - - hasher.finish() - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct Shader -{ - kind: Kind, - source: String, - file: PathBuf, -} - -impl Shader -{ - /// Reads a shader from the specified source file. - /// - /// # Errors - /// Will return `Err` if: - /// - Reading the file fails - /// - The shader source is not ASCII - pub fn read_shader_file(kind: Kind, shader_file: &Path) -> Result<Self, Error> - { - let source = read_to_string(shader_file).map_err(|err| Error::ReadFailed { - source: err, - shader_file: shader_file.to_path_buf(), - })?; - - if !source.is_ascii() { - return Err(Error::SourceNotAscii); - } - - Ok(Self { - kind, - source, - file: shader_file.to_path_buf(), - }) - } - - /// Preprocesses the shaders. - /// - /// # Errors - /// Returns `Err` if preprocessing fails. - pub fn preprocess(self) -> Result<Self, Error> - { - let shader_preprocessor = ShaderPreprocessor::new( - self.file - .parent() - .ok_or(Error::SourcePathHasNoParent)? - .to_path_buf(), - ); - - let source_preprocessed = shader_preprocessor - .preprocess(self.source, &self.file) - .map_err(|err| Error::PreprocessFailed { - source: err, - shader_file: self.file.clone(), - })?; - - Ok(Self { - kind: self.kind, - source: source_preprocessed, - file: self.file.clone(), - }) - } - - #[must_use] - pub fn kind(&self) -> Kind - { - self.kind - } - - #[must_use] - pub fn source(&self) -> &str - { - &self.source - } -} - -/// Shader kind. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Kind -{ - Vertex, - Fragment, -} - -/// Shader error -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("Failed to read shader {}", shader_file.display())] - ReadFailed - { - #[source] - source: std::io::Error, - shader_file: PathBuf, - }, - - #[error("Shader source is not ASCII")] - SourceNotAscii, - - #[error("Failed to preprocess shader {}", shader_file.display())] - PreprocessFailed - { - #[source] - source: ShaderPreprocessorError, - shader_file: PathBuf, - }, - - #[error("Shader source path has no parent")] - SourcePathHasNoParent, -} diff --git a/engine/src/texture.rs b/engine/src/texture.rs index f82b59d..4a4fe86 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -8,6 +8,7 @@ use image::{DynamicImage, ImageError, Rgb, RgbImage}; use crate::color::Color; use crate::data_types::dimens::Dimens; use crate::opengl::texture::PixelDataFormat; +use crate::util::builder; static NEXT_ID: AtomicU32 = AtomicU32::new(0); @@ -30,62 +31,26 @@ pub struct Texture impl Texture { + pub fn builder() -> Builder + { + Builder::default() + } + /// Opens a texture image. /// /// # Errors /// Will return `Err` if: /// - Opening the image fails /// - The image data is not 8-bit/color RGB - #[allow(clippy::new_without_default)] pub fn open(path: &Path) -> Result<Self, Error> { - let image = ImageReader::open(path) - .map_err(Error::OpenImageFailed)? - .decode() - .map_err(Error::DecodeImageFailed)?; - - let pixel_data_format = match &image { - DynamicImage::ImageRgb8(_) => PixelDataFormat::Rgb8, - DynamicImage::ImageRgba8(_) => PixelDataFormat::Rgba8, - _ => { - return Err(Error::UnsupportedImageDataKind); - } - }; - - let dimensions = Dimens { - width: image.width(), - height: image.height(), - }; - - let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - - Ok(Self { - id: Id::new(id), - image, - pixel_data_format, - dimensions, - properties: Properties::default(), - }) + Self::builder().open(path) } #[must_use] pub fn new_from_color(dimensions: &Dimens<u32>, color: &Color<u8>) -> Self { - let image = RgbImage::from_pixel( - dimensions.width, - dimensions.height, - Rgb([color.red, color.green, color.blue]), - ); - - let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - - Self { - id: Id::new(id), - image: image.into(), - pixel_data_format: PixelDataFormat::Rgb8, - dimensions: *dimensions, - properties: Properties::default(), - } + Self::builder().build_with_single_color(dimensions, color) } #[must_use] @@ -132,6 +97,84 @@ impl Drop for Texture } } +/// Texture builder. +#[derive(Debug, Default, Clone)] +pub struct Builder +{ + properties: Properties, +} + +impl Builder +{ + pub fn properties(mut self, properties: Properties) -> Self + { + self.properties = properties; + self + } + + /// Opens a image as a texture. + /// + /// # Errors + /// Will return `Err` if: + /// - Opening the image fails + /// - Decoding the image fails + /// - The image data is in a unsupported format + pub fn open(&self, path: &(impl AsRef<Path> + ?Sized)) -> Result<Texture, Error> + { + let image = ImageReader::open(path) + .map_err(Error::OpenImageFailed)? + .decode() + .map_err(Error::DecodeImageFailed)?; + + let pixel_data_format = match &image { + DynamicImage::ImageRgb8(_) => PixelDataFormat::Rgb8, + DynamicImage::ImageRgba8(_) => PixelDataFormat::Rgba8, + _ => { + return Err(Error::UnsupportedImageDataFormat); + } + }; + + let dimensions = Dimens { + width: image.width(), + height: image.height(), + }; + + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + + Ok(Texture { + id: Id::new(id), + image, + pixel_data_format, + dimensions, + properties: self.properties.clone(), + }) + } + + #[must_use] + pub fn build_with_single_color( + &self, + dimensions: &Dimens<u32>, + color: &Color<u8>, + ) -> Texture + { + let image = RgbImage::from_pixel( + dimensions.width, + dimensions.height, + Rgb([color.red, color.green, color.blue]), + ); + + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + + Texture { + id: Id::new(id), + image: image.into(), + pixel_data_format: PixelDataFormat::Rgb8, + dimensions: *dimensions, + properties: self.properties.clone(), + } + } +} + /// Texture error. #[derive(Debug, thiserror::Error)] pub enum Error @@ -142,11 +185,13 @@ pub enum Error #[error("Failed to decode texture image")] DecodeImageFailed(#[source] ImageError), - #[error("Unsupported image data kind")] - UnsupportedImageDataKind, + #[error("Unsupported image data format")] + UnsupportedImageDataFormat, } +builder! { /// Texture properties +#[builder(name = PropertiesBuilder, derives=(Debug, Clone))] #[derive(Debug, Clone)] #[non_exhaustive] pub struct Properties @@ -155,6 +200,15 @@ pub struct Properties pub magnifying_filter: Filtering, pub minifying_filter: Filtering, } +} + +impl Properties +{ + pub fn builder() -> PropertiesBuilder + { + PropertiesBuilder::default() + } +} impl Default for Properties { @@ -168,6 +222,14 @@ impl Default for Properties } } +impl Default for PropertiesBuilder +{ + fn default() -> Self + { + Properties::default().into() + } +} + /// Texture ID. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id @@ -181,11 +243,6 @@ impl Id { Self { id } } - - pub(crate) fn into_inner(self) -> u32 - { - self.id - } } impl Display for Id diff --git a/engine/src/util.rs b/engine/src/util.rs index a505a38..0f6c78c 100644 --- a/engine/src/util.rs +++ b/engine/src/util.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + macro_rules! try_option { ($expr: expr) => { match $expr { @@ -9,9 +11,6 @@ macro_rules! try_option { }; } -use std::mem::ManuallyDrop; -use std::ops::{Deref, DerefMut}; - pub(crate) use try_option; macro_rules! or { @@ -97,36 +96,74 @@ macro_rules! builder { pub(crate) use builder; -/// Wrapper that ensures the contained value will never be dropped. -#[derive(Debug)] -pub struct NeverDrop<Value> +pub enum RefOrValue<'a, T> { - value: ManuallyDrop<Value>, + Ref(&'a T), + Value(Option<T>), } -impl<Value> NeverDrop<Value> +impl<'a, T> RefOrValue<'a, T> { - #[must_use] - pub fn new(value: Value) -> Self + pub fn get(&self) -> Option<&T> { - Self { value: ManuallyDrop::new(value) } + match self { + Self::Ref(val_ref) => Some(val_ref), + Self::Value(val_cell) => val_cell.as_ref(), + } } } -impl<Value> Deref for NeverDrop<Value> +#[derive(Debug)] +pub struct Defer<'func, Func, Data> +where + Func: FnMut(&mut Data) + 'func, { - type Target = Value; + func: Func, + pub data: Data, + _pd: PhantomData<&'func ()>, +} - fn deref(&self) -> &Self::Target +impl<'func, Func, Data> Defer<'func, Func, Data> +where + Func: FnMut(&mut Data) + 'func, +{ + pub fn new(data: Data, func: Func) -> Self { - &self.value + Self { func, data, _pd: PhantomData } } } -impl<Value> DerefMut for NeverDrop<Value> +impl<'func, Func, Data> Drop for Defer<'func, Func, Data> +where + Func: FnMut(&mut Data) + 'func, { - fn deref_mut(&mut self) -> &mut Self::Target + fn drop(&mut self) { - &mut self.value + (self.func)(&mut self.data) } } + +/// Defines a function that will be called at the end of the current scope. +/// +/// Only captured variables that are later mutably borrowed needs to specified as +/// captures. +macro_rules! defer { + (|$capture: ident| {$($tt: tt)*}) => { + // This uses the automatic temporary lifetime extension behaviour introduced + // in Rust 1.79.0 (https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html) to + // create a unnamable variable for the Defer struct. The variable should be + // unnamable so that it cannot be missused and so that this macro can be used + // multiple times without having to give it a identifier for the Defer struct + // variable + let Defer { data: $capture, .. } = if true { + &Defer::new($capture, |$capture| { + $($tt)* + }) + } + else { + unreachable!(); + }; + }; +} + +pub(crate) use defer; diff --git a/engine/src/vertex.rs b/engine/src/vertex.rs index 897ee97..30640c4 100644 --- a/engine/src/vertex.rs +++ b/engine/src/vertex.rs @@ -5,13 +5,14 @@ use crate::vector::{Vec2, Vec3}; builder! { #[builder(name = Builder, derives = (Debug, Default))] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] #[repr(C)] +#[non_exhaustive] pub struct Vertex { - pos: Vec3<f32>, - texture_coords: Vec2<f32>, - normal: Vec3<f32>, + pub pos: Vec3<f32>, + pub texture_coords: Vec2<f32>, + pub normal: Vec3<f32>, } } diff --git a/engine/src/window.rs b/engine/src/window.rs index ccc1b8d..00c360e 100644 --- a/engine/src/window.rs +++ b/engine/src/window.rs @@ -1,30 +1,24 @@ use std::borrow::Cow; use std::ffi::{CStr, CString}; +use bitflags::bitflags; use ecs::actions::Actions; use ecs::extension::Collector as ExtensionCollector; +use ecs::phase::{Phase, PRESENT as PRESENT_PHASE, START as START_PHASE}; +use ecs::relationship::{ChildOf, Relationship}; use ecs::sole::Single; -use ecs::Sole; +use ecs::{static_entity, Sole}; +use glfw::window::{Hint as WindowCreationHint, HintValue as WindowCreationHintValue}; use glfw::WindowSize; +use util_macros::VariantArr; use crate::data_types::dimens::Dimens; -use crate::event::{Conclude as ConcludeEvent, Start as StartEvent}; use crate::vector::Vec2; -mod reexports -{ - pub use glfw::window::{ - CursorMode, - Hint as CreationHint, - HintValue as CreationHintValue, - InputMode, - Key, - KeyModifiers, - KeyState, - }; -} - -pub use reexports::*; +static_entity!( + pub UPDATE_PHASE, + (Phase, <Relationship<ChildOf, Phase>>::new(*PRESENT_PHASE)) +); #[derive(Debug, Sole)] /// Has to be dropped last since it holds the OpenGL context. @@ -53,7 +47,9 @@ impl Window enabled: bool, ) -> Result<(), Error> { - Ok(self.inner.set_input_mode(input_mode, enabled)?) + Ok(self + .inner + .set_input_mode(input_mode.to_glfw_input_mode(), enabled)?) } /// Sets the cursor mode. @@ -62,7 +58,9 @@ impl Window /// If a platform error occurs. pub fn set_cursor_mode(&self, cursor_mode: CursorMode) -> Result<(), Error> { - Ok(self.inner.set_cursor_mode(cursor_mode)?) + Ok(self + .inner + .set_cursor_mode(cursor_mode.to_glfw_cursor_mode())?) } /// Returns whether or not the window should close. Will return true when the user has @@ -155,7 +153,19 @@ impl Window callback: impl Fn(Key, i32, KeyState, KeyModifiers) + 'static, ) { - self.inner.set_key_callback(callback); + self.inner + .set_key_callback(move |key, scancode, key_state, key_modifiers| { + let Some(key_state) = KeyState::from_glfw_key_state(key_state) else { + return; + }; + + callback( + Key::from_glfw_key(key), + scancode, + key_state, + KeyModifiers::from_bits_truncate(key_modifiers.bits()), + ) + }); } /// Sets the window's cursor position callback. @@ -165,6 +175,24 @@ impl Window .set_cursor_pos_callback(move |pos| callback(Vec2 { x: pos.x, y: pos.y })); } + /// Sets the window's mouse button callback. The given function is called when a mouse + /// button enters a new state. + pub fn set_mouse_button_callback( + &self, + callback: impl Fn(MouseButton, MouseButtonState, KeyModifiers) + 'static, + ) + { + self.inner.set_mouse_button_callback( + move |mouse_button, mouse_button_state, key_modifiers| { + callback( + MouseButton::from_glfw_mouse_button(mouse_button), + MouseButtonState::from_glfw_mouse_button_state(mouse_button_state), + KeyModifiers::from_bits_truncate(key_modifiers.bits()), + ) + }, + ); + } + /// Sets the window's close callback. pub fn set_close_callback(&self, callback: impl Fn() + 'static) { @@ -188,10 +216,26 @@ pub struct Builder impl Builder { - #[must_use] - pub fn creation_hint(mut self, hint: CreationHint, value: CreationHintValue) -> Self + /// Sets whether the OpenGL context should be created in debug mode, which may + /// provide additional error and diagnostic reporting functionality. + pub fn opengl_debug_context(mut self, enabled: bool) -> Self + { + self.inner = self.inner.hint( + WindowCreationHint::OpenGLDebugContext, + WindowCreationHintValue::Bool(enabled), + ); + + self + } + + /// Set the desired number of samples to use for multisampling. Zero disables + /// multisampling. + pub fn multisampling_sample_count(mut self, sample_count: u16) -> Self { - self.inner = self.inner.hint(hint, value); + self.inner = self.inner.hint( + WindowCreationHint::Samples, + WindowCreationHintValue::Number(sample_count as i32), + ); self } @@ -204,8 +248,8 @@ impl Builder pub fn create(&self, size: Dimens<u32>, title: &str) -> Result<Window, Error> { let builder = self.inner.clone().hint( - CreationHint::OpenGLDebugContext, - CreationHintValue::Bool(cfg!(feature = "debug")), + WindowCreationHint::OpenGLDebugContext, + WindowCreationHintValue::Bool(true), ); let window = builder.create( @@ -220,6 +264,396 @@ impl Builder } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, VariantArr)] +#[variant_arr(name = KEYS)] +pub enum Key +{ + Space, + Apostrophe, + Comma, + Minus, + Period, + Slash, + Digit0, + Digit1, + Digit2, + Digit3, + Digit4, + Digit5, + Digit6, + Digit7, + Digit8, + Digit9, + Semicolon, + Equal, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + LeftBracket, + Backslash, + RightBracket, + GraveAccent, + World1, + World2, + Escape, + Enter, + Tab, + Backspace, + Insert, + Delete, + Right, + Left, + Down, + Up, + PageUp, + PageDown, + Home, + End, + CapsLock, + ScrollLock, + NumLock, + PrintScreen, + Pause, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + F25, + Kp0, + Kp1, + Kp2, + Kp3, + Kp4, + Kp5, + Kp6, + Kp7, + Kp8, + Kp9, + KpDecimal, + KpDivide, + KpMultiply, + KpSubtract, + KpAdd, + KpEnter, + KpEqual, + LeftShift, + LeftControl, + LeftAlt, + LeftSuper, + RightShift, + RightControl, + RightAlt, + RightSuper, + Menu, +} + +impl Key +{ + fn from_glfw_key(glfw_key: glfw::window::Key) -> Self + { + match glfw_key { + glfw::window::Key::Space => Self::Space, + glfw::window::Key::Apostrophe => Self::Apostrophe, + glfw::window::Key::Comma => Self::Comma, + glfw::window::Key::Minus => Self::Minus, + glfw::window::Key::Period => Self::Period, + glfw::window::Key::Slash => Self::Slash, + glfw::window::Key::Digit0 => Self::Digit0, + glfw::window::Key::Digit1 => Self::Digit1, + glfw::window::Key::Digit2 => Self::Digit2, + glfw::window::Key::Digit3 => Self::Digit3, + glfw::window::Key::Digit4 => Self::Digit4, + glfw::window::Key::Digit5 => Self::Digit5, + glfw::window::Key::Digit6 => Self::Digit6, + glfw::window::Key::Digit7 => Self::Digit7, + glfw::window::Key::Digit8 => Self::Digit8, + glfw::window::Key::Digit9 => Self::Digit9, + glfw::window::Key::Semicolon => Self::Semicolon, + glfw::window::Key::Equal => Self::Equal, + glfw::window::Key::A => Self::A, + glfw::window::Key::B => Self::B, + glfw::window::Key::C => Self::C, + glfw::window::Key::D => Self::D, + glfw::window::Key::E => Self::E, + glfw::window::Key::F => Self::F, + glfw::window::Key::G => Self::G, + glfw::window::Key::H => Self::H, + glfw::window::Key::I => Self::I, + glfw::window::Key::J => Self::J, + glfw::window::Key::K => Self::K, + glfw::window::Key::L => Self::L, + glfw::window::Key::M => Self::M, + glfw::window::Key::N => Self::N, + glfw::window::Key::O => Self::O, + glfw::window::Key::P => Self::P, + glfw::window::Key::Q => Self::Q, + glfw::window::Key::R => Self::R, + glfw::window::Key::S => Self::S, + glfw::window::Key::T => Self::T, + glfw::window::Key::U => Self::U, + glfw::window::Key::V => Self::V, + glfw::window::Key::W => Self::W, + glfw::window::Key::X => Self::X, + glfw::window::Key::Y => Self::Y, + glfw::window::Key::Z => Self::Z, + glfw::window::Key::LeftBracket => Self::LeftBracket, + glfw::window::Key::Backslash => Self::Backslash, + glfw::window::Key::RightBracket => Self::RightBracket, + glfw::window::Key::GraveAccent => Self::GraveAccent, + glfw::window::Key::World1 => Self::World1, + glfw::window::Key::World2 => Self::World2, + glfw::window::Key::Escape => Self::Escape, + glfw::window::Key::Enter => Self::Enter, + glfw::window::Key::Tab => Self::Tab, + glfw::window::Key::Backspace => Self::Backspace, + glfw::window::Key::Insert => Self::Insert, + glfw::window::Key::Delete => Self::Delete, + glfw::window::Key::Right => Self::Right, + glfw::window::Key::Left => Self::Left, + glfw::window::Key::Down => Self::Down, + glfw::window::Key::Up => Self::Up, + glfw::window::Key::PageUp => Self::PageUp, + glfw::window::Key::PageDown => Self::PageDown, + glfw::window::Key::Home => Self::Home, + glfw::window::Key::End => Self::End, + glfw::window::Key::CapsLock => Self::CapsLock, + glfw::window::Key::ScrollLock => Self::ScrollLock, + glfw::window::Key::NumLock => Self::NumLock, + glfw::window::Key::PrintScreen => Self::PrintScreen, + glfw::window::Key::Pause => Self::Pause, + glfw::window::Key::F1 => Self::F1, + glfw::window::Key::F2 => Self::F2, + glfw::window::Key::F3 => Self::F3, + glfw::window::Key::F4 => Self::F4, + glfw::window::Key::F5 => Self::F5, + glfw::window::Key::F6 => Self::F6, + glfw::window::Key::F7 => Self::F7, + glfw::window::Key::F8 => Self::F8, + glfw::window::Key::F9 => Self::F9, + glfw::window::Key::F10 => Self::F10, + glfw::window::Key::F11 => Self::F11, + glfw::window::Key::F12 => Self::F12, + glfw::window::Key::F13 => Self::F13, + glfw::window::Key::F14 => Self::F14, + glfw::window::Key::F15 => Self::F15, + glfw::window::Key::F16 => Self::F16, + glfw::window::Key::F17 => Self::F17, + glfw::window::Key::F18 => Self::F18, + glfw::window::Key::F19 => Self::F19, + glfw::window::Key::F20 => Self::F20, + glfw::window::Key::F21 => Self::F21, + glfw::window::Key::F22 => Self::F22, + glfw::window::Key::F23 => Self::F23, + glfw::window::Key::F24 => Self::F24, + glfw::window::Key::F25 => Self::F25, + glfw::window::Key::Kp0 => Self::Kp0, + glfw::window::Key::Kp1 => Self::Kp1, + glfw::window::Key::Kp2 => Self::Kp2, + glfw::window::Key::Kp3 => Self::Kp3, + glfw::window::Key::Kp4 => Self::Kp4, + glfw::window::Key::Kp5 => Self::Kp5, + glfw::window::Key::Kp6 => Self::Kp6, + glfw::window::Key::Kp7 => Self::Kp7, + glfw::window::Key::Kp8 => Self::Kp8, + glfw::window::Key::Kp9 => Self::Kp9, + glfw::window::Key::KpDecimal => Self::KpDecimal, + glfw::window::Key::KpDivide => Self::KpDivide, + glfw::window::Key::KpMultiply => Self::KpMultiply, + glfw::window::Key::KpSubtract => Self::KpSubtract, + glfw::window::Key::KpAdd => Self::KpAdd, + glfw::window::Key::KpEnter => Self::KpEnter, + glfw::window::Key::KpEqual => Self::KpEqual, + glfw::window::Key::LeftShift => Self::LeftShift, + glfw::window::Key::LeftControl => Self::LeftControl, + glfw::window::Key::LeftAlt => Self::LeftAlt, + glfw::window::Key::LeftSuper => Self::LeftSuper, + glfw::window::Key::RightShift => Self::RightShift, + glfw::window::Key::RightControl => Self::RightControl, + glfw::window::Key::RightAlt => Self::RightAlt, + glfw::window::Key::RightSuper => Self::RightSuper, + glfw::window::Key::Menu => Self::Menu, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum KeyState +{ + Pressed, + Released, +} + +impl KeyState +{ + fn from_glfw_key_state(glfw_key_state: glfw::window::KeyState) -> Option<Self> + { + match glfw_key_state { + glfw::window::KeyState::Pressed => Some(Self::Pressed), + glfw::window::KeyState::Released => Some(Self::Released), + glfw::window::KeyState::Repeat => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MouseButton +{ + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, +} + +impl MouseButton +{ + pub const LEFT: Self = Self::One; + pub const MIDDLE: Self = Self::Three; + pub const RIGHT: Self = Self::Two; + + fn from_glfw_mouse_button(mouse_button: glfw::window::MouseButton) -> Self + { + match mouse_button { + glfw::window::MouseButton::One => Self::One, + glfw::window::MouseButton::Two => Self::Two, + glfw::window::MouseButton::Three => Self::Three, + glfw::window::MouseButton::Four => Self::Four, + glfw::window::MouseButton::Five => Self::Five, + glfw::window::MouseButton::Six => Self::Six, + glfw::window::MouseButton::Seven => Self::Seven, + glfw::window::MouseButton::Eight => Self::Eight, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MouseButtonState +{ + Pressed, + Released, +} + +impl MouseButtonState +{ + fn from_glfw_mouse_button_state( + mouse_button_state: glfw::window::MouseButtonState, + ) -> Self + { + match mouse_button_state { + glfw::window::MouseButtonState::Pressed => Self::Pressed, + glfw::window::MouseButtonState::Released => Self::Released, + } + } +} + +bitflags! { + #[derive(Debug, Clone, Copy)] + pub struct KeyModifiers: i32 { + const SHIFT = glfw::window::KeyModifiers::SHIFT.bits(); + const CONTROL = glfw::window::KeyModifiers::CONTROL.bits(); + const ALT = glfw::window::KeyModifiers::ALT.bits(); + const SUPER = glfw::window::KeyModifiers::SUPER.bits(); + const CAPS_LOCK = glfw::window::KeyModifiers::CAPS_LOCK.bits(); + const NUM_LOCK = glfw::window::KeyModifiers::NUM_LOCK.bits(); + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum CursorMode +{ + /// Hides and grabs the cursor, providing virtual and unlimited cursor movement. + Disabled, + + /// Makes the cursor invisible when it is over the content area of the window but + /// does not restrict the cursor from leaving. + Hidden, + + /// Makes the cursor visible and behaving normally. + Normal, +} + +impl CursorMode +{ + fn to_glfw_cursor_mode(self) -> glfw::window::CursorMode + { + match self { + Self::Disabled => glfw::window::CursorMode::Disabled, + Self::Hidden => glfw::window::CursorMode::Hidden, + Self::Normal => glfw::window::CursorMode::Normal, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum InputMode +{ + /// When the cursor is disabled, raw (unscaled and unaccelerated) mouse motion can be + /// enabled if available. + /// + /// Raw mouse motion is closer to the actual motion of the mouse across a surface. It + /// is not affected by the scaling and acceleration applied to the motion of the + /// desktop cursor. That processing is suitable for a cursor while raw motion is + /// better for controlling for example a 3D camera. Because of this, raw mouse motion + /// is only provided when the cursor is disabled. + RawMouseMotion, +} + +impl InputMode +{ + fn to_glfw_input_mode(self) -> glfw::window::InputMode + { + match self { + Self::RawMouseMotion => glfw::window::InputMode::RawMouseMotion, + } + } +} + #[derive(Debug)] pub struct Extension { @@ -257,8 +691,8 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ExtensionCollector<'_>) { - collector.add_system(StartEvent, initialize); - collector.add_system(ConcludeEvent, update); + collector.add_system(*START_PHASE, initialize); + collector.add_system(*UPDATE_PHASE, update); let window = self .window_builder |