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.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler32"
version = "1.0.4"
@ -297,9 +299,9 @@ dependencies = [
[[package]]
name = "glam"
version = "0.8.5"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb24d4e1b92ceed0450bbf803ac894b597c5b8d0e16f175f7ef28c42024d8cbd"
checksum = "68270e16582ea40f9c5b2fcd588fbc9cb696577222e04a64d9085cc314806a8a"
[[package]]
name = "hermit-abi"

View File

@ -15,7 +15,8 @@ float-ord = "0.2.0"
futures = "0.3.8"
futures-util = "0.3.9"
gl = "0.14.0"
glam = "0.8.5"
# glam = "0.8.5"
glam = "0.20.1"
iota = "0.2.1"
maplit = "1.0.2"
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))?;
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 (())
}
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 ();
Vec3::from ((
rgb.x () / 255.0,
rgb.y () / 255.0,
rgb.z () / 255.0
rgb.x / 255.0,
rgb.y / 255.0,
rgb.z / 255.0
))
}
@ -293,9 +293,9 @@ impl FlightState {
// Forces
let gravity = Vec3::from ((0.0, 0.0, -0.25));
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 air_drag = linear_drag + quadratic_drag;
@ -305,9 +305,9 @@ impl FlightState {
airplane.vel += dt * (thrust + gravity + air_drag);
airplane.pos += dt * airplane.vel;
if airplane.pos.z () < 0.0 {
airplane.vel.set_z (0.0);
airplane.pos.set_z (0.0);
if airplane.pos.z < 0.0 {
airplane.vel.z = 0.0;
airplane.pos.z = 0.0;
}
if microstep == microsteps - 1 {
@ -325,10 +325,10 @@ impl FlightState {
];
// Gauges
let alti = airplane.pos.z () * 100.0;
let sink_rate = -airplane.vel.z () * 100.0;
let alti = airplane.pos.z * 100.0;
let sink_rate = -airplane.vel.z * 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 glide_ratio = if sink_rate > 1.0 && throttle == 0.0 {
Some (ground_speed / sink_rate)
@ -457,8 +457,8 @@ fn make_object_space_vec (inverse_model_mat: &Mat4, world_space_vec: &Vec3)
-> Vec3
{
let v = world_space_vec;
let v4 = *inverse_model_mat * Vec4::from ((v.x (), v.y (), v.z (), 0.0));
Vec3::from ((v4.x (), v4.y (), v4.z ()))
let v4 = *inverse_model_mat * Vec4::from ((v.x, v.y, v.z, 0.0));
Vec3::from ((v4.x, v4.y, v4.z))
}
#[derive (Clone)]
@ -861,11 +861,12 @@ impl GameGraphics {
let light = state.wind_tunnel.sunlight.to_vec3 ();
let shadow_mat = {
let mut mat = Mat4::identity ();
mat.set_z_axis ((-light.x () / light.z (), -light.y () / light.z (), 0.0, 0.0).into ());
mat
};
let shadow_mat = Mat4::from_cols (
(1.0, 0.0, 0.0, 0.0).into (),
(0.0, 1.0, 0.0, 0.0).into (),
(-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 ();
//println! ("Started frame");
@ -931,7 +932,7 @@ impl GameGraphics {
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 world_model_mat = Mat4::identity ();
let world_model_mat = Mat4::IDENTITY;
use uniforms::*;
@ -1272,7 +1273,7 @@ fn main () {
let gravity = (0.0, 0.0, -1.0).into ();
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| {
if state.user_control == control_type {
@ -1314,10 +1315,10 @@ fn main () {
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)
}
else if d.z () < -0.5 {
else if d.z < -0.5 {
(-1.0, 0.0, 0.0)
}
else {
@ -1329,11 +1330,12 @@ fn main () {
let left = d.cross (up);
let up = d.cross (left);
let mut dir_mat = Mat4::identity ();
dir_mat.set_x_axis ((left.x (), left.y (), left.z (), 0.0).into ());
dir_mat.set_y_axis ((up.x (), up.y (), up.z (), 0.0).into ());
dir_mat.set_z_axis ((d.x (), d.y (), d.z (), 0.0).into ());
let dir_mat = Mat4::from_cols (
(left, 0.0).into (),
(up, 0.0).into (),
(d, 0.0).into (),
(0.0, 0.0, 0.0, 1.0).into (),
);
let s = dir_len * 0.0625;

View File

@ -9,8 +9,6 @@ use std::io::Cursor;
pub struct VertexBuffer {
id: u32,
// Not bytes.
len: usize,
}
const FLOAT_SIZE: usize = 4;
@ -59,7 +57,6 @@ impl VertexBuffer {
Self {
id,
len,
}
}
@ -74,7 +71,6 @@ impl VertexBuffer {
Self {
id,
len,
}
}
@ -103,9 +99,6 @@ pub struct IndexBuffer {
/// The OpenGL ID of the buffer
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
max: u32,
}
@ -144,7 +137,6 @@ impl IndexBuffer {
Self {
id,
len: slice.len () / IDX_SIZE,
max: max.unwrap (),
}
}
@ -166,7 +158,6 @@ impl IndexBuffer {
Self {
id,
len: slice.len (),
max: *max.unwrap (),
}
}

View File

@ -1,39 +1,37 @@
use glam::{Vec2, Vec3};
use partial_min_max::{min, max};
#[derive (Debug, PartialEq)]
pub struct PhysicsBody {
pos: Vec3,
vel: Vec3,
}
#[derive (Debug, PartialEq)]
pub struct PhysicsResult {
body: PhysicsBody,
triangles_hit: Vec <usize>,
kill: bool,
pub body: PhysicsBody,
pub triangles_hit: Vec <usize>,
pub kill: bool,
}
#[derive (Copy, Clone)]
pub struct Triangle {
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 {
Vec3::from ((
min (a.x (), b.x ()),
min (a.y (), b.y ()),
min (a.z (), b.z ())
min (a.x, b.x),
min (a.y, b.y),
min (a.z, b.z)
))
}
fn vec_max (a: &Vec3, b: &Vec3) -> Vec3 {
Vec3::from ((
max (a.x (), b.x ()),
max (a.y (), b.y ()),
max (a.z (), b.z ())
max (a.x, b.x),
max (a.y, b.y),
max (a.z, b.z)
))
}
@ -53,7 +51,7 @@ impl Triangle {
}
}
#[derive (Clone)]
#[derive (Clone, Debug, PartialEq)]
pub struct Collision {
t: f32,
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
where MB: MeshBuffer
{
let radius = 0.0625f32;
let radius3 = Vec3::from ((
radius,
radius,
@ -91,19 +87,17 @@ where MB: MeshBuffer
i: 0,
};
for i in 0..world.num_triangles () {
let tri = world.get_triangle (i);
for (i, tri) in world.iter ().enumerate () {
let tri_min = tri.min () - radius3;
let tri_max = tri.max () + radius3;
let ray_min = min (p0, p1);
let ray_max = max (p0, p1);
let ray_min = p0.min (p1);
let ray_max = p0.max (p1);
if
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.z () < tri_min.z () || ray_min.z () > tri_max.z ()
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.z < tri_min.z || ray_min.z > tri_max.z
{
// AABB reject
continue;
@ -189,14 +183,14 @@ where MB: MeshBuffer
// and Y is the cross of those.
// 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 {
// No possible collision
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 {
// The cylinder is along the line,
@ -280,25 +274,31 @@ where MB: MeshBuffer
candidate
}
pub fn physics_step <MB> (input: &PhysicsBody, world: &MB)
-> PhysicsResult
where MB: MeshBuffer
pub struct Params {
dt: f32,
gravity: Vec3,
margin: f32,
}
pub fn physics_step (
params: &Params, world: &[Triangle],
radius: f32, input: &PhysicsBody,
) -> PhysicsResult
{
let dt = 1.0 / 60.0;
let z = Vec3::from ((0.0, 0.0, 1.0));
let gravity = z * -16.0 * dt;
let margin = 0.00125;
let margin = params.margin;
let dt = params.dt;
let mut t_remaining = 1.0;
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 triangles_hit = Vec::new ();
for i in 0..5 {
let candidate = get_candidate (world, old_pos, new_pos);
// Do 5 iterations of the sub-step, trying to converge on a valid state
for _ in 0..5 {
let candidate = get_candidate (world, old_pos, new_pos, radius);
if candidate.t <= 1.0 {
t_remaining *= 1.0 - candidate.t;
@ -315,6 +315,8 @@ where MB: MeshBuffer
new_vel += candidate.normal * speed_towards_normal;
// But also compensate for the slide distance it lost
new_pos = push_out_pos + new_vel * dt * t_remaining;
triangles_hit.push (candidate.i);
}
else {
t_remaining = 0.0;
@ -332,3 +334,121 @@ where MB: MeshBuffer
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,
network_protocol::*,
quinn_common::make_client_endpoint,
renderable_model::renderable_from_iqm_file,
shader,
timestep::TimeStep,
};

View File

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