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}