diff options
Diffstat (limited to 'engine/src')
-rw-r--r-- | engine/src/camera/fly.rs | 14 | ||||
-rw-r--r-- | engine/src/collision.rs | 142 | ||||
-rw-r--r-- | engine/src/data_types/dimens.rs | 11 | ||||
-rw-r--r-- | engine/src/data_types/vector.rs | 25 | ||||
-rw-r--r-- | engine/src/event.rs | 27 | ||||
-rw-r--r-- | engine/src/file_format/wavefront/obj.rs | 49 | ||||
-rw-r--r-- | engine/src/input.rs | 43 | ||||
-rw-r--r-- | engine/src/lib.rs | 45 | ||||
-rw-r--r-- | engine/src/material.rs | 8 | ||||
-rw-r--r-- | engine/src/math.rs | 2 | ||||
-rw-r--r-- | engine/src/mesh.rs | 112 | ||||
-rw-r--r-- | engine/src/mesh/cube.rs | 989 | ||||
-rw-r--r-- | engine/src/opengl/buffer.rs | 13 | ||||
-rw-r--r-- | engine/src/opengl/vertex_array.rs | 11 | ||||
-rw-r--r-- | engine/src/performance.rs | 45 | ||||
-rw-r--r-- | engine/src/projection.rs | 118 | ||||
-rw-r--r-- | engine/src/renderer/opengl.rs | 124 | ||||
-rw-r--r-- | engine/src/renderer/opengl/glsl/light.glsl | 4 | ||||
-rw-r--r-- | engine/src/texture.rs | 152 | ||||
-rw-r--r-- | engine/src/util.rs | 73 | ||||
-rw-r--r-- | engine/src/vertex.rs | 9 | ||||
-rw-r--r-- | engine/src/window.rs | 86 |
22 files changed, 1168 insertions, 934 deletions
diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs index 1333360..087f727 100644 --- a/engine/src/camera/fly.rs +++ b/engine/src/camera/fly.rs @@ -1,11 +1,11 @@ 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 crate::camera::{Active as ActiveCamera, Camera}; use crate::delta_time::DeltaTime; -use crate::event::Update as UpdateEvent; use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys}; use crate::transform::Position; use crate::util::builder; @@ -60,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)), @@ -75,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>, @@ -121,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/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 e847702..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()), } } @@ -145,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(); @@ -169,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(); @@ -220,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 07b2f8d..a9a5a97 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,40 +1,30 @@ #![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 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 texture; @@ -47,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 { @@ -72,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(),)), @@ -83,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. @@ -115,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/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 3ec8994..0000000 --- a/engine/src/performance.rs +++ /dev/null @@ -1,45 +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(),)), - ); - } -} - -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); - - tracing::info!( - "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 c036cc0..c44a479 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -9,6 +9,7 @@ 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}; @@ -18,7 +19,6 @@ 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; @@ -61,22 +61,22 @@ use crate::opengl::{ Capability, ContextFlags, }; -use crate::projection::{new_perspective_matrix, Projection}; +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, - 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)] @@ -87,10 +87,10 @@ 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(),)), @@ -133,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>, @@ -166,32 +166,25 @@ fn render( clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); for ( - entity_index, + euid, (mesh, material, material_flags, position, scale, draw_flags, gl_objects), - ) in query.iter().enumerate() + ) in query.iter_with_euids() { let material_flags = material_flags .map(|material_flags| material_flags.clone()) .unwrap_or_default(); - 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, @@ -238,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(); @@ -276,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); } } @@ -363,9 +356,9 @@ fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error> 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, } @@ -418,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( @@ -462,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>( @@ -687,11 +673,11 @@ 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 } diff --git a/engine/src/renderer/opengl/glsl/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl index 1bc23a4..f12b5fe 100644 --- a/engine/src/renderer/opengl/glsl/light.glsl +++ b/engine/src/renderer/opengl/glsl/light.glsl @@ -80,10 +80,10 @@ vec3 calc_specular_light( { vec3 view_direction = normalize(view_pos - frag_pos); - vec3 reflect_direction = reflect(-light_dir, norm); + vec3 halfway_direction = normalize(light_dir + view_direction); float spec = - pow(max(dot(view_direction, reflect_direction), 0.0), material.shininess); + pow(max(dot(norm, halfway_direction), 0.0), material.shininess); return light_phong.specular * ( spec * (vec3(texture(material.specular_map, texture_coords)) * material.specular) diff --git a/engine/src/texture.rs b/engine/src/texture.rs index 16c1941..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 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 ad239a1..00c360e 100644 --- a/engine/src/window.rs +++ b/engine/src/window.rs @@ -4,16 +4,22 @@ 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; +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. #[sole(drop_last)] @@ -169,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) { @@ -514,6 +538,60 @@ impl KeyState } } +#[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 { @@ -613,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 |