added all I already made for this engine prototype
This commit is contained in:
@ -1,2 +1,8 @@
|
||||
# Engine.rs
|
||||
|
||||
|
||||
|
||||
The Goal of this repo is to make a simple engine like program in Rust
|
||||
|
||||
It is mainly for learning purposes and should be the base for a later, more fleshed out Engine made in Rust (with the possibility to create logic in Julia)
|
||||
|
||||
|
1
opengl_beginnings/.gitignore
vendored
Normal file
1
opengl_beginnings/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
8
opengl_beginnings/.idea/.gitignore
generated
vendored
Normal file
8
opengl_beginnings/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
7
opengl_beginnings/.idea/dictionaries/project.xml
generated
Normal file
7
opengl_beginnings/.idea/dictionaries/project.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="project">
|
||||
<words>
|
||||
<w>nebulix</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
8
opengl_beginnings/.idea/modules.xml
generated
Normal file
8
opengl_beginnings/.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/opengl_beginnings.iml" filepath="$PROJECT_DIR$/.idea/opengl_beginnings.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
11
opengl_beginnings/.idea/opengl_beginnings.iml
generated
Normal file
11
opengl_beginnings/.idea/opengl_beginnings.iml
generated
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
opengl_beginnings/.idea/vcs.xml
generated
Normal file
6
opengl_beginnings/.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
2099
opengl_beginnings/Cargo.lock
generated
Normal file
2099
opengl_beginnings/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
opengl_beginnings/Cargo.toml
Normal file
12
opengl_beginnings/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "opengl_beginnings"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
glutin = "0.32.3"
|
||||
glutin-winit = "0.5.0"
|
||||
raw-window-handle = "0.6.2"
|
||||
winit = {version = "0.30.11", features = ["rwh_06"]}
|
||||
glow = "0.16.0"
|
||||
nalgebra = "0.33.2"
|
2
opengl_beginnings/src/camera.rs
Normal file
2
opengl_beginnings/src/camera.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod perspective_camera;
|
||||
|
90
opengl_beginnings/src/camera/perspective_camera.rs
Normal file
90
opengl_beginnings/src/camera/perspective_camera.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use nalgebra::{Matrix3, Matrix4, Point3, Vector2, Vector3};
|
||||
use winit::dpi::LogicalSize;
|
||||
|
||||
pub struct PerspectiveCamera {
|
||||
position: Vector3<f32>,
|
||||
world_up: Vector3<f32>,
|
||||
yaw: f32,
|
||||
pitch: f32,
|
||||
front: Vector3<f32>,
|
||||
intrinsic_coordinates: Matrix3<f32>, // right (x) | up (y) | front (z)
|
||||
aspect_ratio: Option<f32>,
|
||||
|
||||
pub fov: f32,
|
||||
pub movement_speed: f32, // In general, all the movement stuff should be moved to a "GameObject" that has the camera as a component, as well a movement script. However, for this I would need to build an entire Entity Component System, so thats for way farther into the future
|
||||
}
|
||||
|
||||
impl PerspectiveCamera {
|
||||
pub fn new(position: Vector3<f32>, yaw: Option<f32>, pitch: Option<f32>, fov: Option<f32>, movement_speed: Option<f32>) -> PerspectiveCamera {
|
||||
let yaw = yaw.unwrap_or(-90.0);
|
||||
let pitch = pitch.unwrap_or(0.0);
|
||||
let world_up = Vector3::y_axis().into_inner();
|
||||
|
||||
let front = Vector3::new(
|
||||
yaw.to_radians().cos() * pitch.to_radians().cos(),
|
||||
pitch.to_radians().sin(),
|
||||
yaw.to_radians().sin() * pitch.to_radians().cos(),
|
||||
).normalize();
|
||||
let right = Vector3::cross(&front, &world_up).normalize();
|
||||
let up = Vector3::cross(&right, &front).normalize();
|
||||
|
||||
let coordinates = Matrix3::new(
|
||||
front.x, right.x, up.x,
|
||||
front.y, right.y, up.y,
|
||||
front.z, right.z, up.z);
|
||||
|
||||
Self {
|
||||
yaw,
|
||||
pitch,
|
||||
world_up,
|
||||
front,
|
||||
position,
|
||||
intrinsic_coordinates: coordinates,
|
||||
fov: fov.unwrap_or(45.0),
|
||||
movement_speed: movement_speed.unwrap_or(2.5),
|
||||
aspect_ratio: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_matrix(&self) -> Matrix4<f32> {
|
||||
Matrix4::look_at_rh(&Point3::from(self.position), &Point3::from(self.position + self.front), &self.world_up)
|
||||
}
|
||||
|
||||
pub fn projection_matrix(&self) -> Matrix4<f32> {
|
||||
Matrix4::new_perspective(self.aspect_ratio.unwrap(), self.fov, 0.1, 100.0) // TODO: make clipping plane configureable. probably through public property
|
||||
}
|
||||
|
||||
pub fn update_aspect(&mut self, logical_size: &LogicalSize<i32>) {
|
||||
self.aspect_ratio = Some(logical_size.width as f32 / logical_size.height as f32);
|
||||
}
|
||||
|
||||
pub fn process_movement(&mut self, delta_time: f32, movement_direction: Vector3<f32>) {
|
||||
let velocity = self.movement_speed * delta_time; // velocity not really the right term, but oh well
|
||||
self.position += self.intrinsic_coordinates * (movement_direction * velocity);
|
||||
}
|
||||
pub fn process_mouse(&mut self, offset: Vector2<f32>) {
|
||||
self.yaw += offset.x;
|
||||
self.pitch += offset.y;
|
||||
|
||||
if self.pitch > 89.0 {self.pitch = 89.0;}
|
||||
if self.pitch < -89.0 {self.pitch = -89.0;}
|
||||
|
||||
self.update_camera_vectors();
|
||||
}
|
||||
|
||||
fn update_camera_vectors(&mut self) {
|
||||
let front = Vector3::new(
|
||||
self.yaw.to_radians().cos() * self.pitch.to_radians().cos(),
|
||||
self.pitch.to_radians().sin(),
|
||||
self.yaw.to_radians().sin() * self.pitch.to_radians().cos(),
|
||||
).normalize();
|
||||
let right = Vector3::cross(&front, &self.world_up).normalize();
|
||||
let up = Vector3::cross(&right, &front).normalize();
|
||||
|
||||
self.front = front;
|
||||
self.intrinsic_coordinates = Matrix3::new(
|
||||
right.x, up.x, front.x,
|
||||
right.y, up.y, front.y,
|
||||
right.z, up.z, front.z);
|
||||
}
|
||||
}
|
5
opengl_beginnings/src/engine.rs
Normal file
5
opengl_beginnings/src/engine.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod nebulix;
|
||||
pub mod time;
|
||||
pub mod input;
|
||||
mod shader;
|
||||
mod mesh;
|
96
opengl_beginnings/src/engine/input.rs
Normal file
96
opengl_beginnings/src/engine/input.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use nalgebra::Vector2;
|
||||
use std::collections::HashSet;
|
||||
use winit::dpi::LogicalPosition;
|
||||
use winit::keyboard::{NamedKey, SmolStr};
|
||||
|
||||
enum Keys {
|
||||
/// Shift, Control, etc.
|
||||
SpecialKey(NamedKey),
|
||||
/// A, B, 1, 2, etc. case-insensitive. If case is required, also sheck for special key "Shift"
|
||||
StandardKey(SmolStr)
|
||||
}
|
||||
|
||||
pub struct Input {
|
||||
previous_mouse_position: Option<LogicalPosition<f32>>,
|
||||
current_mouse_position: Option<LogicalPosition<f32>>,
|
||||
mouse_offset: Vector2<f32>,
|
||||
mouse_left_screen: bool,
|
||||
pressed_keys: HashSet<String>,
|
||||
pressed_special_keys: HashSet<NamedKey>,
|
||||
|
||||
pub mouse_sensitivity: f32,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
/// param ```mouse_sensitivity``` Defaults to 0.1
|
||||
pub fn new(mouse_sensitivity: Option<f32>) -> Self {
|
||||
Self {
|
||||
previous_mouse_position: None,
|
||||
current_mouse_position: None,
|
||||
mouse_offset: Vector2::zeros(),
|
||||
mouse_left_screen: false,
|
||||
pressed_keys: HashSet::new(),
|
||||
pressed_special_keys: HashSet::new(),
|
||||
|
||||
mouse_sensitivity: mouse_sensitivity.unwrap_or(0.1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mouse_offset(&self) -> Vector2<f32> {
|
||||
self.mouse_offset
|
||||
}
|
||||
|
||||
/// Case insensitive
|
||||
pub fn is_key_pressed(&self, key: &str) -> bool {
|
||||
self.pressed_keys.contains(&key.to_uppercase())
|
||||
}
|
||||
|
||||
pub fn is_special_key_pressed(&self, key: NamedKey) -> bool {
|
||||
self.pressed_special_keys.contains(&key)
|
||||
}
|
||||
|
||||
pub(crate) fn mouse_moved(&mut self, mouse_position: LogicalPosition<f32>) {
|
||||
if self.mouse_left_screen {
|
||||
self.previous_mouse_position = Some(mouse_position);
|
||||
}
|
||||
else {
|
||||
self.previous_mouse_position = match self.current_mouse_position {
|
||||
None => None,
|
||||
Some(mp) => Some(mp)
|
||||
};
|
||||
}
|
||||
|
||||
self.current_mouse_position = Some(mouse_position);
|
||||
self.calculate_mouse_offset();
|
||||
}
|
||||
|
||||
pub(crate) fn mouse_left_screen(&mut self) {
|
||||
self.mouse_left_screen = true;
|
||||
}
|
||||
|
||||
pub(crate) fn special_key_pressed(&mut self, key: NamedKey) {
|
||||
self.pressed_special_keys.insert(key);
|
||||
}
|
||||
pub(crate) fn special_key_released(&mut self, key: NamedKey) {
|
||||
self.pressed_special_keys.remove(&key);
|
||||
}
|
||||
|
||||
pub(crate) fn key_pressed(&mut self, key: SmolStr) {
|
||||
self.pressed_keys.insert(key.to_uppercase());
|
||||
}
|
||||
pub(crate) fn key_released(&mut self, key: SmolStr) {
|
||||
self.pressed_keys.remove(&key.to_uppercase());
|
||||
}
|
||||
|
||||
fn calculate_mouse_offset(&mut self) {
|
||||
match self.previous_mouse_position {
|
||||
None => {}
|
||||
Some(prev_pos) => { // previous can only have a value if current also has one. so we only need to match previous
|
||||
self.mouse_offset = Vector2::new((self.current_mouse_position.unwrap().x - prev_pos.x),
|
||||
(self.current_mouse_position.unwrap().y - prev_pos.y)) * self.mouse_sensitivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
98
opengl_beginnings/src/engine/mesh.rs
Normal file
98
opengl_beginnings/src/engine/mesh.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use glow::{Context, HasContext, NativeBuffer, NativeVertexArray};
|
||||
use nalgebra::Vector3;
|
||||
|
||||
// TODO: create a "MeshRenderer" which contains a mesh and a shader
|
||||
// the MeshRenderer uses the mesh of this file as the data for rendering and ensures the shader is enabled so it is rendered correctly
|
||||
// Down the line the MeshRenderer should have a material. The material then holds the data that is needed by the shader (so the actual texture or data for variables inside the shader)
|
||||
// End Goal: Shader belongs to Material; Material belongs to MeshRenderer; Mesh belongs to MeshRenderer; MeshRenderer renders the mesh using the shader and data provided by the material
|
||||
|
||||
struct Mesh {
|
||||
vertices: Vec<Vector3<f32>>,
|
||||
normals: Vec<Vector3<f32>>,
|
||||
indices: Vec<u32>,
|
||||
// TODO: implement optional uv coordinates, which are added to the mesh data in the function "generate" if they are actually set
|
||||
|
||||
vao: Option<NativeVertexArray>,
|
||||
vbo: Option<NativeBuffer>,
|
||||
ebo: Option<NativeBuffer>,
|
||||
regenerate: bool // TODO: Set to "true" everytime vertices or indices are changed
|
||||
}
|
||||
|
||||
impl Mesh {
|
||||
pub fn new(vertices: Vec<Vector3<f32>>, indices: Vec<u32>) -> Self {
|
||||
let num_vertices = vertices.len();
|
||||
Self {
|
||||
vertices,
|
||||
indices,
|
||||
normals: Vec::with_capacity(num_vertices),
|
||||
vao: None,
|
||||
vbo: None,
|
||||
ebo: None,
|
||||
regenerate: true
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render(&mut self, gl: &Context) {
|
||||
if self.regenerate {
|
||||
self.generate(gl)
|
||||
}
|
||||
|
||||
unsafe {
|
||||
gl.bind_vertex_array(self.vao);
|
||||
gl.draw_elements(glow::TRIANGLES, self.indices.len() as i32, glow::UNSIGNED_INT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn generate(&mut self, gl: &Context) {
|
||||
self.regenerate = false;
|
||||
|
||||
unsafe {
|
||||
match self.vao {
|
||||
None => {self.vao = Some(gl.create_vertex_array().unwrap())}
|
||||
_ => {}
|
||||
}
|
||||
match self.vbo {
|
||||
None => {self.vbo = Some(gl.create_buffer().unwrap())}
|
||||
_ => {}
|
||||
}
|
||||
match self.ebo {
|
||||
None => {self.ebo = Some(gl.create_buffer().unwrap())}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
gl.bind_vertex_array(self.vao);
|
||||
|
||||
let mut mesh_data: Vec<f32> = Vec::with_capacity(self.vertices.len() * 3 + self.normals.len() * 3);
|
||||
|
||||
for i in 0..self.vertices.len() {
|
||||
mesh_data.push(self.vertices[i].x);
|
||||
mesh_data.push(self.vertices[i].y);
|
||||
mesh_data.push(self.vertices[i].z);
|
||||
|
||||
mesh_data.push(self.normals[i].x);
|
||||
mesh_data.push(self.normals[i].y);
|
||||
mesh_data.push(self.normals[i].z);
|
||||
}
|
||||
|
||||
Self::fill_buffer(mesh_data.as_ptr() as *const u8, mesh_data.len(), self.vbo, gl);
|
||||
Self::fill_buffer(self.indices.as_ptr() as *const u8, self.indices.len(), self.ebo, gl);
|
||||
|
||||
// vertices
|
||||
gl.enable_vertex_attrib_array(0);
|
||||
gl.vertex_attrib_pointer_f32(0, 3, glow::FLOAT, false, 6 * size_of::<f32>() as i32, 0); // stride is (6 * size_of::<f32>) because 3 for vertices and 3 for normals
|
||||
|
||||
// normals
|
||||
gl.enable_vertex_attrib_array(1);
|
||||
gl.vertex_attrib_pointer_f32(0, 3, glow::FLOAT, false, 6 * size_of::<f32>() as i32, 0); // stride is (6 * size_of::<f32>) because 3 for vertices and 3 for normals
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn fill_buffer(data: *const u8, num_elements: usize, dst_buffer: Option<NativeBuffer>, gl: &Context) {
|
||||
unsafe {
|
||||
let mesh_data_u8: &[u8] = std::slice::from_raw_parts(data, num_elements * size_of::<f32>());
|
||||
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, dst_buffer);
|
||||
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, mesh_data_u8, glow::STATIC_DRAW);
|
||||
}
|
||||
}
|
||||
}
|
208
opengl_beginnings/src/engine/nebulix.rs
Normal file
208
opengl_beginnings/src/engine/nebulix.rs
Normal file
@ -0,0 +1,208 @@
|
||||
use crate::camera::perspective_camera::PerspectiveCamera;
|
||||
use crate::engine::input::Input;
|
||||
use crate::engine::time::Time;
|
||||
use crate::Extension;
|
||||
use glow::{Context, HasContext};
|
||||
use glutin::config::{ConfigTemplateBuilder, GlConfig};
|
||||
use glutin::context::{ContextApi, ContextAttributesBuilder, NotCurrentContext, NotCurrentGlContext, PossiblyCurrentContext};
|
||||
use glutin::display::{GetGlDisplay, GlDisplay};
|
||||
use glutin::prelude::GlSurface;
|
||||
use glutin::surface::{Surface, SwapInterval, WindowSurface};
|
||||
use glutin_winit::{DisplayBuilder, GlWindow};
|
||||
use nalgebra::{Matrix4, Vector3};
|
||||
use raw_window_handle::HasWindowHandle;
|
||||
use std::num::NonZeroU32;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event::{ElementState, WindowEvent};
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
use winit::keyboard::{Key, NamedKey};
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
pub struct Nebulix {
|
||||
window: Option<Window>,
|
||||
gl: Option<Context>,
|
||||
gl_context: Option<PossiblyCurrentContext>,
|
||||
gl_surface: Option<Surface<WindowSurface>>,
|
||||
camera: PerspectiveCamera,
|
||||
time: Option<Time>,
|
||||
input: Input,
|
||||
}
|
||||
|
||||
impl Nebulix {
|
||||
pub fn new() -> Nebulix {
|
||||
let input = Input::new(None);
|
||||
|
||||
Self {
|
||||
window: None,
|
||||
gl: None,
|
||||
gl_context: None,
|
||||
gl_surface: None,
|
||||
camera: PerspectiveCamera::new(
|
||||
Vector3::zeros(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None
|
||||
),
|
||||
time: None,
|
||||
input,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler for Nebulix {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let(gl, gl_context, gl_surface, window) = create_window_with_gl_context(event_loop);
|
||||
|
||||
unsafe {
|
||||
gl.clear_color(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
self.camera.update_aspect(&window.inner_size().to_logical(window.scale_factor()));
|
||||
self.window = Some(window);
|
||||
self.gl = Some(gl);
|
||||
self.gl_context = Some(gl_context);
|
||||
self.gl_surface = Some(gl_surface);
|
||||
self.time = Some(Time::new());
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {event_loop.exit()}
|
||||
WindowEvent::Destroyed => {}
|
||||
WindowEvent::Focused(_) => {}
|
||||
WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer } => {
|
||||
self.camera.update_aspect(&self.window.as_ref().unwrap().inner_size().to_logical(scale_factor));
|
||||
}
|
||||
WindowEvent::Resized(size) => {
|
||||
self.gl_surface.as_ref().unwrap()
|
||||
.resize(self.gl_context.as_ref().unwrap(), NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap());
|
||||
|
||||
let logical_size: LogicalSize<i32> = size.to_logical(self.window.as_ref().unwrap().scale_factor());
|
||||
unsafe {
|
||||
self.gl.as_ref().unwrap().viewport(0, 0, logical_size.width, logical_size.height)
|
||||
}
|
||||
self.camera.update_aspect(&logical_size);
|
||||
}
|
||||
// Input system stuff
|
||||
WindowEvent::KeyboardInput { event, ..} => {
|
||||
match event.logical_key {
|
||||
Key::Named(special) => { match event.state {
|
||||
ElementState::Pressed => {self.input.special_key_pressed(special)}
|
||||
ElementState::Released => {self.input.special_key_released(special)}
|
||||
}}
|
||||
Key::Character(char) => { match event.state {
|
||||
ElementState::Pressed => {self.input.key_pressed(char)}
|
||||
ElementState::Released => {self.input.key_released(char)}
|
||||
}}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
WindowEvent::ModifiersChanged(_) => {} // if lshift etc has been pressed
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
self.input.mouse_moved(position.to_logical(self.window.as_ref().unwrap().scale_factor()));
|
||||
}
|
||||
WindowEvent::CursorEntered { .. } => {}
|
||||
WindowEvent::CursorLeft { .. } => {self.input.mouse_left_screen()}
|
||||
WindowEvent::MouseWheel { .. } => {}
|
||||
WindowEvent::MouseInput { .. } => {}
|
||||
// Update loop
|
||||
WindowEvent::RedrawRequested => {
|
||||
self.time.as_mut().unwrap().update_delta_time();
|
||||
|
||||
self.camera.process_mouse(self.input.get_mouse_offset());
|
||||
let mut move_direction: Vector3<f32> = Vector3::zeros();
|
||||
|
||||
if self.input.is_key_pressed("a") { move_direction.x -= 1.0f32; }
|
||||
if self.input.is_key_pressed("d") { move_direction.x += 1.0f32; }
|
||||
if self.input.is_key_pressed("w") { move_direction.z -= 1.0f32; }
|
||||
if self.input.is_key_pressed("s") { move_direction.z += 1.0f32; }
|
||||
if self.input.is_special_key_pressed(NamedKey::Space) { move_direction.y += 1.0f32; }
|
||||
if self.input.is_special_key_pressed(NamedKey::Control) { move_direction.z += 1.0f32; }
|
||||
|
||||
self.camera.process_movement(self.time.as_mut().unwrap().delta_time(), move_direction.normalize_checked());
|
||||
|
||||
let gl = self.gl.as_ref().unwrap();
|
||||
let gl_surface = self.gl_surface.as_ref().unwrap();
|
||||
let gl_context = self.gl_context.as_ref().unwrap();
|
||||
|
||||
let model_matrix = Matrix4::new_translation(&Vector3::new(0.0f32, 0.0f32, -3.0f32));
|
||||
let view_matrix = self.camera.view_matrix();
|
||||
let projection_matrix = self.camera.projection_matrix();
|
||||
|
||||
unsafe {
|
||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
// Custom code HERE
|
||||
// TODO: Next step is:
|
||||
// - dynamically generate a mesh
|
||||
// - use a shader to render it
|
||||
// - and fly around in the scene
|
||||
// After I confirmed that the above is working -> add textures
|
||||
|
||||
gl_surface.swap_buffers(&gl_context).unwrap();
|
||||
self.window.as_ref().unwrap().request_redraw();
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_window_with_gl_context(event_loop: &ActiveEventLoop) -> (Context, PossiblyCurrentContext, Surface<WindowSurface>, Window) {
|
||||
let window_attributes = Window::default_attributes().with_transparent(true);
|
||||
let template = ConfigTemplateBuilder::new().with_alpha_size(8);
|
||||
let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes));
|
||||
|
||||
let (window, gl_config) = display_builder.build(event_loop, template, |configs| {
|
||||
configs.reduce(|accum, config| {
|
||||
if config.num_samples() > accum.num_samples() {
|
||||
config
|
||||
}
|
||||
else {
|
||||
accum
|
||||
}
|
||||
}).unwrap()
|
||||
}).unwrap();
|
||||
|
||||
let raw_window_handle = window
|
||||
.as_ref()
|
||||
.and_then(|window| window.window_handle().map(|handle| handle.as_raw()).ok());
|
||||
|
||||
let gl_display = gl_config.display();
|
||||
|
||||
let context_attributes = ContextAttributesBuilder::new()
|
||||
.with_context_api(ContextApi::OpenGl(Some(glutin::context::Version {
|
||||
major: 3,
|
||||
minor: 3,
|
||||
})))
|
||||
.build(raw_window_handle);
|
||||
|
||||
let not_current_gl_context: NotCurrentContext;
|
||||
unsafe {
|
||||
not_current_gl_context = gl_display.create_context(&gl_config, &context_attributes).expect("OpenGL context creation failed");
|
||||
};
|
||||
|
||||
let window = window.unwrap();
|
||||
|
||||
let attrs = window
|
||||
.build_surface_attributes(<_>::default())
|
||||
.expect("Failed to build surface attributes");
|
||||
let gl_surface = unsafe {
|
||||
gl_config.display().create_window_surface(&gl_config, &attrs).unwrap()
|
||||
};
|
||||
|
||||
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
|
||||
let gl: Context;
|
||||
unsafe {
|
||||
gl = Context::from_loader_function_cstr(|s| gl_display.get_proc_address(s));
|
||||
};
|
||||
|
||||
gl_surface
|
||||
.set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) // vsync on
|
||||
// .set_swap_interval(&gl_context, SwapInterval::DontWait) // vsync off (let her rip)
|
||||
.unwrap();
|
||||
|
||||
(gl, gl_context, gl_surface, window)
|
||||
}
|
102
opengl_beginnings/src/engine/shader.rs
Normal file
102
opengl_beginnings/src/engine/shader.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use glow::{Context, HasContext, NativeProgram, NativeShader};
|
||||
use nalgebra::{Matrix4, Vector3};
|
||||
|
||||
enum ShaderType {
|
||||
Vertex,
|
||||
Fragment,
|
||||
}
|
||||
impl ShaderType {
|
||||
fn to_u32(&self) -> u32 {
|
||||
match self {
|
||||
ShaderType::Vertex => {glow::VERTEX_SHADER}
|
||||
ShaderType::Fragment => {glow::FRAGMENT_SHADER}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Shader<'a> {
|
||||
gl: &'a Context,
|
||||
program: NativeProgram,
|
||||
}
|
||||
|
||||
impl<'a> Shader<'a> {
|
||||
pub fn new(&self, gl: &'a Context, vertex_shader_file: String, fragment_shader_file: String) -> Self {
|
||||
let vertex_shader = self.compile_shader(vertex_shader_file, ShaderType::Vertex);
|
||||
let fragment_shader = self.compile_shader(fragment_shader_file, ShaderType::Fragment);
|
||||
let program = self.create_program(vertex_shader, fragment_shader);
|
||||
|
||||
Self {
|
||||
gl,
|
||||
program,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_shader(&self) {
|
||||
unsafe {
|
||||
self.gl.use_program(Some(self.program));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_i32(&self, name: &str, value: i32) {
|
||||
unsafe {
|
||||
let location = self.gl.get_uniform_location(self.program, name);
|
||||
self.gl.uniform_1_i32(location.as_ref(), value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_f32(&self, name: &str, value: f32) {
|
||||
unsafe {
|
||||
let location = self.gl.get_uniform_location(self.program, name);
|
||||
self.gl.uniform_1_f32(location.as_ref(), value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vector3_f32(&self, name: &str, value: Vector3<f32>) {
|
||||
unsafe {
|
||||
let location = self.gl.get_uniform_location(self.program, name);
|
||||
self.gl.uniform_3_f32(location.as_ref(), value.x, value.y, value.z)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_matrix4_f32(&self, name: &str, value: Matrix4<f32>) {
|
||||
unsafe {
|
||||
let location = self.gl.get_uniform_location(self.program, name);
|
||||
self.gl.uniform_matrix_4_f32_slice(location.as_ref(), false, value.as_slice());
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_shader(&self, shader_code: String, shader_type: ShaderType) -> NativeShader {
|
||||
unsafe {
|
||||
let shader = self.gl.create_shader(shader_type.to_u32()).unwrap();
|
||||
|
||||
self.gl.shader_source(shader, shader_code.as_str());
|
||||
self.gl.compile_shader(shader);
|
||||
|
||||
if self.gl.get_shader_compile_status(shader) {
|
||||
panic!("{}", self.gl.get_shader_info_log(shader));
|
||||
}
|
||||
|
||||
shader
|
||||
}
|
||||
}
|
||||
|
||||
fn create_program(&self, vertex_shader: NativeShader, fragment_shader: NativeShader) -> NativeProgram {
|
||||
unsafe {
|
||||
let program = self.gl.create_program().unwrap();
|
||||
self.gl.attach_shader(program, vertex_shader);
|
||||
self.gl.attach_shader(program, fragment_shader);
|
||||
self.gl.link_program(program);
|
||||
|
||||
if(!self.gl.get_program_link_status(program)) {
|
||||
panic!("Error while linking shaders with message: \n{}", self.gl.get_program_info_log(program));
|
||||
}
|
||||
|
||||
self.gl.detach_shader(program, vertex_shader);
|
||||
self.gl.detach_shader(program, fragment_shader);
|
||||
self.gl.delete_shader(vertex_shader);
|
||||
self.gl.delete_shader(fragment_shader);
|
||||
|
||||
program
|
||||
}
|
||||
}
|
||||
}
|
25
opengl_beginnings/src/engine/time.rs
Normal file
25
opengl_beginnings/src/engine/time.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use std::time::Instant;
|
||||
|
||||
pub struct Time {
|
||||
previous_time: Instant,
|
||||
delta_time: f32,
|
||||
}
|
||||
|
||||
impl Time {
|
||||
pub fn delta_time(&mut self) -> f32 {
|
||||
self.delta_time
|
||||
}
|
||||
|
||||
pub(crate) fn new() -> Time {
|
||||
Self {
|
||||
previous_time: Instant::now(),
|
||||
delta_time: 0.0,
|
||||
}
|
||||
}
|
||||
/// Call this at the start of each frame
|
||||
pub(crate) fn update_delta_time(&mut self) {
|
||||
let frame_start_time = Instant::now();
|
||||
self.delta_time = (frame_start_time - self.previous_time).as_secs_f32();
|
||||
self.previous_time = frame_start_time;
|
||||
}
|
||||
}
|
38
opengl_beginnings/src/main.rs
Normal file
38
opengl_beginnings/src/main.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use crate::engine::nebulix::Nebulix;
|
||||
use nalgebra::Vector3;
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
|
||||
mod camera;
|
||||
mod engine;
|
||||
|
||||
trait Extension {
|
||||
fn normalize_checked(&self) -> Self;
|
||||
fn normalize_checked_mut(&mut self);
|
||||
}
|
||||
impl Extension for Vector3<f32> {
|
||||
/// If the vector is equal to Vector3::zeroes() it will be returned. Otherwise, a normalised version of it will be returned, without mutating the vector itself.
|
||||
fn normalize_checked(&self) -> Self {
|
||||
if self.eq(&Vector3::zeros()) {
|
||||
*self
|
||||
} else {
|
||||
self.normalize()
|
||||
}
|
||||
}
|
||||
|
||||
/// If the vector is equal to Vector3::zeroes() nothing will be done. Otherwise, this vector gets normalised in place.
|
||||
fn normalize_checked_mut(&mut self) {
|
||||
if self != &Vector3::zeros() {
|
||||
self.normalize_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
event_loop.set_control_flow(ControlFlow::Poll);
|
||||
|
||||
let mut engine = Nebulix::new();
|
||||
event_loop.run_app(&mut engine).unwrap()
|
||||
}
|
Reference in New Issue
Block a user