update `glam`, add tests to the physics

main
_ 2021-12-18 19:46:40 +00:00
parent f5da7b5188
commit 96436d6c36
8 changed files with 251 additions and 79 deletions

6
Cargo.lock generated
View File

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "adler32" name = "adler32"
version = "1.0.4" version = "1.0.4"
@ -297,9 +299,9 @@ dependencies = [
[[package]] [[package]]
name = "glam" name = "glam"
version = "0.8.5" version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb24d4e1b92ceed0450bbf803ac894b597c5b8d0e16f175f7ef28c42024d8cbd" checksum = "68270e16582ea40f9c5b2fcd588fbc9cb696577222e04a64d9085cc314806a8a"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"

View File

@ -15,7 +15,8 @@ float-ord = "0.2.0"
futures = "0.3.8" futures = "0.3.8"
futures-util = "0.3.9" futures-util = "0.3.9"
gl = "0.14.0" gl = "0.14.0"
glam = "0.8.5" # glam = "0.8.5"
glam = "0.20.1"
iota = "0.2.1" iota = "0.2.1"
maplit = "1.0.2" maplit = "1.0.2"
partial-min-max = "0.4.0" partial-min-max = "0.4.0"

View File

@ -24,5 +24,62 @@ async fn main () -> Result <()> {
window.gl_make_current (&gl_ctx).map_err (|e| anyhow! ("Can't make OpenGL context current: {}", e))?; window.gl_make_current (&gl_ctx).map_err (|e| anyhow! ("Can't make OpenGL context current: {}", e))?;
let mut event_pump = sdl_context.event_pump ().unwrap ();
let mut time_step = TimeStep::new (60, 1000);
let mut graphics_frames = 0;
let shader_program = shader::shader_from_files ("shaders/terrain-vert.glsl", "shaders/terrain-frag.glsl");
let shader_locations = ShaderLocations::new (&shader_program)?;
let mesh_cube = renderable_from_iqm_file ("cube.iqm");
'running: loop {
let _frames_to_do = time_step.step ();
for event in event_pump.poll_iter () {
match event {
Event::Quit {..} |
Event::KeyDown { keycode: Some (Keycode::Escape), .. } => {
break 'running
},
_ => (),
}
}
window.gl_make_current (&gl_ctx).unwrap ();
glezz::enable (gl::DEPTH_TEST);
glezz::clear_color (0.392f32, 0.710f32, 0.965f32, 1.0f32);
glezz::clear (gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);
window.gl_swap_window ();
tokio::time::sleep (Duration::from_millis (15)).await;
}
Ok (()) Ok (())
} }
struct ShaderLocations {
attr_pos: u32,
attr_normal: u32,
attr_color: u32,
uni_mvp: i32,
}
impl ShaderLocations {
pub fn new (shader_program: &shader::ShaderProgram) -> anyhow::Result <Self>
{
let attr = |name: &str| shader_program.get_attribute_location (&CString::new (name.as_bytes ())?).try_into ().context ("Attribute location negative");
let uni = |name: &str| shader_program.get_uniform_location (&CString::new (name.as_bytes ())?).try_into ().context ("Uniform location bad");
Ok (Self {
attr_pos: attr ("attr_pos")?,
attr_normal: attr ("attr_normal")?,
attr_color: attr ("attr_color")?,
uni_mvp: uni ("uni_mvp")?,
})
}
}

View File

@ -25,9 +25,9 @@ where V: Into <Vec3>
let rgb: Vec3 = rgb.into (); let rgb: Vec3 = rgb.into ();
Vec3::from (( Vec3::from ((
rgb.x () / 255.0, rgb.x / 255.0,
rgb.y () / 255.0, rgb.y / 255.0,
rgb.z () / 255.0 rgb.z / 255.0
)) ))
} }
@ -293,9 +293,9 @@ impl FlightState {
// Forces // Forces
let gravity = Vec3::from ((0.0, 0.0, -0.25)); let gravity = Vec3::from ((0.0, 0.0, -0.25));
let thrust = 0.125 * nose * throttle; let thrust = 0.125 * nose * throttle;
let linear_drag = 0.0 * 0.25 * speed * -object_space_dir.y () * nose; let linear_drag = 0.0 * 0.25 * speed * -object_space_dir.y * nose;
let turbulent_dir = Vec3::from ((-1.0 * object_space_dir.x (), -0.03125 * object_space_dir.y (), -16.0 * object_space_dir.z ())); let turbulent_dir = Vec3::from ((-1.0 * object_space_dir.x, -0.03125 * object_space_dir.y, -16.0 * object_space_dir.z));
let quadratic_drag = speed * speed * airplane.ori.mul_vec3 (turbulent_dir); let quadratic_drag = speed * speed * airplane.ori.mul_vec3 (turbulent_dir);
let air_drag = linear_drag + quadratic_drag; let air_drag = linear_drag + quadratic_drag;
@ -305,9 +305,9 @@ impl FlightState {
airplane.vel += dt * (thrust + gravity + air_drag); airplane.vel += dt * (thrust + gravity + air_drag);
airplane.pos += dt * airplane.vel; airplane.pos += dt * airplane.vel;
if airplane.pos.z () < 0.0 { if airplane.pos.z < 0.0 {
airplane.vel.set_z (0.0); airplane.vel.z = 0.0;
airplane.pos.set_z (0.0); airplane.pos.z = 0.0;
} }
if microstep == microsteps - 1 { if microstep == microsteps - 1 {
@ -325,10 +325,10 @@ impl FlightState {
]; ];
// Gauges // Gauges
let alti = airplane.pos.z () * 100.0; let alti = airplane.pos.z * 100.0;
let sink_rate = -airplane.vel.z () * 100.0; let sink_rate = -airplane.vel.z * 100.0;
let air_speed = speed * 100.0; let air_speed = speed * 100.0;
let ground_vel = Vec3::from ((airplane.vel.x (), airplane.vel.y (), 0.0)); let ground_vel = Vec3::from ((airplane.vel.x, airplane.vel.y, 0.0));
let ground_speed = ground_vel.length () * 100.0; let ground_speed = ground_vel.length () * 100.0;
let glide_ratio = if sink_rate > 1.0 && throttle == 0.0 { let glide_ratio = if sink_rate > 1.0 && throttle == 0.0 {
Some (ground_speed / sink_rate) Some (ground_speed / sink_rate)
@ -457,8 +457,8 @@ fn make_object_space_vec (inverse_model_mat: &Mat4, world_space_vec: &Vec3)
-> Vec3 -> Vec3
{ {
let v = world_space_vec; let v = world_space_vec;
let v4 = *inverse_model_mat * Vec4::from ((v.x (), v.y (), v.z (), 0.0)); let v4 = *inverse_model_mat * Vec4::from ((v.x, v.y, v.z, 0.0));
Vec3::from ((v4.x (), v4.y (), v4.z ())) Vec3::from ((v4.x, v4.y, v4.z))
} }
#[derive (Clone)] #[derive (Clone)]
@ -861,11 +861,12 @@ impl GameGraphics {
let light = state.wind_tunnel.sunlight.to_vec3 (); let light = state.wind_tunnel.sunlight.to_vec3 ();
let shadow_mat = { let shadow_mat = Mat4::from_cols (
let mut mat = Mat4::identity (); (1.0, 0.0, 0.0, 0.0).into (),
mat.set_z_axis ((-light.x () / light.z (), -light.y () / light.z (), 0.0, 0.0).into ()); (0.0, 1.0, 0.0, 0.0).into (),
mat (-light.x / light.z, -light.y / light.z, 0.0, 0.0).into (),
}; (0.0, 0.0, 0.0, 1.0).into (),
);
let mut passes = self.passes.iter (); let mut passes = self.passes.iter ();
//println! ("Started frame"); //println! ("Started frame");
@ -931,7 +932,7 @@ impl GameGraphics {
let inverse_truck = truck_model_mat.inverse (); let inverse_truck = truck_model_mat.inverse ();
let truck_model_mat = truck_model_mat * Mat4::from_scale ((airplane_scale, airplane_scale, airplane_scale).into ()); let truck_model_mat = truck_model_mat * Mat4::from_scale ((airplane_scale, airplane_scale, airplane_scale).into ());
let world_model_mat = Mat4::identity (); let world_model_mat = Mat4::IDENTITY;
use uniforms::*; use uniforms::*;
@ -1272,7 +1273,7 @@ fn main () {
let gravity = (0.0, 0.0, -1.0).into (); let gravity = (0.0, 0.0, -1.0).into ();
let wind = state.wind.to_vec3 () * -1.0; let wind = state.wind.to_vec3 () * -1.0;
let wind_force = (wind.x (), 0.125 * wind.y (), wind.z ()).into (); let wind_force = (wind.x, 0.125 * wind.y, wind.z).into ();
let get_flash = |control_type, default_color| { let get_flash = |control_type, default_color| {
if state.user_control == control_type { if state.user_control == control_type {
@ -1314,10 +1315,10 @@ fn main () {
let d = arrow.direction / dir_len; let d = arrow.direction / dir_len;
let up: Vec3 = if d.z () > 0.5 { let up: Vec3 = if d.z > 0.5 {
(-1.0, 0.0, 0.0) (-1.0, 0.0, 0.0)
} }
else if d.z () < -0.5 { else if d.z < -0.5 {
(-1.0, 0.0, 0.0) (-1.0, 0.0, 0.0)
} }
else { else {
@ -1329,11 +1330,12 @@ fn main () {
let left = d.cross (up); let left = d.cross (up);
let up = d.cross (left); let up = d.cross (left);
let mut dir_mat = Mat4::identity (); let dir_mat = Mat4::from_cols (
(left, 0.0).into (),
dir_mat.set_x_axis ((left.x (), left.y (), left.z (), 0.0).into ()); (up, 0.0).into (),
dir_mat.set_y_axis ((up.x (), up.y (), up.z (), 0.0).into ()); (d, 0.0).into (),
dir_mat.set_z_axis ((d.x (), d.y (), d.z (), 0.0).into ()); (0.0, 0.0, 0.0, 1.0).into (),
);
let s = dir_len * 0.0625; let s = dir_len * 0.0625;

View File

@ -9,8 +9,6 @@ use std::io::Cursor;
pub struct VertexBuffer { pub struct VertexBuffer {
id: u32, id: u32,
// Not bytes.
len: usize,
} }
const FLOAT_SIZE: usize = 4; const FLOAT_SIZE: usize = 4;
@ -59,7 +57,6 @@ impl VertexBuffer {
Self { Self {
id, id,
len,
} }
} }
@ -74,7 +71,6 @@ impl VertexBuffer {
Self { Self {
id, id,
len,
} }
} }
@ -103,9 +99,6 @@ pub struct IndexBuffer {
/// The OpenGL ID of the buffer /// The OpenGL ID of the buffer
id: u32, id: u32,
/// The count of 32-bit indices the buffer can store
len: usize,
/// The largest index stored in the buffer when it was created /// The largest index stored in the buffer when it was created
max: u32, max: u32,
} }
@ -144,7 +137,6 @@ impl IndexBuffer {
Self { Self {
id, id,
len: slice.len () / IDX_SIZE,
max: max.unwrap (), max: max.unwrap (),
} }
} }
@ -166,7 +158,6 @@ impl IndexBuffer {
Self { Self {
id, id,
len: slice.len (),
max: *max.unwrap (), max: *max.unwrap (),
} }
} }

View File

@ -1,39 +1,37 @@
use glam::{Vec2, Vec3}; use glam::{Vec2, Vec3};
use partial_min_max::{min, max}; use partial_min_max::{min, max};
#[derive (Debug, PartialEq)]
pub struct PhysicsBody { pub struct PhysicsBody {
pos: Vec3, pos: Vec3,
vel: Vec3, vel: Vec3,
} }
#[derive (Debug, PartialEq)]
pub struct PhysicsResult { pub struct PhysicsResult {
body: PhysicsBody, pub body: PhysicsBody,
triangles_hit: Vec <usize>, pub triangles_hit: Vec <usize>,
kill: bool, pub kill: bool,
} }
#[derive (Copy, Clone)]
pub struct Triangle { pub struct Triangle {
verts: [Vec3; 3], verts: [Vec3; 3],
} }
pub trait MeshBuffer {
fn num_triangles (&self) -> usize;
fn get_triangle (&self, i: usize) -> Triangle;
}
fn vec_min (a: &Vec3, b: &Vec3) -> Vec3 { fn vec_min (a: &Vec3, b: &Vec3) -> Vec3 {
Vec3::from (( Vec3::from ((
min (a.x (), b.x ()), min (a.x, b.x),
min (a.y (), b.y ()), min (a.y, b.y),
min (a.z (), b.z ()) min (a.z, b.z)
)) ))
} }
fn vec_max (a: &Vec3, b: &Vec3) -> Vec3 { fn vec_max (a: &Vec3, b: &Vec3) -> Vec3 {
Vec3::from (( Vec3::from ((
max (a.x (), b.x ()), max (a.x, b.x),
max (a.y (), b.y ()), max (a.y, b.y),
max (a.z (), b.z ()) max (a.z, b.z)
)) ))
} }
@ -53,7 +51,7 @@ impl Triangle {
} }
} }
#[derive (Clone)] #[derive (Clone, Debug, PartialEq)]
pub struct Collision { pub struct Collision {
t: f32, t: f32,
p_impact: Vec3, p_impact: Vec3,
@ -72,11 +70,9 @@ impl Collision {
} }
} }
pub fn get_candidate <MB> (world: &MB, p0: Vec3, p1: Vec3) pub fn get_candidate (world: &[Triangle], p0: Vec3, p1: Vec3, radius: f32)
-> Collision -> Collision
where MB: MeshBuffer
{ {
let radius = 0.0625f32;
let radius3 = Vec3::from (( let radius3 = Vec3::from ((
radius, radius,
radius, radius,
@ -91,19 +87,17 @@ where MB: MeshBuffer
i: 0, i: 0,
}; };
for i in 0..world.num_triangles () { for (i, tri) in world.iter ().enumerate () {
let tri = world.get_triangle (i);
let tri_min = tri.min () - radius3; let tri_min = tri.min () - radius3;
let tri_max = tri.max () + radius3; let tri_max = tri.max () + radius3;
let ray_min = min (p0, p1); let ray_min = p0.min (p1);
let ray_max = max (p0, p1); let ray_max = p0.max (p1);
if if
ray_max.x () < tri_min.x () || ray_min.x () > tri_max.x () || ray_max.x < tri_min.x || ray_min.x > tri_max.x ||
ray_max.y () < tri_min.y () || ray_min.y () > tri_max.y () || ray_max.y < tri_min.y || ray_min.y > tri_max.y ||
ray_max.z () < tri_min.z () || ray_min.z () > tri_max.z () ray_max.z < tri_min.z || ray_min.z > tri_max.z
{ {
// AABB reject // AABB reject
continue; continue;
@ -189,14 +183,14 @@ where MB: MeshBuffer
// and Y is the cross of those. // and Y is the cross of those.
// I forgot the maths word for this // I forgot the maths word for this
let discriminant = radius * radius - a_ray.y () * a_ray.y (); let discriminant = radius * radius - a_ray.y * a_ray.y;
if discriminant < 0.0 { if discriminant < 0.0 {
// No possible collision // No possible collision
continue; continue;
} }
let t = (a_ray.x () - discriminant.sqrt ()) / (p1 - p0).length (); let t = (a_ray.x - discriminant.sqrt ()) / (p1 - p0).length ();
if t < 0.0 || t > 1.0 { if t < 0.0 || t > 1.0 {
// The cylinder is along the line, // The cylinder is along the line,
@ -280,25 +274,31 @@ where MB: MeshBuffer
candidate candidate
} }
pub fn physics_step <MB> (input: &PhysicsBody, world: &MB) pub struct Params {
-> PhysicsResult dt: f32,
where MB: MeshBuffer gravity: Vec3,
margin: f32,
}
pub fn physics_step (
params: &Params, world: &[Triangle],
radius: f32, input: &PhysicsBody,
) -> PhysicsResult
{ {
let dt = 1.0 / 60.0; let margin = params.margin;
let z = Vec3::from ((0.0, 0.0, 1.0)); let dt = params.dt;
let gravity = z * -16.0 * dt;
let margin = 0.00125;
let mut t_remaining = 1.0; let mut t_remaining = 1.0;
let mut old_pos = input.pos; let mut old_pos = input.pos;
let mut new_vel = input.vel + gravity; let mut new_vel = input.vel + params.gravity;
let mut new_pos = old_pos + new_vel * dt * t_remaining; let mut new_pos = old_pos + new_vel * dt * t_remaining;
let mut triangles_hit = Vec::new (); let mut triangles_hit = Vec::new ();
for i in 0..5 { // Do 5 iterations of the sub-step, trying to converge on a valid state
let candidate = get_candidate (world, old_pos, new_pos); for _ in 0..5 {
let candidate = get_candidate (world, old_pos, new_pos, radius);
if candidate.t <= 1.0 { if candidate.t <= 1.0 {
t_remaining *= 1.0 - candidate.t; t_remaining *= 1.0 - candidate.t;
@ -315,6 +315,8 @@ where MB: MeshBuffer
new_vel += candidate.normal * speed_towards_normal; new_vel += candidate.normal * speed_towards_normal;
// But also compensate for the slide distance it lost // But also compensate for the slide distance it lost
new_pos = push_out_pos + new_vel * dt * t_remaining; new_pos = push_out_pos + new_vel * dt * t_remaining;
triangles_hit.push (candidate.i);
} }
else { else {
t_remaining = 0.0; t_remaining = 0.0;
@ -332,3 +334,121 @@ where MB: MeshBuffer
kill: false, kill: false,
} }
} }
#[cfg (test)]
mod test {
use super::*;
#[test]
fn test_physics () {
// Remember, Z is up
let params = Params {
dt: 1.0,
gravity: (0.0, 0.0, 0.0).into (),
margin: 0.00125,
};
let world: Vec <_> = vec! [
(
(0.0, 0.0, 0.0),
(0.0, 2.0, 0.0),
(2.0, 0.0, 0.0),
),
].into_iter ()
.map (|(v0, v1, v2)| {
Triangle {
verts: [
v0.into (),
v1.into (),
v2.into (),
],
}
})
.collect ();
let magic_0 = f32::sqrt (f32::powf (0.5 + params.margin, 2.0) / 2.0);
for ((radius, body_before), e) in [
// Ray striking triangle from above, stops at triangle
(
(
0.0,
PhysicsBody {
pos: (0.5, 0.5, 0.5).into (),
vel: (0.0, 0.0, -1.0).into (),
},
),
PhysicsResult {
body: PhysicsBody {
pos: (0.5, 0.5, params.margin).into (),
vel: (0.0, 0.0, 0.0).into (),
},
triangles_hit: vec! [0],
kill: false,
},
),
// Ball striking triangle from above, stops at ball radius
(
(
0.5,
PhysicsBody {
pos: (0.5, 0.5, 2.0).into (),
vel: (0.0, 0.0, -2.0).into (),
},
),
PhysicsResult {
body: PhysicsBody {
pos: (0.5, 0.5, params.margin + 0.5).into (),
vel: (0.0, 0.0, 0.0).into (),
},
triangles_hit: vec! [0],
kill: false,
},
),
// Ball striking triangle on edge
(
(
0.5,
PhysicsBody {
pos: (-2.0, 1.0, 0.0).into (),
vel: (2.0, 0.0, 0.0).into (),
},
),
PhysicsResult {
body: PhysicsBody {
pos: (-params.margin - 0.5, 1.0, 0.0).into (),
vel: (0.0, 0.0, 0.0).into (),
},
triangles_hit: vec! [0],
kill: false,
},
),
// Ball striking triangle on diagonal edge
(
(
0.5,
PhysicsBody {
pos: (2.0, 2.0, 0.0).into (),
vel: (-2.0, -2.0, 0.0).into (),
},
),
PhysicsResult {
body: PhysicsBody {
pos: (1.0 + magic_0, 1.0 + magic_0, 0.0).into (),
vel: (0.0, 0.0, 0.0).into (),
},
triangles_hit: vec! [0],
kill: false,
},
),
] {
let a = physics_step (&params, &world, radius, &body_before);
assert! (a.body.pos.distance_squared (e.body.pos) < 0.00125);
assert! (a.body.vel.distance_squared (e.body.vel) < 0.00125);
assert_eq! (a.triangles_hit, e.triangles_hit);
assert_eq! (a.kill, e.kill);
}
}
}

View File

@ -30,6 +30,7 @@ pub use crate::{
gpu_buffers, gpu_buffers,
network_protocol::*, network_protocol::*,
quinn_common::make_client_endpoint, quinn_common::make_client_endpoint,
renderable_model::renderable_from_iqm_file,
shader, shader,
timestep::TimeStep, timestep::TimeStep,
}; };

View File

@ -18,7 +18,6 @@ pub struct RenderableMesh {
pub struct RenderableModel { pub struct RenderableModel {
num_pos: usize, num_pos: usize,
num_uv: usize, num_uv: usize,
num_normal: usize,
vertexes: VertexBuffer, vertexes: VertexBuffer,
indexes: IndexBuffer, indexes: IndexBuffer,
@ -89,7 +88,6 @@ impl RenderableModel {
Self { Self {
num_pos, num_pos,
num_uv, num_uv,
num_normal,
vertexes, vertexes,
indexes, indexes,