From 238052c8a8f99bfc7fb791ce16e3349e45be337f Mon Sep 17 00:00:00 2001 From: Benjamin Loison Date: Sun, 16 Oct 2022 22:27:32 +0200 Subject: [PATCH] Display two not textured triangles in 3D with camera with horizontal rotation Thanks to `glium/examples/teapot` which was loading an OBJ and not having any rotation for the camera. --- Cargo.toml | 4 +- src/main.rs | 108 ++++++++++++++++++++- src/support/camera.rs | 212 ++++++++++++++++++++++++++++++++++++++++++ src/support/mod.rs | 132 ++++++++++++++++++++++++++ 4 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 src/support/camera.rs create mode 100644 src/support/mod.rs diff --git a/Cargo.toml b/Cargo.toml index d66583a..1e458d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,6 @@ name = "lemnoslife" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +glium = "0.32.1" +glutin = "0.29.1" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e7a11a9..13d2ca1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,109 @@ +#[macro_use] +extern crate glium; + +#[allow(unused_imports)] +use glium::{glutin, Surface}; + +mod support; + fn main() { - println!("Hello, world!"); + // building the display, ie. the main object + let event_loop = glutin::event_loop::EventLoop::new(); + let wb = glutin::window::WindowBuilder::new(); + let cb = glutin::ContextBuilder::new().with_depth_buffer(24); + let display = glium::Display::new(wb, cb, &event_loop).unwrap(); + + // building the vertex and index buffers + let vertex_buffer = support::load_ground(&display); + + // the program + let program = program!(&display, + 140 => { + vertex: " + #version 140 + + uniform mat4 persp_matrix; + uniform mat4 view_matrix; + + in vec3 position; + in vec3 normal; + out vec3 v_position; + out vec3 v_normal; + + void main() { + v_position = position; + v_normal = normal; + gl_Position = persp_matrix * view_matrix * vec4(v_position * 0.005, 1.0); + } + ", + + fragment: " + #version 140 + + in vec3 v_normal; + out vec4 f_color; + + const vec3 LIGHT = vec3(0.0, -1.0, 0.0); + + void main() { + float lum = max(dot(normalize(v_normal), normalize(LIGHT)), 0.0); + vec3 color = (0.3 + 0.7 * lum) * vec3(1.0, 1.0, 1.0); + f_color = vec4(color, 1.0); + } + " + }, + ) + .unwrap(); + + let mut camera = support::camera::CameraState::new(); + + // the main loop + support::start_loop(event_loop, move |events| { + camera.update(); + + // building the uniforms + let uniforms = uniform! { + persp_matrix: camera.get_perspective(), + view_matrix: camera.get_view(), + }; + + // draw parameters + let params = glium::DrawParameters { + depth: glium::Depth { + test: glium::DepthTest::IfLess, + write: true, + ..Default::default() + }, + ..Default::default() + }; + + // drawing a frame + let mut target = display.draw(); + target.clear_color_and_depth((0.0, 0.0, 0.0, 0.0), 1.0); + target + .draw( + &vertex_buffer, + &glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList), + &program, + &uniforms, + ¶ms, + ) + .unwrap(); + target.finish().unwrap(); + + let mut action = support::Action::Continue; + + // polling and handling the events received by the window + for event in events { + match event { + glutin::event::Event::WindowEvent { event, .. } => match event { + glutin::event::WindowEvent::CloseRequested => action = support::Action::Stop, + ev => camera.process_input(&ev), + }, + _ => (), + } + } + + action + }); } diff --git a/src/support/camera.rs b/src/support/camera.rs new file mode 100644 index 0000000..db5af3c --- /dev/null +++ b/src/support/camera.rs @@ -0,0 +1,212 @@ +pub struct CameraState { + aspect_ratio: f32, + position: (f32, f32, f32), + direction: (f32, f32, f32), + + moving_up: bool, + moving_left: bool, + moving_down: bool, + moving_right: bool, + moving_forward: bool, + moving_backward: bool, + rotate_left: bool, + rotate_right: bool, +} + +impl CameraState { + pub fn new() -> CameraState { + CameraState { + aspect_ratio: 1024.0 / 768.0, + position: (0.1, 0.1, 1.0), + // The second coordinate is for the altitude. + direction: (0.0, 0.0, -1.0), + moving_up: false, + moving_left: false, + moving_down: false, + moving_right: false, + moving_forward: false, + moving_backward: false, + rotate_left: false, + rotate_right: false, + } + } + + pub fn get_perspective(&self) -> [[f32; 4]; 4] { + let fov: f32 = 3.141592 / 2.0; + let zfar = 8000.0; + let znear = 0.01; + + let f = 1.0 / (fov / 2.0).tan(); + + // note: remember that this is column-major, so the lines of code are actually columns + [ + [f / self.aspect_ratio, 0.0, 0.0, 0.0], + [0.0, f, 0.0, 0.0], + [0.0, 0.0, (zfar + znear) / (zfar - znear), 1.0], + [0.0, 0.0, -(2.0 * zfar * znear) / (zfar - znear), 0.0], + ] + } + + pub fn get_view(&self) -> [[f32; 4]; 4] { + let f = { + let f = self.direction; + let len = f.0 * f.0 + f.1 * f.1 + f.2 * f.2; + let len = len.sqrt(); + (f.0 / len, f.1 / len, f.2 / len) + }; + + let up = (0.0, 1.0, 0.0); + + let s = ( + f.1 * up.2 - f.2 * up.1, + f.2 * up.0 - f.0 * up.2, + f.0 * up.1 - f.1 * up.0, + ); + + let s_norm = { + let len = s.0 * s.0 + s.1 * s.1 + s.2 * s.2; + let len = len.sqrt(); + (s.0 / len, s.1 / len, s.2 / len) + }; + + let u = ( + s_norm.1 * f.2 - s_norm.2 * f.1, + s_norm.2 * f.0 - s_norm.0 * f.2, + s_norm.0 * f.1 - s_norm.1 * f.0, + ); + + let p = ( + -self.position.0 * s.0 - self.position.1 * s.1 - self.position.2 * s.2, + -self.position.0 * u.0 - self.position.1 * u.1 - self.position.2 * u.2, + -self.position.0 * f.0 - self.position.1 * f.1 - self.position.2 * f.2, + ); + + // note: remember that this is column-major, so the lines of code are actually columns + [ + [s_norm.0, u.0, f.0, 0.0], + [s_norm.1, u.1, f.1, 0.0], + [s_norm.2, u.2, f.2, 0.0], + [p.0, p.1, p.2, 1.0], + ] + } + + pub fn update(&mut self) { + let f = { + let f = self.direction; + let len = f.0 * f.0 + f.1 * f.1 + f.2 * f.2; + let len = len.sqrt(); + (f.0 / len, f.1 / len, f.2 / len) + }; + + let up = (0.0, 1.0, 0.0); + + let s = ( + f.1 * up.2 - f.2 * up.1, + f.2 * up.0 - f.0 * up.2, + f.0 * up.1 - f.1 * up.0, + ); + + let s = { + let len = s.0 * s.0 + s.1 * s.1 + s.2 * s.2; + let len = len.sqrt(); + (s.0 / len, s.1 / len, s.2 / len) + }; + + let u = ( + s.1 * f.2 - s.2 * f.1, + s.2 * f.0 - s.0 * f.2, + s.0 * f.1 - s.1 * f.0, + ); + + if self.moving_up { + self.position.0 += u.0 * 0.01; + self.position.1 += u.1 * 0.01; + self.position.2 += u.2 * 0.01; + } + + if self.moving_left { + self.position.0 -= s.0 * 0.01; + self.position.1 -= s.1 * 0.01; + self.position.2 -= s.2 * 0.01; + } + + if self.moving_down { + self.position.0 -= u.0 * 0.01; + self.position.1 -= u.1 * 0.01; + self.position.2 -= u.2 * 0.01; + } + + if self.moving_right { + self.position.0 += s.0 * 0.01; + self.position.1 += s.1 * 0.01; + self.position.2 += s.2 * 0.01; + } + + if self.moving_forward { + self.position.0 += f.0 * 0.01; + self.position.1 += f.1 * 0.01; + self.position.2 += f.2 * 0.01; + } + + if self.moving_backward { + self.position.0 -= f.0 * 0.01; + self.position.1 -= f.1 * 0.01; + self.position.2 -= f.2 * 0.01; + } + + if self.rotate_left { + let theta: f32 = -0.1; + + let a_x = self.direction.0; + let a_y = self.direction.2; + + let cos_theta = theta.cos(); + let sin_theta = theta.sin(); + + self.direction.0 = cos_theta * a_x - sin_theta * a_y; + self.direction.2 = sin_theta * a_x + cos_theta * a_y; + } + + if self.rotate_right { + let theta: f32 = 0.1; + + let a_x = self.direction.0; + let a_y = self.direction.2; + + let cos_theta = theta.cos(); + let sin_theta = theta.sin(); + + self.direction.0 = cos_theta * a_x - sin_theta * a_y; + self.direction.2 = sin_theta * a_x + cos_theta * a_y; + } + + if self.rotate_left || self.rotate_right { + let norm = (self.direction.0.powi(2) + self.direction.2.powi(2)).sqrt(); + self.direction.0 /= norm; + self.direction.1 /= norm; + } + } + + pub fn process_input(&mut self, event: &glutin::event::WindowEvent<'_>) { + let input = match *event { + glutin::event::WindowEvent::KeyboardInput { input, .. } => input, + _ => return, + }; + let pressed = input.state == glutin::event::ElementState::Pressed; + let key = match input.virtual_keycode { + Some(key) => key, + None => return, + }; + match key { + glutin::event::VirtualKeyCode::Up => self.moving_up = pressed, + glutin::event::VirtualKeyCode::Down => self.moving_down = pressed, + glutin::event::VirtualKeyCode::Q => self.moving_left = pressed, + glutin::event::VirtualKeyCode::D => self.moving_right = pressed, + glutin::event::VirtualKeyCode::Z => self.moving_forward = pressed, + glutin::event::VirtualKeyCode::S => self.moving_backward = pressed, + glutin::event::VirtualKeyCode::A => self.rotate_left = pressed, + glutin::event::VirtualKeyCode::E => self.rotate_right = pressed, + _ => (), + }; + } +} diff --git a/src/support/mod.rs b/src/support/mod.rs new file mode 100644 index 0000000..eaa5ca6 --- /dev/null +++ b/src/support/mod.rs @@ -0,0 +1,132 @@ +#![allow(dead_code)] +use glium::glutin::event::{Event, StartCause}; +use glium::glutin::event_loop::{ControlFlow, EventLoop}; +use glium::vertex::VertexBufferAny; +use glium::{self, Display}; +use std::time::{Duration, Instant}; + +pub mod camera; + +pub enum Action { + Stop, + Continue, +} + +pub fn start_loop(event_loop: EventLoop<()>, mut callback: F) -> ! +where + F: 'static + FnMut(&Vec>) -> Action, +{ + let mut events_buffer = Vec::new(); + let mut next_frame_time = Instant::now(); + event_loop.run(move |event, _, control_flow| { + let run_callback = match event.to_static() { + Some(Event::NewEvents(cause)) => match cause { + StartCause::ResumeTimeReached { .. } | StartCause::Init => true, + _ => false, + }, + Some(event) => { + events_buffer.push(event); + false + } + None => { + // Ignore this event. + false + } + }; + + let action = if run_callback { + let action = callback(&events_buffer); + next_frame_time = Instant::now() + Duration::from_nanos(16666667); + // TODO: Add back the old accumulator loop in some way + + events_buffer.clear(); + action + } else { + Action::Continue + }; + + match action { + Action::Continue => { + *control_flow = ControlFlow::WaitUntil(next_frame_time); + } + Action::Stop => *control_flow = ControlFlow::Exit, + } + }) +} + +/// Returns a vertex buffer that should be rendered as `TrianglesList`. +pub fn load_ground(display: &Display) -> VertexBufferAny { + #[derive(Copy, Clone)] + struct Vertex { + position: [f32; 3], + normal: [f32; 3], + texture: [f32; 2], + } + + implement_vertex!(Vertex, position, normal, texture); + + let mut vertex_data = Vec::new(); + + vertex_data.push(Vertex { + position: [0.0, 0.0, 0.0], + normal: [0.0, 0.0, 1.0], + texture: [0.0, 0.0], + }); + + vertex_data.push(Vertex { + position: [1.0, 0.0, 0.0], + normal: [0.0, 0.0, 1.0], + texture: [0.0, 0.0], + }); + + vertex_data.push(Vertex { + position: [0.0, 0.0, 1.0], + normal: [0.0, 0.0, 1.0], + texture: [0.0, 0.0], + }); + + vertex_data.push(Vertex { + position: [1.0, 0.0, 0.0], + normal: [0.0, 0.0, 1.0], + texture: [0.0, 0.0], + }); + + vertex_data.push(Vertex { + position: [0.0, 0.0, 1.0], + normal: [0.0, 0.0, 1.0], + texture: [0.0, 0.0], + }); + + vertex_data.push(Vertex { + position: [1.0, 0.0, 1.0], + normal: [0.0, 0.0, 1.0], + texture: [0.0, 0.0], + }); + + /*for object in data.objects.iter() { + for polygon in object.groups.iter().flat_map(|g| g.polys.iter()) { + match polygon { + obj::SimplePolygon(indices) => { + for v in indices.iter() { + let position = data.position[v.0]; + let texture = v.1.map(|index| data.texture[index]); + let normal = v.2.map(|index| data.normal[index]); + + let texture = texture.unwrap_or([0.0, 0.0]); + let normal = normal.unwrap_or([0.0, 0.0, 0.0]); + + vertex_data.push(Vertex { + position, + normal, + texture, + }) + } + } + } + } + }*/ + + glium::vertex::VertexBuffer::new(display, &vertex_data) + .unwrap() + .into() +}