Fix axis layout and implement click raytracing
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<f32>) {
|
||||
let euler = Euler::from(quat);
|
||||
print!("({:?},{:?},{:?})", Deg::from(euler.x), Deg::from(euler.y), Deg::from(euler.z));
|
||||
}
|
||||
@@ -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<f32>) -> Vector3<f32> {
|
||||
self.rotation.invert().rotate_vector(vec)
|
||||
}
|
||||
|
||||
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.;
|
||||
pub fn viewport_pos_to_ray_direction(&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_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<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)]
|
||||
@@ -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<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 {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -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");
|
||||
|
||||
39
src/tests/mod.rs
Normal file
39
src/tests/mod.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ pub struct Mesh {
|
||||
pub vertex_buffer: Arc<CpuAccessibleBuffer<[Vertex]>>,
|
||||
pub index_buffer: Arc<CpuAccessibleBuffer<[u32]>>,
|
||||
pub original_path: String,
|
||||
pub collision_mesh: mgf::Mesh,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -245,7 +244,7 @@ impl VulkanRenderer {
|
||||
|
||||
let pipelines: Vec<Box<dyn Drawcall>> = 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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user