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