589 lines
27 KiB
Rust
589 lines
27 KiB
Rust
// Uncomment these following global attributes to silence most warnings of "low" interest:
|
||
/*
|
||
#![allow(dead_code)]
|
||
#![allow(non_snake_case)]
|
||
#![allow(unreachable_code)]
|
||
#![allow(unused_mut)]
|
||
#![allow(unused_unsafe)]
|
||
#![allow(unused_variables)]
|
||
*/
|
||
extern crate nalgebra_glm as glm;
|
||
use std::sync::{Arc, Mutex, RwLock};
|
||
use std::thread;
|
||
use std::{mem, os::raw::c_void, ptr};
|
||
|
||
mod shader;
|
||
mod util;
|
||
mod mesh;
|
||
mod scene_graph;
|
||
use scene_graph::SceneNode;
|
||
mod toolbox;
|
||
use toolbox::simple_heading_animation;
|
||
|
||
use glutin::event::{
|
||
DeviceEvent,
|
||
ElementState::{Pressed, Released},
|
||
Event, KeyboardInput,
|
||
VirtualKeyCode::{self, *},
|
||
WindowEvent,
|
||
};
|
||
use glutin::event_loop::ControlFlow;
|
||
|
||
// initial window size
|
||
const INITIAL_SCREEN_W: u32 = 800;
|
||
const INITIAL_SCREEN_H: u32 = 600;
|
||
const VERTEX_ATTRIBUTE_INDEX: u32 = 0;
|
||
const DIMENSIONS: i32 = 3;
|
||
|
||
// == // Helper functions to make interacting with OpenGL a little bit prettier. You *WILL* need these! // == //
|
||
|
||
// Get the size of an arbitrary array of numbers measured in bytes
|
||
// Example usage: byte_size_of_array(my_array)
|
||
fn byte_size_of_array<T>(val: &[T]) -> isize {
|
||
std::mem::size_of_val(&val[..]) as isize
|
||
}
|
||
|
||
// Get the OpenGL-compatible pointer to an arbitrary array of numbers
|
||
// Example usage: pointer_to_array(my_array)
|
||
fn pointer_to_array<T>(val: &[T]) -> *const c_void {
|
||
&val[0] as *const T as *const c_void
|
||
}
|
||
|
||
// Get the size of the given type in bytes
|
||
// Example usage: size_of::<u64>()
|
||
fn size_of<T>() -> i32 {
|
||
mem::size_of::<T>() as i32
|
||
}
|
||
|
||
// Get an offset in bytes for n units of type T, represented as a relative pointer
|
||
// Example usage: offset::<u64>(4)
|
||
fn offset<T>(n: u32) -> *const c_void {
|
||
(n * mem::size_of::<T>() as u32) as *const T as *const c_void
|
||
}
|
||
|
||
// Get a null pointer (equivalent to an offset of 0)
|
||
// ptr::null()
|
||
|
||
unsafe fn create_vao(vertices: &Vec<f32>, normals: &Vec<f32>, colors: &Vec<f32>, indices: &Vec<u32>) -> u32 {
|
||
let mut vao_id = 0;
|
||
gl::GenVertexArrays(1, &mut vao_id);
|
||
gl::BindVertexArray(vao_id);
|
||
let mut vbo_id = 0;
|
||
gl::GenBuffers(1, &mut vbo_id);
|
||
gl::BindBuffer(gl::ARRAY_BUFFER, vbo_id);
|
||
gl::BufferData(
|
||
gl::ARRAY_BUFFER,
|
||
byte_size_of_array(&vertices),
|
||
vertices.as_ptr() as *const c_void,
|
||
gl::STATIC_DRAW,
|
||
);
|
||
let mut max_vertex_attribs = 0;
|
||
gl::GetIntegerv(gl::MAX_VERTEX_ATTRIBS, &mut max_vertex_attribs);
|
||
println!("max vertex attributes: {max_vertex_attribs}");
|
||
gl::VertexAttribPointer(
|
||
VERTEX_ATTRIBUTE_INDEX,
|
||
DIMENSIONS,
|
||
gl::FLOAT,
|
||
gl::FALSE,
|
||
0,
|
||
std::ptr::null(),
|
||
);
|
||
gl::EnableVertexAttribArray(VERTEX_ATTRIBUTE_INDEX);
|
||
|
||
let mut index_buf_id = 0;
|
||
gl::GenBuffers(1, &mut index_buf_id);
|
||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, index_buf_id);
|
||
gl::BufferData(
|
||
gl::ELEMENT_ARRAY_BUFFER,
|
||
byte_size_of_array(&indices),
|
||
indices.as_ptr() as *const c_void,
|
||
gl::STATIC_DRAW,
|
||
);
|
||
|
||
// color buffer
|
||
let mut cbo_id = 0;
|
||
gl::GenBuffers(1, &mut cbo_id);
|
||
gl::BindBuffer(gl::ARRAY_BUFFER, cbo_id);
|
||
gl::BufferData(
|
||
gl::ARRAY_BUFFER,
|
||
byte_size_of_array(&colors),
|
||
colors.as_ptr() as *const c_void,
|
||
gl::STATIC_DRAW,
|
||
);
|
||
gl::VertexAttribPointer(1, 4, gl::FLOAT, gl::FALSE, 0, std::ptr::null());
|
||
gl::EnableVertexAttribArray(1);
|
||
|
||
// normal buffer
|
||
let mut nbo_id = 0;
|
||
gl::GenBuffers(1, &mut nbo_id);
|
||
gl::BindBuffer(gl::ARRAY_BUFFER, nbo_id);
|
||
gl::BufferData(
|
||
gl::ARRAY_BUFFER,
|
||
byte_size_of_array(&normals),
|
||
normals.as_ptr() as *const c_void,
|
||
gl::STATIC_DRAW,
|
||
);
|
||
gl::VertexAttribPointer(2, DIMENSIONS, gl::FLOAT, gl::FALSE, 0, std::ptr::null());
|
||
gl::EnableVertexAttribArray(2);
|
||
|
||
vao_id
|
||
}
|
||
|
||
// Scene graph drawing function
|
||
unsafe fn draw_scene(node: &scene_graph::SceneNode, transform_loc: i32, normal_loc: i32, view_projection: &glm::Mat4, transform_so_far: &glm::Mat4) {
|
||
// Build model matrix from node transformations
|
||
let translation = glm::translation(&node.position);
|
||
let rot_x = glm::rotation(node.rotation.x, &glm::vec3(1.0, 0.0, 0.0));
|
||
let rot_y = glm::rotation(node.rotation.y, &glm::vec3(0.0, 1.0, 0.0));
|
||
let rot_z = glm::rotation(node.rotation.z, &glm::vec3(0.0, 0.0, 1.0));
|
||
let rotation = rot_z * rot_y * rot_x;
|
||
let scaling = glm::scaling(&node.scale);
|
||
let refp = node.reference_point;
|
||
let to_ref = glm::translation(&refp);
|
||
let from_ref = glm::translation(&-refp);
|
||
let model = translation * to_ref * rotation * scaling * from_ref;
|
||
let new_transform = *transform_so_far * model;
|
||
|
||
// Draw this node if drawable
|
||
if node.index_count > 0 {
|
||
let mvp = view_projection * new_transform;
|
||
gl::UniformMatrix4fv(transform_loc, 1, gl::FALSE, mvp.as_ptr());
|
||
// compute normal matrix (inverse‐transpose of model)
|
||
let nm4 = glm::transpose(&glm::inverse(&new_transform));
|
||
let normal_matrix = glm::mat3(
|
||
nm4[(0,0)], nm4[(0,1)], nm4[(0,2)],
|
||
nm4[(1,0)], nm4[(1,1)], nm4[(1,2)],
|
||
nm4[(2,0)], nm4[(2,1)], nm4[(2,2)],
|
||
);
|
||
gl::UniformMatrix3fv(normal_loc, 1, gl::FALSE, normal_matrix.as_ptr());
|
||
gl::BindVertexArray(node.vao_id);
|
||
gl::DrawElements(gl::TRIANGLES, node.index_count, gl::UNSIGNED_INT, ptr::null());
|
||
}
|
||
// Recurse to children
|
||
for &child in &node.children {
|
||
draw_scene(&*child, transform_loc, normal_loc, view_projection, &new_transform);
|
||
}
|
||
}
|
||
|
||
fn main() {
|
||
// Set up the necessary objects to deal with windows and event handling
|
||
let el = glutin::event_loop::EventLoop::new();
|
||
let wb = glutin::window::WindowBuilder::new()
|
||
.with_title("Gloom-rs")
|
||
.with_resizable(true)
|
||
.with_inner_size(glutin::dpi::LogicalSize::new(
|
||
INITIAL_SCREEN_W,
|
||
INITIAL_SCREEN_H,
|
||
));
|
||
let cb = glutin::ContextBuilder::new().with_vsync(true);
|
||
let windowed_context = cb.build_windowed(wb, &el).unwrap();
|
||
// Uncomment these if you want to use the mouse for controls, but want it to be confined to the screen and/or invisible.
|
||
// windowed_context.window().set_cursor_grab(true).expect("failed to grab cursor");
|
||
// windowed_context.window().set_cursor_visible(false);
|
||
|
||
// Set up a shared vector for keeping track of currently pressed keys
|
||
let arc_pressed_keys = Arc::new(Mutex::new(Vec::<VirtualKeyCode>::with_capacity(10)));
|
||
// Make a reference of this vector to send to the render thread
|
||
let pressed_keys = Arc::clone(&arc_pressed_keys);
|
||
|
||
// Set up shared tuple for tracking mouse movement between frames
|
||
let arc_mouse_delta = Arc::new(Mutex::new((0f32, 0f32)));
|
||
// Make a reference of this tuple to send to the render thread
|
||
let mouse_delta = Arc::clone(&arc_mouse_delta);
|
||
|
||
// Set up shared tuple for tracking changes to the window size
|
||
let arc_window_size = Arc::new(Mutex::new((INITIAL_SCREEN_W, INITIAL_SCREEN_H, false)));
|
||
// Make a reference of this tuple to send to the render thread
|
||
let window_size = Arc::clone(&arc_window_size);
|
||
|
||
// Spawn a separate thread for rendering, so event handling doesn't block rendering
|
||
let render_thread = thread::spawn(move || {
|
||
// Acquire the OpenGL Context and load the function pointers.
|
||
// This has to be done inside of the rendering thread, because
|
||
// an active OpenGL context cannot safely traverse a thread boundary
|
||
let context = unsafe {
|
||
let c = windowed_context.make_current().unwrap();
|
||
gl::load_with(|symbol| c.get_proc_address(symbol) as *const _);
|
||
c
|
||
};
|
||
|
||
let mut window_aspect_ratio = INITIAL_SCREEN_W as f32 / INITIAL_SCREEN_H as f32;
|
||
|
||
// Set up openGL
|
||
unsafe {
|
||
gl::Enable(gl::DEPTH_TEST);
|
||
gl::DepthFunc(gl::LESS);
|
||
gl::Enable(gl::CULL_FACE);
|
||
gl::Disable(gl::MULTISAMPLE);
|
||
gl::Enable(gl::BLEND);
|
||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||
gl::Enable(gl::PROGRAM_POINT_SIZE);
|
||
gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS);
|
||
gl::DebugMessageCallback(Some(util::debug_callback), ptr::null());
|
||
|
||
// Print some diagnostics
|
||
println!(
|
||
"{}: {}",
|
||
util::get_gl_string(gl::VENDOR),
|
||
util::get_gl_string(gl::RENDERER)
|
||
);
|
||
println!("OpenGL\t: {}", util::get_gl_string(gl::VERSION));
|
||
println!(
|
||
"GLSL\t: {}",
|
||
util::get_gl_string(gl::SHADING_LANGUAGE_VERSION)
|
||
);
|
||
}
|
||
|
||
// == // Set up your VAO around here
|
||
let terrain_mesh = mesh::Terrain::load("resources/lunarsurface.obj");
|
||
let terrain_vao = unsafe {
|
||
create_vao(&terrain_mesh.vertices, &terrain_mesh.normals, &terrain_mesh.colors, &terrain_mesh.indices)
|
||
};
|
||
// helicopter VAOs
|
||
let helicopter = mesh::Helicopter::load("resources/helicopter.obj");
|
||
let heli_body_vao = unsafe { create_vao(&helicopter.body.vertices, &helicopter.body.normals, &helicopter.body.colors, &helicopter.body.indices) };
|
||
let heli_door_vao = unsafe { create_vao(&helicopter.door.vertices, &helicopter.door.normals, &helicopter.door.colors, &helicopter.door.indices) };
|
||
let heli_main_rotor_vao = unsafe { create_vao(&helicopter.main_rotor.vertices, &helicopter.main_rotor.normals, &helicopter.main_rotor.colors, &helicopter.main_rotor.indices) };
|
||
let heli_tail_rotor_vao = unsafe { create_vao(&helicopter.tail_rotor.vertices, &helicopter.tail_rotor.normals, &helicopter.tail_rotor.colors, &helicopter.tail_rotor.indices) };
|
||
|
||
// Scene Graph setup
|
||
let mut root_node = SceneNode::new();
|
||
let mut terrain_node = SceneNode::from_vao(terrain_vao, terrain_mesh.index_count);
|
||
root_node.add_child(&*terrain_node);
|
||
|
||
// Instantiate multiple helicopters
|
||
let heli_count = 5;
|
||
let mut heli_roots = Vec::new();
|
||
for _ in 0..heli_count {
|
||
let mut root = SceneNode::new();
|
||
let body_node = SceneNode::from_vao(heli_body_vao, helicopter.body.index_count);
|
||
let door_node = SceneNode::from_vao(heli_door_vao, helicopter.door.index_count);
|
||
let mut main_rotor_node = SceneNode::from_vao(heli_main_rotor_vao, helicopter.main_rotor.index_count);
|
||
let mut tail_rotor_node = SceneNode::from_vao(heli_tail_rotor_vao, helicopter.tail_rotor.index_count);
|
||
// Set reference point for tail rotor (pivot around its hub)
|
||
{
|
||
let mut tr_pin = tail_rotor_node.as_mut();
|
||
unsafe { tr_pin.get_unchecked_mut().reference_point = glm::vec3(0.35, 2.3, 10.4); }
|
||
}
|
||
root.add_child(&*body_node);
|
||
root.add_child(&*door_node);
|
||
root.add_child(&*main_rotor_node);
|
||
root.add_child(&*tail_rotor_node);
|
||
terrain_node.add_child(&*root);
|
||
heli_roots.push(root);
|
||
}
|
||
|
||
let vertices = vec![
|
||
// triangle 1
|
||
-0.8, -0.8, 0.0,
|
||
-0.2, -0.8, 0.0,
|
||
-0.5, -0.2, 0.0,
|
||
// triangle 2
|
||
0.2, -0.8, 0.0,
|
||
0.8, -0.8, 0.0,
|
||
0.5, -0.2, 0.0,
|
||
// triangle 3
|
||
-0.3, 0.2, 0.0,
|
||
0.3, 0.2, 0.0,
|
||
0.0, 0.8, 0.0,
|
||
];
|
||
let indices = vec![
|
||
0, 1, 2,
|
||
3, 4, 5,
|
||
6, 7, 8,
|
||
];
|
||
let colors = vec![
|
||
// triangle 1 colors
|
||
1.0, 0.0, 0.0, 1.0,
|
||
0.0, 1.0, 0.0, 1.0,
|
||
0.0, 0.0, 1.0, 1.0,
|
||
// triangle 2 colors
|
||
1.0, 1.0, 0.0, 1.0,
|
||
0.0, 1.0, 1.0, 1.0,
|
||
1.0, 0.0, 1.0, 1.0,
|
||
// triangle 3 colors
|
||
0.5, 0.5, 0.5, 1.0,
|
||
1.0, 0.5, 0.0, 1.0,
|
||
0.0, 0.5, 1.0, 1.0,
|
||
];
|
||
|
||
|
||
|
||
|
||
|
||
let billboard_vertices = vec![
|
||
-0.5, -0.5, 0.0,
|
||
-0.5, 0.5, 0.0,
|
||
0.5, 0.5, 0.0,
|
||
0.5, -0.5, 0.0,
|
||
];
|
||
let billboard_indices = vec![
|
||
0, 1, 2,
|
||
0, 2, 3,
|
||
];
|
||
let billboard_colors = vec![
|
||
1.0, 1.0, 1.0, 1.0,
|
||
1.0, 1.0, 1.0, 1.0,
|
||
1.0, 1.0, 1.0, 1.0,
|
||
1.0, 1.0, 1.0, 1.0,
|
||
];
|
||
let billboard_vao = unsafe { create_vao(&billboard_vertices, &Vec::new(), &billboard_colors, &billboard_indices) };
|
||
|
||
// Generate a grid of point‐particles
|
||
let grid_size = 200;
|
||
let mut particle_vertices = Vec::with_capacity(grid_size * grid_size * 3);
|
||
let mut particle_colors = Vec::with_capacity(grid_size * grid_size * 4);
|
||
for i in 0..grid_size {
|
||
for j in 0..grid_size {
|
||
let x = (i as f32 / grid_size as f32 - 0.5) * 20.0;
|
||
let y = (j as f32 / grid_size as f32 - 0.5) * 20.0;
|
||
let z = -5.0;
|
||
particle_vertices.extend_from_slice(&[x, y, z]);
|
||
particle_colors.extend_from_slice(&[1.0, 1.0, 1.0, 1.0]);
|
||
}
|
||
}
|
||
let particle_vao = unsafe { create_vao(&particle_vertices, &Vec::new(), &particle_colors, &Vec::new()) };
|
||
let particle_count = (grid_size * grid_size) as i32;
|
||
|
||
// == // Set up your shaders here
|
||
|
||
// Basic usage of shader helper:
|
||
// The example code below creates a 'shader' object.
|
||
// It which contains the field `.program_id` and the method `.activate()`.
|
||
// The `.` in the path is relative to `Cargo.toml`.
|
||
// This snippet is not enough to do the exercise, and will need to be modified (outside
|
||
// of just using the correct path), but it only needs to be called once
|
||
|
||
// TODO: Find out what to do with vertex and fragment shader.
|
||
let simple_shader = unsafe {
|
||
shader::ShaderBuilder::new()
|
||
.attach_file("./shaders/simple.vert")
|
||
.attach_file("./shaders/simple.frag")
|
||
.link()
|
||
};
|
||
// get uniform location for the matrix (named `transform` in your vertex shader)
|
||
let transform_loc = unsafe { simple_shader.get_uniform_location("transform") };
|
||
let normal_loc = unsafe { simple_shader.get_uniform_location("normalMatrix") };
|
||
|
||
// Used to demonstrate keyboard handling for exercise 2.
|
||
let mut _arbitrary_number = 0.0; // feel free to remove
|
||
|
||
// camera position (x, y, z) and orientation (yaw, pitch)
|
||
let mut cam_pos: glm::Vec3 = glm::vec3(0.0, 0.0, 0.0);
|
||
let mut cam_yaw: f32 = 0.0;
|
||
let mut cam_pitch: f32 = 0.0;
|
||
|
||
// The main rendering loop
|
||
let first_frame_time = std::time::Instant::now();
|
||
let mut previous_frame_time = first_frame_time;
|
||
loop {
|
||
// Compute time passed since the previous frame and since the start of the program
|
||
let now = std::time::Instant::now();
|
||
let elapsed = now.duration_since(first_frame_time).as_secs_f32();
|
||
let delta_time = now.duration_since(previous_frame_time).as_secs_f32();
|
||
previous_frame_time = now;
|
||
|
||
// Handle resize events
|
||
if let Ok(mut new_size) = window_size.lock() {
|
||
if new_size.2 {
|
||
context.resize(glutin::dpi::PhysicalSize::new(new_size.0, new_size.1));
|
||
window_aspect_ratio = new_size.0 as f32 / new_size.1 as f32;
|
||
(*new_size).2 = false;
|
||
println!("Window was resized to {}x{}", new_size.0, new_size.1);
|
||
unsafe {
|
||
gl::Viewport(0, 0, new_size.0 as i32, new_size.1 as i32);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Handle keyboard input for camera movement and rotation
|
||
if let Ok(keys) = pressed_keys.lock() {
|
||
// movement speed
|
||
let speed = 10.0 * delta_time;
|
||
// camera direction vectors based on yaw & pitch
|
||
let front = glm::vec3(
|
||
cam_yaw.sin() * cam_pitch.cos(),
|
||
cam_pitch.sin(),
|
||
-cam_yaw.cos() * cam_pitch.cos(),
|
||
);
|
||
let right = glm::normalize(&glm::cross(&front, &glm::vec3(0.0, 1.0, 0.0)));
|
||
let up_vec = glm::normalize(&glm::cross(&right, &front));
|
||
for key in keys.iter() {
|
||
match key {
|
||
// Move relative to camera orientation
|
||
VirtualKeyCode::W => { cam_pos -= front * speed; }
|
||
VirtualKeyCode::S => { cam_pos += front * speed; }
|
||
VirtualKeyCode::A => { cam_pos -= right * speed; }
|
||
VirtualKeyCode::D => { cam_pos += right * speed; }
|
||
VirtualKeyCode::Space => { cam_pos += up_vec * speed; }
|
||
VirtualKeyCode::LShift => { cam_pos -= up_vec * speed; }
|
||
// Rotation: adjust yaw and pitch
|
||
VirtualKeyCode::Left => { cam_yaw -= delta_time; }
|
||
VirtualKeyCode::Right => { cam_yaw += delta_time; }
|
||
VirtualKeyCode::Up => { cam_pitch += delta_time; }
|
||
VirtualKeyCode::Down => { cam_pitch -= delta_time; }
|
||
_ => {}
|
||
}
|
||
}
|
||
// clamp pitch to avoid flipping
|
||
let pitch_limit = std::f32::consts::FRAC_PI_2 - 0.01;
|
||
cam_pitch = cam_pitch.clamp(-pitch_limit, pitch_limit);
|
||
}
|
||
// Handle mouse movement. delta contains the x and y movement of the mouse since last frame in pixels
|
||
if let Ok(mut delta) = mouse_delta.lock() {
|
||
// == // Optionally access the accumulated mouse movement between
|
||
// == // frames here with `delta.0` and `delta.1`
|
||
|
||
*delta = (0.0, 0.0); // reset when done
|
||
}
|
||
|
||
// == // Please compute camera transforms here (exercise 2 & 3)
|
||
|
||
unsafe {
|
||
// Clear the color and depth buffers
|
||
gl::ClearColor(0.035, 0.046, 0.078, 1.0); // night sky
|
||
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
|
||
|
||
// == // Issue the necessary gl:: commands to draw your scene here
|
||
simple_shader.activate();
|
||
// build View matrix: translate and rotate world relative to camera
|
||
let view_translation: glm::Mat4 = glm::translation(&(-cam_pos));
|
||
let view_rot_y: glm::Mat4 = glm::rotation(-cam_yaw, &glm::vec3(0.0, 1.0, 0.0));
|
||
let view_rot_x: glm::Mat4 = glm::rotation(-cam_pitch, &glm::vec3(1.0, 0.0, 0.0));
|
||
let view: glm::Mat4 = view_rot_x * view_rot_y * view_translation;
|
||
// build Projection matrix
|
||
let projection: glm::Mat4 =
|
||
glm::perspective(window_aspect_ratio, std::f32::consts::PI / 4.0, 1.0, 1000.0);
|
||
|
||
// Animate and update multiple helicopters
|
||
for (i, root) in heli_roots.iter_mut().enumerate() {
|
||
let time_offset = elapsed + (i as f32) * 0.5;
|
||
let heading = simple_heading_animation(time_offset);
|
||
{
|
||
let mut pin = root.as_mut();
|
||
let node = unsafe { pin.get_unchecked_mut() };
|
||
node.position = glm::vec3(heading.x + (i as f32) * 10.0, 0.0, heading.z);
|
||
node.rotation = glm::vec3(heading.pitch, heading.yaw, heading.roll);
|
||
}
|
||
// Update rotors: child index 2 = main rotor, 3 = tail rotor
|
||
{
|
||
let mut pin = root.as_mut();
|
||
let node = unsafe { pin.get_unchecked_mut() };
|
||
unsafe {
|
||
(&mut (*node.children[2]).rotation).y = elapsed * 10.0;
|
||
(&mut (*node.children[3]).rotation).x = elapsed * 10.0;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Draw scene via scene graph
|
||
let view_proj = projection * view;
|
||
draw_scene(&*root_node, transform_loc, normal_loc, &view_proj, &glm::identity());
|
||
}
|
||
|
||
// Display the new color buffer on the display
|
||
context.swap_buffers().unwrap(); // we use "double buffering" to avoid artifacts
|
||
}
|
||
});
|
||
|
||
// == //
|
||
// == // From here on down there are only internals.
|
||
// == //
|
||
|
||
// Keep track of the health of the rendering thread
|
||
let render_thread_healthy = Arc::new(RwLock::new(true));
|
||
let render_thread_watchdog = Arc::clone(&render_thread_healthy);
|
||
thread::spawn(move || {
|
||
if !render_thread.join().is_ok() {
|
||
if let Ok(mut health) = render_thread_watchdog.write() {
|
||
println!("Render thread panicked!");
|
||
*health = false;
|
||
}
|
||
}
|
||
});
|
||
|
||
// Start the event loop -- This is where window events are initially handled
|
||
el.run(move |event, _, control_flow| {
|
||
*control_flow = ControlFlow::Wait;
|
||
|
||
// Terminate program if render thread panics
|
||
if let Ok(health) = render_thread_healthy.read() {
|
||
if *health == false {
|
||
*control_flow = ControlFlow::Exit;
|
||
}
|
||
}
|
||
|
||
match event {
|
||
Event::WindowEvent {
|
||
event: WindowEvent::Resized(physical_size),
|
||
..
|
||
} => {
|
||
println!(
|
||
"New window size received: {}x{}",
|
||
physical_size.width, physical_size.height
|
||
);
|
||
if let Ok(mut new_size) = arc_window_size.lock() {
|
||
*new_size = (physical_size.width, physical_size.height, true);
|
||
}
|
||
}
|
||
Event::WindowEvent {
|
||
event: WindowEvent::CloseRequested,
|
||
..
|
||
} => {
|
||
*control_flow = ControlFlow::Exit;
|
||
}
|
||
// Keep track of currently pressed keys to send to the rendering thread
|
||
Event::WindowEvent {
|
||
event:
|
||
WindowEvent::KeyboardInput {
|
||
input:
|
||
KeyboardInput {
|
||
state: key_state,
|
||
virtual_keycode: Some(keycode),
|
||
..
|
||
},
|
||
..
|
||
},
|
||
..
|
||
} => {
|
||
if let Ok(mut keys) = arc_pressed_keys.lock() {
|
||
match key_state {
|
||
Released => {
|
||
if keys.contains(&keycode) {
|
||
let i = keys.iter().position(|&k| k == keycode).unwrap();
|
||
keys.remove(i);
|
||
}
|
||
}
|
||
Pressed => {
|
||
if !keys.contains(&keycode) {
|
||
keys.push(keycode);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Handle Escape and Q keys separately
|
||
match keycode {
|
||
Escape => {
|
||
*control_flow = ControlFlow::Exit;
|
||
}
|
||
Q => {
|
||
*control_flow = ControlFlow::Exit;
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
Event::DeviceEvent {
|
||
event: DeviceEvent::MouseMotion { delta },
|
||
..
|
||
} => {
|
||
// Accumulate mouse movement
|
||
if let Ok(mut position) = arc_mouse_delta.lock() {
|
||
*position = (position.0 + delta.0 as f32, position.1 + delta.1 as f32);
|
||
}
|
||
}
|
||
_ => {}
|
||
}
|
||
});
|
||
}
|