Fix axis layout and implement click raytracing

This commit is contained in:
2021-07-30 22:41:05 +02:00
parent 4c2409dbd0
commit 02ba2bb95a
8 changed files with 167 additions and 98 deletions

View File

@@ -18,7 +18,6 @@ serde_derive = "1.0.114"
toml = "0.5.6" toml = "0.5.6"
gilrs = "0.7.4" gilrs = "0.7.4"
gltf = "0.15.2" gltf = "0.15.2"
mgf = "1.4.0"
[[bin]] [[bin]]
name = "converter" name = "converter"

View File

@@ -8,25 +8,6 @@
} }
], ],
"objects": [ "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, "mesh_index": 1,
"position": [ "position": [
@@ -50,15 +31,15 @@
"player": { "player": {
"mesh_index": null, "mesh_index": null,
"position": [ "position": [
-3.0608184, 0,
-1.0, 0,
-3.0598369 5.0
], ],
"rotation": [ "rotation": [
0.15801395, 1,
0.031636234, 0,
0.96772224, 0,
0.19375017 0
], ],
"scale": [ "scale": [
1.0, 1.0,

View File

@@ -1,7 +1,7 @@
use std::time::SystemTime; use std::time::SystemTime;
use cgmath::{Deg, InnerSpace, Quaternion, Rotation3, vec3}; use cgmath::{Deg, Euler, Quaternion, vec3};
use winit::event::Event; use winit::event::Event;
use level::{load_level, save_level}; use level::{load_level, save_level};
@@ -66,6 +66,8 @@ impl Game for TestGame {
if self.input.button_down("print_framerate") { if self.input.button_down("print_framerate") {
println!("{:.0} ms / {:.0} FPS", frame_time * 1000.0, 1.0 / frame_time); 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") { if self.input.button_just_pressed("test") {
@@ -77,7 +79,7 @@ impl Game for TestGame {
if !self.paused { if !self.paused {
self.player.update(frame_time, &self.input, renderer); 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 // End frame
self.last_time = time; self.last_time = time;
@@ -171,3 +173,8 @@ impl TestGame {
renderer.clear_all(); renderer.clear_all();
} }
} }
pub fn print_quat_as_euler(quat: Quaternion<f32>) {
let euler = Euler::from(quat);
print!("({:?},{:?},{:?})", Deg::from(euler.x), Deg::from(euler.y), Deg::from(euler.z));
}

View File

@@ -1,9 +1,11 @@
use cgmath::{Deg, InnerSpace, Matrix4, One, Quaternion, Rad, Rotation, Rotation3, SquareMatrix, Vector3, vec3, vec4}; use cgmath::{Deg, InnerSpace, Matrix4, One, Quaternion, Rad, Rotation, Rotation3, SquareMatrix, Vector3, Vector4, vec3, vec4};
use mgf::Intersection; use vulkano::buffer::TypedBufferAccess;
use crate::game::player::PlayerMovementMode::{FirstPerson, Flying}; use crate::game::player::PlayerMovementMode::{FirstPerson, Flying};
use crate::input::InputState; use crate::input::InputState;
use crate::vulkan::Mesh;
use crate::vulkan::gameobject::GameObject;
use crate::vulkan::{ use crate::vulkan::{
gameobject::Updatable, gameobject::Updatable,
VulkanRenderer VulkanRenderer
@@ -29,10 +31,12 @@ impl Camera {
} }
pub fn update(&mut self, renderer: &mut VulkanRenderer) { pub fn update(&mut self, renderer: &mut VulkanRenderer) {
// Camera stuff // Don't let floating point error accumulate in rotation
let mut pos = self.position.clone(); debug_assert!(f32::abs(1.0 - self.rotation.magnitude()) < 0.00001 as f32);
pos.y *= -1.0; self.rotation = self.rotation.normalize();
self.view = Matrix4::from(self.rotation) * Matrix4::from_translation(pos);
// Create matrices
self.view = Matrix4::from(self.rotation) * Matrix4::from_translation(self.position * -1.);
self.proj = cgmath::perspective( self.proj = cgmath::perspective(
Rad::from(Deg(self.fov_y)), Rad::from(Deg(self.fov_y)),
@@ -40,19 +44,17 @@ impl Camera {
0.1, 0.1,
100.0 100.0
); );
// Why?
self.proj.y.y *= -1.0; self.proj.y.y *= -1.0;
// Upload
renderer.game_data.line_push_constants.view = self.view.into(); renderer.game_data.line_push_constants.view = self.view.into();
renderer.game_data.line_push_constants.projection = self.proj.into(); renderer.game_data.line_push_constants.projection = self.proj.into();
} }
pub fn transform_vector(&self, vec: Vector3<f32>) -> Vector3<f32> { pub fn viewport_pos_to_ray_direction(&self, viewport_pos: [f64; 2], viewport_dimensions: [u32; 2]) -> Option<Vector3<f32>> {
self.rotation.invert().rotate_vector(vec) 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(&self, viewport_pos: [f64; 2], viewport_dimensions: [u32; 2]) -> Option<Vector3<f32>> {
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_start_screen = vec4(normalized_x, normalized_y, -1.0, 1.0);
let click_end_screen = vec4(normalized_x, normalized_y, 0.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; ray_end_world /= ray_end_world.w;
let ray_dir_world = (ray_end_world - ray_start_world).normalize(); let ray_dir_world = (ray_end_world - ray_start_world).normalize();
Some(ray_dir_world.truncate()) Some(ray_dir_world.truncate())
} }
pub fn local_to_world(&self, vec:Vector3<f32>) -> Vector3<f32> {
self.rotation.invert().rotate_vector(vec)
}
pub fn world_to_local(&self, vec:Vector3<f32>) -> Vector3<f32> {
self.rotation.rotate_vector(vec)
}
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@@ -88,21 +99,77 @@ pub struct Player {
pub movement_mode: PlayerMovementMode, pub movement_mode: PlayerMovementMode,
pub movement_speed: f32, pub movement_speed: f32,
pub look_sensitivity: f32, pub look_sensitivity: f32,
pub height: f32 pub height: f32,
pub x_look: f32,
pub y_look: f32
} }
impl Player { impl Player {
pub fn new() -> Player { pub fn new() -> Player {
Player { Player {
camera: Camera::new(), camera: Camera::new(),
movement_mode: FirstPerson, movement_mode: Flying,
movement_speed: 3.0, movement_speed: 3.0,
look_sensitivity: 30.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<f32> {
vec4(pos[0], pos[1], pos[2], 1.0)
}
pub fn intersection_distance(ray_origin: Vector3<f32>, ray_direction: Vector3<f32>, mesh: &Mesh, game_object: &GameObject) -> Option<f32> {
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<f32>, ray_direction: Vector3<f32>, vtx_a: [f32; 3], vtx_b: [f32; 3], vtx_c: [f32; 3]) -> Option<f32> {
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<f32> = 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 { impl Updatable for Player {
fn update(&mut self, delta_time: f32, input: &InputState, renderer: &mut VulkanRenderer) { fn update(&mut self, delta_time: f32, input: &InputState, renderer: &mut VulkanRenderer) {
// Edit mode // Edit mode
@@ -115,52 +182,32 @@ impl Updatable for Player {
} }
if input.button_just_pressed("select") { 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_direction = self.camera.viewport_pos_to_ray_direction(input.mouse_position, renderer.game_data.dimensions).unwrap();
let ray_origin: [f32; 3] = self.camera.position.into(); for game_object in &mut renderer.game_data.game_objects {
let ray = mgf::Ray::new(ray_origin.into(), ray_direction); let mesh = &renderer.game_data.meshes[game_object.mesh_index];
if let Some(dist) = intersection_distance(self.camera.position, ray_direction, mesh, game_object) {
let mut shortest_hit: Option<(usize, Intersection)> = None; game_object.is_selected = !game_object.is_selected;
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);
} }
} }
// Rotation // Rotation
self.camera.rotation = self.camera.rotation * Quaternion::from_angle_y(Deg(input.get_axis("look_horizontal") * delta_time * self.look_sensitivity)); self.x_look += input.get_axis("look_vertical") * 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.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 // 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 { 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; 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 { } else if self.movement_mode == Flying {
self.camera.position += self.camera.transform_vector(vec3( self.camera.position += self.camera.local_to_world(local_input);
input.get_axis("move_sideways") * -self.movement_speed,
0.0,
input.get_axis("move_forward") * self.movement_speed)) * delta_time;
} }
// Update // Update

View File

@@ -1,19 +1,16 @@
extern crate serde_json; extern crate serde_json;
use vulkan::gameobject::GameObject; use vulkan::gameobject::GameObject;
use crate::config::{LogConfig, RenderConfig}; use crate::config::{LogConfig, RenderConfig};
use crate::game::TestGame; use crate::game::TestGame;
use crate::vulkan::{LinePoint, VulkanRenderer}; use crate::vulkan::{LinePoint, VulkanRenderer};
mod vulkan; mod vulkan;
mod input; mod input;
mod config; mod config;
mod game; mod game;
mod tests;
fn main() { fn main() {
let log_config = LogConfig::from_file("config/log.toml"); let log_config = LogConfig::from_file("config/log.toml");

39
src/tests/mod.rs Normal file
View File

@@ -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<f32>, o2: Option<f32>) {
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));
}
}

View File

@@ -57,7 +57,7 @@ impl GameObject {
pub fn get_push_constants(&self) -> vs::ty::PushConstants { pub fn get_push_constants(&self) -> vs::ty::PushConstants {
vs::ty::PushConstants { vs::ty::PushConstants {
model: self.get_model_matrix().into(), model: self.get_model_matrix().into(),
is_selected: if self.is_selected { 0 } else { 1 }, is_selected: if self.is_selected { 1 } else { 0 },
} }
} }

View File

@@ -70,7 +70,6 @@ pub struct Mesh {
pub vertex_buffer: Arc<CpuAccessibleBuffer<[Vertex]>>, pub vertex_buffer: Arc<CpuAccessibleBuffer<[Vertex]>>,
pub index_buffer: Arc<CpuAccessibleBuffer<[u32]>>, pub index_buffer: Arc<CpuAccessibleBuffer<[u32]>>,
pub original_path: String, pub original_path: String,
pub collision_mesh: mgf::Mesh,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -245,7 +244,7 @@ impl VulkanRenderer {
let pipelines: Vec<Box<dyn Drawcall>> = vec![ let pipelines: Vec<Box<dyn Drawcall>> = vec![
Box::new(DefaultShader::new(device.clone(), render_pass.clone())), 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 // Dynamic viewports allow us to recreate just the viewport when the window is resized
@@ -344,7 +343,7 @@ impl VulkanRenderer {
self.pipelines = vec![ self.pipelines = vec![
Box::new(DefaultShader::new(self.device.clone(), self.render_pass.clone())), 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; self.swapchain = new_swapchain;
@@ -413,18 +412,18 @@ impl VulkanRenderer {
} }
pub fn upload_mesh(self: &mut Self, mesh: CPUMesh, original_path: String) -> usize { pub fn upload_mesh(self: &mut Self, mesh: CPUMesh, original_path: String) -> usize {
let mut collision_mesh = mgf::Mesh::new(); // let mut collision_mesh = mgf::Mesh::new();
mesh.vertices.iter().for_each(|v| { // mesh.vertices.iter().for_each(|v| {
collision_mesh.push_vert(v.position.into()); // collision_mesh.push_vert(v.position.into());
}); // TODO: convert vert pos to world space // }); // TODO: convert vert pos to world space
for i in (0..mesh.indices.len()).step_by(3) { // 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)); // 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 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(); 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 self.game_data.meshes.len() - 1
} }