thin_engine/
lib.rs

1//! A thin game engine (hence the name). Drawing done with `glium`, game variables done with
2//! `glium-types`, windowing done with `winit` and input done with `winit-input-map`. It has easy fxaa
3//! support and low boilerplate despite having lots of control.
4//! ```
5//! use thin_engine::{prelude::*, meshes::screen};
6//! use std::{cell::RefCell, rc::Rc};
7//! use Action::*;
8//!
9//! #[derive(Hash, PartialEq, Eq, Clone, Copy)]
10//! enum Action {
11//!     Left,
12//!     Right,
13//!     Jump,
14//!     Exit
15//! }
16//! let event_loop = EventLoop::new().unwrap();
17//! event_loop.set_control_flow(ControlFlow::Poll);
18//! let mut input = { use base_input_codes::*; input_map!(
19//!     (Left,  KeyA, MouseButton::Left,  ArrowLeft,  DPadLeft),
20//!     (Right, KeyD, MouseButton::Right, ArrowRight, DPadRight),
21//!     (Jump,  KeyW, ArrowUp, Space, GamepadInput::South),
22//!     // square brackets mean that all input codes must be pressed for the bind to be pressed
23//!     (Exit,  [ControlLeft, Escape], GamepadInput::Start)
24//! )};
25//!
26//! struct Graphics {
27//!     box_indices: IndexBuffer<u32>,
28//!     box_vertices: VertexBuffer<Vertex>,
29//!     box_shader: Program
30//! }
31//! let graphics: Rc<RefCell<Option<Graphics>>> = Rc::default();
32//! let graphics_setup = graphics.clone();
33//!
34//! let mut player_pos = Vec2::ZERO;
35//! let mut player_gravity = 0.0;
36//! let mut player_can_jump = true;
37//!
38//! // camera matrix must be inverse
39//! let camera = Mat4::from_scale(Vec3::splat(10.0)).inverse();
40//! 
41//! let settings = Settings::from_fps(60); // target of 60 fps
42//! let mut frame_start = Instant::now();
43//! thin_engine::builder(input).with_setup(move |display, window, _| {
44//!     let (box_indices, box_vertices) = mesh!(
45//!         display, &screen::INDICES, &screen::VERTICES
46//!     ).unwrap();
47//!     let box_shader = Program::from_source(
48//!         display,
49//!         "#version 140
50//!         in vec3 position;
51//!         
52//!         uniform mat4 perspective;
53//!         uniform mat4 camera;
54//!         uniform mat4 model;
55//!
56//!         void main() {
57//!             gl_Position = perspective * camera * model * vec4(position, 1);
58//!         }", 
59//!         "#version 140
60//!         out vec4 colour;
61//!         void main() {
62//!             colour = vec4(1.0, 0.0, 0.0, 1.0);
63//!         }", None
64//!     ).unwrap();
65//!     graphics_setup.replace(Some(Graphics {
66//!         box_vertices, box_indices, box_shader
67//!     }));
68//! }).with_update(move |input, display, _settings, target, window| {
69//!     // gets time between frames
70//!     let delta_time = frame_start.elapsed().as_secs_f32();
71//!     frame_start = Instant::now();
72//!     
73//!     if input.pressed(Exit) { target.exit() }
74//!
75//!     // game logic
76//!     player_pos.x += input.axis(Right, Left) * 10.0 * delta_time;
77//!     player_gravity += delta_time * 50.0;
78//!     player_pos.y -= player_gravity * delta_time;
79//!     if player_pos.y < 0.0 {
80//!         player_pos.y = 0.0;
81//!         player_can_jump = true;
82//!     }
83//!     if player_can_jump && input.pressed(Jump) {
84//!         player_gravity = -20.0;
85//!         player_can_jump = false;
86//!     }
87//!
88//!     let graphics = graphics.borrow();
89//!     let Graphics {
90//!         box_vertices, box_indices, box_shader
91//!     } = graphics.as_ref().unwrap();
92//!     // set up frame
93//!     let mut frame = display.draw();
94//!     let perspective = Mat4::perspective_2d(frame.get_dimensions());
95//!
96//!     // draw
97//!     frame.clear_color(0.0, 0.0, 0.0, 1.0);
98//!     frame.draw(
99//!         box_vertices, box_indices,
100//!         box_shader, &uniform! {
101//!             perspective: perspective, camera: camera,
102//!             model: Mat4::from_pos(player_pos.extend(0.0)),
103//!         }, &DrawParameters::default()
104//!     );
105//!     window.pre_present_notify();
106//!     frame.finish().unwrap();
107//! }).with_settings(Settings::from_fps(60))
108//!     .build(event_loop).unwrap();
109//! ```
110
111#![allow(deprecated)]
112use glium::backend::glutin::SimpleWindowBuilder;
113pub use gilrs;
114use gilrs::Gilrs;
115pub use glium;
116pub use glium_types;
117pub use winit;
118pub use winit_input_map as input_map;
119use std::time::Duration;
120
121/// Run time settings for thin engine including gamepad settings (through gilrs) and fps settings.
122/// when running `default()` the gamepads may fail to initialise and the program will continue
123/// running after printing the error. If this is undesirable use `with_gamepads()` instead.
124pub struct Settings {
125    pub gamepads: Option<Gilrs>,
126    pub min_frame_duration: Option<Duration>
127}
128impl Settings {
129    pub fn new(gamepads: Option<Gilrs>, min_frame_duration: Option<Duration>) -> Self {
130        Self { gamepads, min_frame_duration }
131    }
132    /// creates settings with the minimum frame duration set to 1 / fps.
133    pub fn from_fps(fps: u32) -> Self {
134        let gamepads = Gilrs::new().map_err(|i| eprintln!("{i}")).ok();
135        let min_frame_duration = Some(Duration::from_secs_f32(1.0/fps as f32));
136
137        Self::new(gamepads, min_frame_duration)
138    }
139    /// guarantees gamepads will be set instead of printing an error and moving on.
140    pub fn with_gamepads() -> Result<Self, gilrs::Error> {
141        let gilrs = Gilrs::new()?;
142        Ok(Self::new(Some(gilrs), None))
143    }
144    /// sets the minimum frame duration to 1 / fps or none if inputed.
145    pub fn set_target_fps(&mut self, target_fps: Option<u32>) {
146        let min_duration = target_fps.map(|i| Duration::from_secs_f32(1.0/i as f32));
147        self.min_frame_duration = min_duration;
148    }
149    pub fn get_fps(&self) -> Option<u32> {
150        self.min_frame_duration.map(|i| (1.0 / i.as_secs_f64()).round() as u32)
151    }
152}
153impl Default for Settings {
154    fn default() -> Self {
155        let gamepads = Gilrs::new().map_err(|i| println!("{i}")).ok();
156        Self::new(gamepads, None)
157    }
158}
159pub mod meshes;
160pub mod shaders;
161pub mod application;
162#[cfg(feature = "text")]
163pub mod text_renderer;
164
165pub type Display = glium::Display<glium::glutin::surface::WindowSurface>;
166
167use winit_input_map::InputMap;
168use crate::application::ThinBuilder;
169use std::hash::Hash;
170pub fn builder<'a, H: Hash + Eq + Copy>(input_map: InputMap<H>) -> ThinBuilder<'a, H> {
171    ThinBuilder::<'a, H>::new(input_map)
172}
173
174pub mod prelude {
175    pub use crate::application::*;
176    pub use glium::{
177        draw_parameters, IndexBuffer, self,
178        VertexBuffer, Program, Texture2d,
179        uniform, Surface, Frame, DrawParameters,
180        backend::glutin::simple_window_builder::SimpleWindowBuilder
181    };
182    pub use crate::Settings;
183    pub use std::time::{Duration, Instant};
184    pub use std::thread;
185    pub use glium_types::prelude::*;
186    pub use crate::{meshes, shaders};
187    pub use winit::{self, event::MouseButton, keyboard::KeyCode};
188    pub use gilrs::ev::{Button as GamepadButton, Axis as GamepadAxis};
189    pub use winit::{event_loop::*, window::{Fullscreen, CursorGrabMode}};
190    pub use crate::input_map::*;
191}
192/// resizable depth texture. recomended to  use with gliums `SimpleFrameBuffer` to draw onto a texture you can use
193/// in another shader! usefull for fxaa
194#[derive(Default)]
195pub struct ResizableTexture2d {
196    pub size: (u32, u32),
197    pub texture: Option<glium::Texture2d>
198}
199impl ResizableTexture2d {
200    pub fn resize(&mut self, display: &Display, new_size: (u32, u32)) {
201        if self.size.0 != new_size.0 || self.size.1 != new_size.1 {
202            self.texture = glium::Texture2d::empty(display, new_size.0, new_size.1).ok();
203            self.size = new_size;
204        }
205    }
206    pub fn resize_to_display(&mut self, display: &Display) {
207        let new_size = display.get_framebuffer_dimensions();
208        if self.size.0 != new_size.0 || self.size.1 != new_size.1 {
209            self.texture = glium::Texture2d::empty(display, new_size.0, new_size.1).ok();
210            self.size = new_size;
211        }
212    }
213    /// borrows the texture or panics. to handle failed borrows use `self.texture.as_ref()` instead
214    pub fn texture(&self) -> &glium::Texture2d {
215        self.texture.as_ref().expect("texture was not initialised. maybe use 'new()' instead of 'default()'")
216    }
217    pub fn new(size: (u32, u32), display: &Display) -> Self {
218        Self { size, texture: glium::Texture2d::empty(display, size.0, size.1).ok() }
219    }
220}
221/// resizable depth texture. use with gliums `SimpleFrameBuffer::WithDepthTexture()` 
222/// to create a texture you can draw on! usefull for things like fog and fxaa.
223#[derive(Default)]
224pub struct ResizableDepthTexture2d {
225    size: (u32, u32),
226    pub texture: Option<glium::texture::DepthTexture2d>
227}
228impl ResizableDepthTexture2d {
229    pub fn resize(&mut self, display: &Display, new_size: (u32, u32)) {
230        if self.size.0 != new_size.0 || self.size.1 != new_size.1 {
231            self.texture = glium::texture::DepthTexture2d::empty(display, new_size.0, new_size.1).ok();
232            self.size = new_size;
233        }
234    }
235    /// borrows the texture or panics. to handle failed borrows use `self.texture.as_ref()` instead
236    pub fn texture(&self) -> &glium::texture::DepthTexture2d {
237        self.texture.as_ref().expect("texture was not initialised. maybe use 'new()' instead of 'default()'")
238    }
239    pub fn new(size: (u32, u32), display: &Display) -> Self {
240        Self { size, texture: glium::texture::DepthTexture2d::empty(display, size.0, size.1).ok() }
241    }
242    pub fn resize_to_display(&mut self, display: &Display) {
243        let new_size = display.get_framebuffer_dimensions();
244        if self.size.0 != new_size.0 || self.size.1 != new_size.1 {
245            self.texture = glium::texture::DepthTexture2d::empty(display, new_size.0, new_size.1).ok();
246            self.size = new_size;
247        }
248    }
249}