diff --git a/Cargo.toml b/Cargo.toml index 27e0416..409ed65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ serde_derive = "1.0.114" toml = "0.5.6" gilrs = "0.7.4" gltf = "0.15.2" -mgf = "1.4.0" [[bin]] name = "converter" diff --git a/levels/test.lvl b/levels/test.lvl index 3255d1f..3485bbc 100644 --- a/levels/test.lvl +++ b/levels/test.lvl @@ -8,25 +8,6 @@ } ], "objects": [ - { - "mesh_index": 0, - "position": [ - 0.0, - 0.0, - 0.0 - ], - "rotation": [ - 1.0, - 0.0, - 0.0, - 0.0 - ], - "scale": [ - 1.0, - 1.0, - 1.0 - ] - }, { "mesh_index": 1, "position": [ @@ -50,15 +31,15 @@ "player": { "mesh_index": null, "position": [ - -3.0608184, - -1.0, - -3.0598369 + 0, + 0, + 5.0 ], "rotation": [ - 0.15801395, - 0.031636234, - 0.96772224, - 0.19375017 + 1, + 0, + 0, + 0 ], "scale": [ 1.0, diff --git a/src/game/mod.rs b/src/game/mod.rs index 47162a1..ba1719d 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,7 +1,7 @@ use std::time::SystemTime; -use cgmath::{Deg, InnerSpace, Quaternion, Rotation3, vec3}; +use cgmath::{Deg, Euler, Quaternion, vec3}; use winit::event::Event; use level::{load_level, save_level}; @@ -66,6 +66,8 @@ impl Game for TestGame { if self.input.button_down("print_framerate") { println!("{:.0} ms / {:.0} FPS", frame_time * 1000.0, 1.0 / frame_time); + print_quat_as_euler(self.player.camera.rotation); + println!(); } if self.input.button_just_pressed("test") { @@ -77,7 +79,7 @@ impl Game for TestGame { if !self.paused { self.player.update(frame_time, &self.input, renderer); } - self.game_objects[1].get_game_object_mut(renderer).unwrap().rotation = Quaternion::from_angle_y(Deg(time * -20.)).normalize(); + // self.game_objects[1].get_game_object_mut(renderer).unwrap().rotation = Quaternion::from_angle_y(Deg(time * -20.)).normalize(); // End frame self.last_time = time; @@ -171,3 +173,8 @@ impl TestGame { renderer.clear_all(); } } + +pub fn print_quat_as_euler(quat: Quaternion) { + let euler = Euler::from(quat); + print!("({:?},{:?},{:?})", Deg::from(euler.x), Deg::from(euler.y), Deg::from(euler.z)); +} \ No newline at end of file diff --git a/src/game/player.rs b/src/game/player.rs index a23b7a5..57375a3 100644 --- a/src/game/player.rs +++ b/src/game/player.rs @@ -1,9 +1,11 @@ -use cgmath::{Deg, InnerSpace, Matrix4, One, Quaternion, Rad, Rotation, Rotation3, SquareMatrix, Vector3, vec3, vec4}; -use mgf::Intersection; +use cgmath::{Deg, InnerSpace, Matrix4, One, Quaternion, Rad, Rotation, Rotation3, SquareMatrix, Vector3, Vector4, vec3, vec4}; +use vulkano::buffer::TypedBufferAccess; use crate::game::player::PlayerMovementMode::{FirstPerson, Flying}; use crate::input::InputState; +use crate::vulkan::Mesh; +use crate::vulkan::gameobject::GameObject; use crate::vulkan::{ gameobject::Updatable, VulkanRenderer @@ -29,10 +31,12 @@ impl Camera { } pub fn update(&mut self, renderer: &mut VulkanRenderer) { - // Camera stuff - let mut pos = self.position.clone(); - pos.y *= -1.0; - self.view = Matrix4::from(self.rotation) * Matrix4::from_translation(pos); + // Don't let floating point error accumulate in rotation + debug_assert!(f32::abs(1.0 - self.rotation.magnitude()) < 0.00001 as f32); + self.rotation = self.rotation.normalize(); + + // Create matrices + self.view = Matrix4::from(self.rotation) * Matrix4::from_translation(self.position * -1.); self.proj = cgmath::perspective( Rad::from(Deg(self.fov_y)), @@ -40,19 +44,17 @@ impl Camera { 0.1, 100.0 ); + // Why? self.proj.y.y *= -1.0; + // Upload renderer.game_data.line_push_constants.view = self.view.into(); renderer.game_data.line_push_constants.projection = self.proj.into(); } - pub fn transform_vector(&self, vec: Vector3) -> Vector3 { - self.rotation.invert().rotate_vector(vec) - } - - pub fn viewport_pos_to_ray(&self, viewport_pos: [f64; 2], viewport_dimensions: [u32; 2]) -> Option> { - let normalized_x = (2. * viewport_pos[0]) as f32 / viewport_dimensions[0] as f32 - 1.; - let normalized_y = (2. * viewport_pos[1]) as f32 / viewport_dimensions[1] as f32 - 1.; + pub fn viewport_pos_to_ray_direction(&self, viewport_pos: [f64; 2], viewport_dimensions: [u32; 2]) -> Option> { + let normalized_x = 2. * (viewport_pos[0] as f32 / viewport_dimensions[0] as f32) - 1.; + let normalized_y = 2. * (viewport_pos[1] as f32 / viewport_dimensions[1] as f32) - 1.; let click_start_screen = vec4(normalized_x, normalized_y, -1.0, 1.0); let click_end_screen = vec4(normalized_x, normalized_y, 0.0, 1.0); @@ -73,8 +75,17 @@ impl Camera { ray_end_world /= ray_end_world.w; let ray_dir_world = (ray_end_world - ray_start_world).normalize(); + Some(ray_dir_world.truncate()) } + + pub fn local_to_world(&self, vec:Vector3) -> Vector3 { + self.rotation.invert().rotate_vector(vec) + } + + pub fn world_to_local(&self, vec:Vector3) -> Vector3 { + self.rotation.rotate_vector(vec) + } } #[derive(PartialEq)] @@ -88,21 +99,77 @@ pub struct Player { pub movement_mode: PlayerMovementMode, pub movement_speed: f32, pub look_sensitivity: f32, - pub height: f32 + pub height: f32, + pub x_look: f32, + pub y_look: f32 } impl Player { pub fn new() -> Player { Player { camera: Camera::new(), - movement_mode: FirstPerson, + movement_mode: Flying, movement_speed: 3.0, look_sensitivity: 30.0, - height: 1.0 + height: 1.0, + x_look: 0.0, + y_look: 0.0 } } } +pub fn vec4_from_pos(pos: [f32; 3]) -> Vector4 { + vec4(pos[0], pos[1], pos[2], 1.0) +} + +pub fn intersection_distance(ray_origin: Vector3, ray_direction: Vector3, mesh: &Mesh, game_object: &GameObject) -> Option { + let index_lock = mesh.index_buffer.read().unwrap(); + let vertex_lock = mesh.vertex_buffer.read().unwrap(); + (0..mesh.index_buffer.len() / 3).map(|tri_idx| { + let vtx_a = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx * 3 ] as usize].position); + let vtx_b = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx * 3 + 1] as usize].position); + let vtx_c = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx * 3 + 2] as usize].position); + intersect_triangle(ray_origin, ray_direction, vtx_a.truncate().into(), vtx_b.truncate().into(), vtx_c.truncate().into()) + }).fold(None, |acc, x| { + if let Some(smallest) = acc { + if let Some(new) = x { + if new < smallest { + Some(new) + } else { + Some(smallest) + } + } else { + acc + } + } else { + x + } + }) +} + +// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm +pub fn intersect_triangle(ray_origin: Vector3, ray_direction: Vector3, vtx_a: [f32; 3], vtx_b: [f32; 3], vtx_c: [f32; 3]) -> Option { + let edge_1 = vec3(vtx_b[0] - vtx_a[0], vtx_b[1] - vtx_a[1], vtx_b[2] - vtx_a[2]); + let edge_2 = vec3(vtx_c[0] - vtx_a[0], vtx_c[1] - vtx_a[1], vtx_c[2] - vtx_a[2]); + let h = ray_direction.cross(edge_2); + let a = edge_1.dot(h); + if a > -f32::EPSILON && a < f32::EPSILON { return None; } + + let f = 1. / a; + let s: Vector3 = ray_origin - Vector3::from(vtx_a); + let u = f * s.dot(h); + if u < 0. || u > 1. { return None; } + + let q = s.cross(edge_1); + let v = f * ray_direction.dot(q); + if v < 0. || u + v > 1. { return None; } + + let t = f * edge_2.dot(q); + if t > f32::EPSILON { return Some(t); } + + None +} + impl Updatable for Player { fn update(&mut self, delta_time: f32, input: &InputState, renderer: &mut VulkanRenderer) { // Edit mode @@ -115,52 +182,32 @@ impl Updatable for Player { } if input.button_just_pressed("select") { - let ray_direction = self.camera.viewport_pos_to_ray(input.mouse_position, renderer.game_data.dimensions).unwrap(); - let ray_origin: [f32; 3] = self.camera.position.into(); - let ray = mgf::Ray::new(ray_origin.into(), ray_direction); - - let mut shortest_hit: Option<(usize, Intersection)> = None; - let mut shortest_hit_distance: f32 = f32::MAX; - - for i in 0..renderer.game_data.game_objects.len() { - let obj = &renderer.game_data.game_objects[i]; - let collision_mesh = &renderer.game_data.meshes[obj.mesh_index].collision_mesh; - collision_mesh.bvh.raytrace(&ray, |_, is| { - if is.t < shortest_hit_distance { - shortest_hit = Some((i, is)); - shortest_hit_distance = is.t; - } - }); - } - - if let Some((obj_index, intersection)) = shortest_hit { - let obj = &mut renderer.game_data.game_objects[obj_index]; - obj.is_selected = !obj.is_selected; - println!("Hit: {:?}, {:?}", obj.mesh_index, intersection); + let ray_direction = self.camera.viewport_pos_to_ray_direction(input.mouse_position, renderer.game_data.dimensions).unwrap(); + for game_object in &mut renderer.game_data.game_objects { + let mesh = &renderer.game_data.meshes[game_object.mesh_index]; + if let Some(dist) = intersection_distance(self.camera.position, ray_direction, mesh, game_object) { + game_object.is_selected = !game_object.is_selected; + } } } // Rotation - self.camera.rotation = self.camera.rotation * Quaternion::from_angle_y(Deg(input.get_axis("look_horizontal") * delta_time * self.look_sensitivity)); - self.camera.rotation = Quaternion::from_angle_x(Deg(input.get_axis("look_vertical") * delta_time * self.look_sensitivity)) * self.camera.rotation; + self.x_look += input.get_axis("look_vertical") * delta_time * self.look_sensitivity; + self.y_look += input.get_axis("look_horizontal") * delta_time * self.look_sensitivity; + let x_rot = Quaternion::from_angle_x(Deg(self.x_look)); + let y_rot = Quaternion::from_angle_y(Deg(self.y_look)); + self.camera.rotation = x_rot * y_rot; // Movement + let local_input = vec3(input.get_axis("move_sideways"), 0.0, -input.get_axis("move_forward")) * self.movement_speed * delta_time; + if self.movement_mode == FirstPerson { - + let mut world_input = self.camera.local_to_world(local_input); + world_input.y = 0.0; + self.camera.position += world_input.normalize(); self.camera.position.y = self.height; - - let mut forward_vector = self.camera.transform_vector(vec3(0.0, 0.0, 1.0)); - forward_vector.y = 0.0; - forward_vector = forward_vector.normalize(); - self.camera.position += forward_vector * delta_time * input.get_axis("move_forward") * self.movement_speed; - - let right_vector = forward_vector.cross(vec3(0.0, 1.0, 0.0)); - self.camera.position += right_vector * delta_time * input.get_axis("move_sideways") * self.movement_speed; } else if self.movement_mode == Flying { - self.camera.position += self.camera.transform_vector(vec3( - input.get_axis("move_sideways") * -self.movement_speed, - 0.0, - input.get_axis("move_forward") * self.movement_speed)) * delta_time; + self.camera.position += self.camera.local_to_world(local_input); } // Update diff --git a/src/main.rs b/src/main.rs index fde8a9c..63b77ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,16 @@ extern crate serde_json; - use vulkan::gameobject::GameObject; - - use crate::config::{LogConfig, RenderConfig}; use crate::game::TestGame; - use crate::vulkan::{LinePoint, VulkanRenderer}; mod vulkan; mod input; mod config; mod game; +mod tests; fn main() { let log_config = LogConfig::from_file("config/log.toml"); diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..56f4c47 --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,39 @@ +#[cfg(test)] +mod tests { + use cgmath::{vec3}; + + use crate::game::player::{intersect_triangle}; + + fn epsilon_eq(f1: f32, f2: f32) { + assert!(f32::abs(f1 - f2) < f32::EPSILON, "{} == {}", f1, f2); + } + + fn epsilon_eq_option(o1: Option, o2: Option) { + if let Some(f1) = o1 { + if let Some(f2) = o2 { + epsilon_eq(f1, f2); + } else { + panic!("Some({}) == None", f1); + } + } else if let Some(f2) = o2 { + panic!("None == Some({})", f2); + } + } + + #[test] + fn triangle_intersection() { + let zero = vec3(0.0, 0.0, 0.0); + let a = [2.0, 0.5, 0.5]; + let b = [2.0, -0.5, 0.5]; + let c = [2.0, 0.0, -0.5]; + + let dist1 = intersect_triangle(zero, vec3(1.0, 0.0, 0.0), a, b, c); + epsilon_eq_option(dist1, Some(2.0)); + + let dist2 = intersect_triangle(zero, vec3(0.0, 0.0, 1.0), a, b, c); + epsilon_eq_option(dist2, None); + + let dist3 = intersect_triangle(zero, vec3(0.9950371902, 0.09950371902, 0.0), a, b, c); + epsilon_eq_option(dist3, Some(2.0099751242)); + } +} \ No newline at end of file diff --git a/src/vulkan/gameobject.rs b/src/vulkan/gameobject.rs index 2a1e914..e90d061 100644 --- a/src/vulkan/gameobject.rs +++ b/src/vulkan/gameobject.rs @@ -57,7 +57,7 @@ impl GameObject { pub fn get_push_constants(&self) -> vs::ty::PushConstants { vs::ty::PushConstants { model: self.get_model_matrix().into(), - is_selected: if self.is_selected { 0 } else { 1 }, + is_selected: if self.is_selected { 1 } else { 0 }, } } diff --git a/src/vulkan/mod.rs b/src/vulkan/mod.rs index 060cbbb..d522e46 100644 --- a/src/vulkan/mod.rs +++ b/src/vulkan/mod.rs @@ -70,7 +70,6 @@ pub struct Mesh { pub vertex_buffer: Arc>, pub index_buffer: Arc>, pub original_path: String, - pub collision_mesh: mgf::Mesh, } #[derive(Debug, Clone)] @@ -245,7 +244,7 @@ impl VulkanRenderer { let pipelines: Vec> = vec![ Box::new(DefaultShader::new(device.clone(), render_pass.clone())), - Box::new(LineShader::new(device.clone(), render_pass.clone(), line_vertex_buffer.clone())), + // Box::new(LineShader::new(device.clone(), render_pass.clone(), line_vertex_buffer.clone())), ]; // Dynamic viewports allow us to recreate just the viewport when the window is resized @@ -344,7 +343,7 @@ impl VulkanRenderer { self.pipelines = vec![ Box::new(DefaultShader::new(self.device.clone(), self.render_pass.clone())), - Box::new(LineShader::new(self.device.clone(), self.render_pass.clone(), self.line_vertex_buffer.clone())), + // Box::new(LineShader::new(self.device.clone(), self.render_pass.clone(), self.line_vertex_buffer.clone())), ]; self.swapchain = new_swapchain; @@ -413,18 +412,18 @@ impl VulkanRenderer { } pub fn upload_mesh(self: &mut Self, mesh: CPUMesh, original_path: String) -> usize { - let mut collision_mesh = mgf::Mesh::new(); - mesh.vertices.iter().for_each(|v| { - collision_mesh.push_vert(v.position.into()); - }); // TODO: convert vert pos to world space - for i in (0..mesh.indices.len()).step_by(3) { - collision_mesh.push_face((mesh.indices[i] as usize, mesh.indices[i + 1] as usize, mesh.indices[i + 2] as usize)); - } + // let mut collision_mesh = mgf::Mesh::new(); + // mesh.vertices.iter().for_each(|v| { + // collision_mesh.push_vert(v.position.into()); + // }); // TODO: convert vert pos to world space + // for i in (0..mesh.indices.len()).step_by(3) { + // collision_mesh.push_face((mesh.indices[i] as usize, mesh.indices[i + 1] as usize, mesh.indices[i + 2] as usize)); + // } let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), false, mesh.vertices.into_iter()).unwrap(); let index_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(), false, mesh.indices.into_iter()).unwrap(); - self.game_data.meshes.push(Mesh { vertex_buffer, index_buffer, original_path, collision_mesh }); + self.game_data.meshes.push(Mesh { vertex_buffer, index_buffer, original_path }); self.game_data.meshes.len() - 1 }