Crate tinystorm

Source
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 use WindowBuilder::NO_MAX_FPS for better readability.
  • with_msaa(msaa_quality: u32) - if greater than 0, enables msaa with msaa_quality samples.
    You can use WindowBuilder::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.
}

Re-exports§

pub use glfw;
pub use gl;
pub use nalgebra;

Modules§

mesh
shader
texture
window