Files
OpenGL_Intro/src/main.rs

589 lines
27 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 (inversetranspose 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 pointparticles
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, 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);
}
}
_ => {}
}
});
}