summaryrefslogtreecommitdiff
path: root/engine/src
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src')
-rw-r--r--engine/src/camera/fly.rs18
-rw-r--r--engine/src/collision.rs142
-rw-r--r--engine/src/data_types/dimens.rs11
-rw-r--r--engine/src/data_types/vector.rs25
-rw-r--r--engine/src/event.rs27
-rw-r--r--engine/src/file_format/wavefront/mtl.rs11
-rw-r--r--engine/src/file_format/wavefront/obj.rs49
-rw-r--r--engine/src/input.rs50
-rw-r--r--engine/src/lib.rs45
-rw-r--r--engine/src/material.rs8
-rw-r--r--engine/src/math.rs2
-rw-r--r--engine/src/mesh.rs112
-rw-r--r--engine/src/mesh/cube.rs989
-rw-r--r--engine/src/opengl/buffer.rs13
-rw-r--r--engine/src/opengl/mod.rs22
-rw-r--r--engine/src/opengl/texture.rs6
-rw-r--r--engine/src/opengl/vertex_array.rs11
-rw-r--r--engine/src/performance.rs59
-rw-r--r--engine/src/projection.rs118
-rw-r--r--engine/src/renderer/opengl.rs183
-rw-r--r--engine/src/renderer/opengl/glsl/light.glsl4
-rw-r--r--engine/src/texture.rs157
-rw-r--r--engine/src/util.rs73
-rw-r--r--engine/src/vertex.rs9
-rw-r--r--engine/src/window.rs486
25 files changed, 1605 insertions, 1025 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 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/mod.rs b/engine/src/opengl/mod.rs
index a4d3959..53e0120 100644
--- a/engine/src/opengl/mod.rs
+++ b/engine/src/opengl/mod.rs
@@ -1,4 +1,5 @@
use bitflags::bitflags;
+use gl::types::GLint;
use crate::data_types::dimens::Dimens;
use crate::vector::Vec2;
@@ -11,7 +12,6 @@ pub mod vertex_array;
mod util;
-#[cfg(feature = "debug")]
pub mod debug;
pub fn set_viewport(position: Vec2<u32>, size: Dimens<u32>)
@@ -48,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 {
@@ -106,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/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 deb26a8..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,14 +19,20 @@ 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,
@@ -46,23 +53,30 @@ use crate::opengl::vertex_array::{
PrimitiveKind,
VertexArray,
};
-use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability};
-use crate::projection::{new_perspective_matrix, Projection};
+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,
- 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)]
@@ -73,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(),)),
@@ -101,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");
@@ -118,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>,
@@ -129,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;
};
@@ -152,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,
@@ -203,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);
@@ -227,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();
@@ -252,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);
}
@@ -274,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);
}
}
@@ -361,19 +356,18 @@ 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,
}
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() {
@@ -417,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(
@@ -461,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>(
@@ -562,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);
@@ -679,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/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 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