Expand description
§Introduction
§Window
§Creating the Window
To create a simple window you can do:
use tinystorm::window::{Window, WindowBuilder};
let mut window: Window = WindowBuilder::default().build();
while window.is_running() {
window.poll_events();
window.swap_buffers();
}
But if you want more percise control over the window creation, you can do:
let mut window: Window = WindowBuilder::default()
.with_size(720, 480) // Default: 960, 540
.with_title("Ne chitay eto :}") // Default: "Titled window, lol"
.with_vsync(false) // Default: true
.with_max_fps(144 * 5) // Default: WindowBuilder::NO_MAX_FPS
.with_msaa(4) // (aka. 4 samples per pixel) Default: WindowBuilder::NO_MSAA
.build();
Let’s breakdown trough each setting:
with_size(width: u32, height: u32)
- sets size of the window.with_title(title: String)
- sets the title of the window.with_vsync(vsync: bool)
- enables/disables vertical synchronization
(if your display refresh rate is 60, FPS would clamp to it).with_max_fps(max_fps: u32)
- sets maximum FPS.
But if vsync enabled and display refresh rate is lower than max FPS - it would clamp to vsync instead.
You can useWindowBuilder::NO_MAX_FPS
for better readability.with_msaa(msaa_quality: u32)
- if greater than 0, enables msaa withmsaa_quality
samples.
You can useWindowBuilder::NO_MSAA
for better readability.
§Working with the Window
Here’s a simple example of working with certain parts of the Window
use tinystorm::{window::{Window, WindowBuilder}, glfw::Key, glfw::MouseButton};
let mut window: Window = WindowBuilder::default().build();
while window.is_running() {
window.poll_events();
// Keyboard input handling
if window.is_key_pressed(Key::Space) {
println!("It would print this message each frame while Space key is pressed.");
}
if window.is_key_just_pressed(Key::Escape) {
println!("It would print this message only at the frame when Escape key was pressed.");
}
// Mouse button input handling
if window.is_mouse_button_pressed(MouseButton::Left) {
println!("The same thing as window.is_key_pressed but with mouse buttons instead.");
}
if window.is_mouse_button_just_pressed(MouseButton::Middle) {
println!("The same thing as window.is_key_just_pressed but with mouse buttons instead.");
}
// Mouse cursor input handling
println!(
"Mouse position in pixels from top-left corner of the window: X {}, Y {}.",
window.get_mouse_x(),
window.get_mouse_y(),
);
println!(
"Mouse movement on this frame: X {}, Y {}.",
window.get_mouse_dx(),
window.get_mouse_dy(),
);
println!("Is mouse grabbed: {}.", window.is_mouse_grabbed());
// Working with time
// ps. You can also do window.get_delta_raw().as_secs_f32()
println!("Current frame delta time in seconds: {}.", window.get_delta());
// Getting window info
println!("Window size in pixels: Width {}, Height {}.", window.get_width(), window.get_height());
println!(
"Window position in pixels from top-left corner: X {}, Y {}.",
window.get_x(),
window.get_y(),
);
println!("Window title: {}.", window.get_title());
println!("Window aspect: {}.", window.get_aspect());
window.swap_buffers();
}
Example usage
if window.is_key_just_pressed(Key::E) {
window.toggle_mouse(); // If mouse is grabbed - it would be released, else - it would be grabbed back.
}
if window.is_key_just_pressed(Key::Q) {
window.grab_mouse();
}
if window.is_key_just_pressed(Key::W) {
window.release_mouse();
}
if window.is_key_just_pressed(Key::R) {
window.set_title(String::from("Oh no! You've just changed the window title!"));
}
if window.is_key_pressed(Key::T) {
// ps. sets the window position from top-left corner.
window.set_position(50, 100); // You can set x/y separately using window.set_x/y(value);
}
if window.is_key_pressed(Key::Y) {
// ps. sets the window size.
window.set_size(100, 300); // You can set width/height separately using window.set_width/height(value);
}
§Mesh
§Creating the Mesh
To create a simple mesh you can do:
use tinystorm::{mesh::{Layout, Mesh}, gl};
// Create the window first
let mesh = Mesh::new::<f32>(&[
-0.5, -0.5,
0.5, -0.5,
0.0, 0.5,
], &Layout::basic_2d(), gl::TRIANGLES); // A simple triangle mesh
You can also define your own layout:
use tinystorm::{mesh::{Attribute, Layout, Mesh}, gl};
// Create the window first
// X, Y, U, V, R, G, B,
let mesh = Mesh::new::<f32>(&[
-0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 0.0,
0.5, -0.5, 1.0, 0.0, 0.0, 1.0, 0.0,
0.5, 0.5, 1.0, 1.0, 0.0, 0.0, 1.0,
-0.5, 0.5, 0.0, 1.0, 1.0, 1.0, 0.0,
], &Layout::default()
.next_attribute(Attribute::Vec2) // XY. In GLSL: layout(location=0) in vec2 a_Position;
.next_attribute(Attribute::Vec2) // UV. In GLSL: layout(location=1) in vec2 a_TexCoord;
.next_attribute(Attribute::Vec3) // RGB. In GLSL: layout(location=2) in vec3 a_Color;
, gl::TRIANGLE_FAN);
Also you’re allowed to send custom vertex structs!
#[repr(C)]
struct Vertex {
position: (f32, f32),
texcoord: (f32, f32),
color: (f32, f32, f32),
}
impl Vertex {
pub fn new(position: (f32, f32), texcoord: (f32, f32), color: (f32, f32, f32)) -> Self {
Self { position, texcoord, color }
}
}
let mesh = Mesh::new::<Vertex>(&[ // IMPORTANT! You need to declare ::<Vertex> for proper data packing
Vertex::new((-0.5, -0.5), (0.0, 0.0), (1.0, 0.0, 0.0)),
Vertex::new(( 0.5, -0.5), (1.0, 0.0), (0.0, 1.0, 0.0)),
Vertex::new(( 0.5, 0.5), (1.0, 1.0), (0.0, 0.0, 1.0)),
Vertex::new((-0.5, 0.5), (0.0, 1.0), (1.0, 1.0, 0.0)),
], &Layout::default()
.next_attribute(Attribute::Vec2) // XY. In GLSL: layout(location=0) in vec2 a_Position;
.next_attribute(Attribute::Vec2) // UV. In GLSL: layout(location=1) in vec2 a_TexCoord;
.next_attribute(Attribute::Vec3) // RGB. In GLSL: layout(location=2) in vec3 a_Color;
, gl::TRIANGLE_FAN);
§If you want to use indices in your mesh, just use IndexedMesh
instead and add your indices as the first argument.
use tinystorm::mesh::{IndexedMesh, Layout};
/* Indices visualized:
* 4
/ \
/ \
3 *---* 2
| |
0 *---* 1
*/
let mesh = IndexedMesh::new::<f32>(&[
0, 1, 3, // Bottom-left triangle of the wall
2, 3, 1, // Top-right triangle of the wall
3, 2, 4, // Roof triangle
], &[
-0.5, -0.5,
0.5, -0.5,
0.5, 0.5,
-0.5, 0.5,
0.0, 1.0,
], &Layout::basic_2d(), gl::TRIANGLES);
§Rendering the mesh
To render the mesh you can just call yourmesh.draw();
But it’s not enough, to see anything on your screen you need to call:
unsafe { gl::Clear(gl::COLOR_BUFFER_BIT); }
Or, if you want to have a 3D game:
unsafe { gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); }
§Example:
while window.is_running() {
window.poll_events();
unsafe { gl::Clear(gl::COLOR_BUFFER_BIT); }
mesh1.draw();
mesh2.draw();
// etc. (It's allowed to draw the same mesh multiple times with no issues!)
window.swap_buffers();
}
§Shader
§Loading shaders
To load vertex and fragment shaders from files you can do:
use tinystorm::shader::Shader;
// Create the window first.
// Vertex shader path Fragment shader path
let shader = Shader::new("./assets/shaders/test.vert", "./assets/shaders/test.frag");
§Using shaders
while window.is_running() {
window.poll_events();
unsafe { gl::Clear(gl::COLOR_BUFFER_BIT); }
shader.bind();
shader.set_float("u_Aspect", window.get_aspect()); // There's many other uniform types you can use.
// If you'll draw mesh while certain shader is bound, this shader would apply to the mesh.
mesh.draw(); // In this case there's "shader" shader bound.
Shader::unbind(); // You can use that if you really need to have no shaders bound at all.
mesh.draw(); // In this case there's no shader bound.
window.swap_buffers();
}
§Texture
§Loading textures
To load texture from file you can do:
use tinystorm::{texture::Texture, gl};
// Create the window first.
let texture = Texture::load_from_file("./assets/textures/super_mario.png", gl::NEAREST, gl::CLAMP_TO_EDGE);
§Using textures
To use texture you need to bind it before rendering the mesh to a certain texture slot.
§Example
while window.is_running() {
window.poll_events();
unsafe { gl::Clear(gl::COLOR_BUFFER_BIT); }
shader.bind();
shader.set_int("u_ColorSampler", 0); // Just bind uniform sampler2D u_ColorSampler; to texture slot 0.
texture.bind(0); // Binding texture to a slot 0.
mesh.draw();
Texture::unbind(); // You can use that if you really need to have no textures bound at all.
}