From 96436d6c36d5409823422a8d4b1cdbfe75d1cc11 Mon Sep 17 00:00:00 2001 From: _ <_@_> Date: Sat, 18 Dec 2021 19:46:40 +0000 Subject: [PATCH] update `glam`, add tests to the physics --- Cargo.lock | 6 +- Cargo.toml | 3 +- src/bin/platformer.rs | 57 ++++++++++++ src/bin/pumpkin.rs | 56 ++++++------ src/gpu_buffers.rs | 9 -- src/physics.rs | 196 ++++++++++++++++++++++++++++++++-------- src/prelude.rs | 1 + src/renderable_model.rs | 2 - 8 files changed, 251 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9da0bd..9b91183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 30c6b6a..092e850 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/bin/platformer.rs b/src/bin/platformer.rs index 754f08c..ea10681 100644 --- a/src/bin/platformer.rs +++ b/src/bin/platformer.rs @@ -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 + { + 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")?, + }) + } +} diff --git a/src/bin/pumpkin.rs b/src/bin/pumpkin.rs index b481d1a..ba8ca22 100644 --- a/src/bin/pumpkin.rs +++ b/src/bin/pumpkin.rs @@ -25,9 +25,9 @@ where V: Into 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; diff --git a/src/gpu_buffers.rs b/src/gpu_buffers.rs index cb0aa74..20d5343 100644 --- a/src/gpu_buffers.rs +++ b/src/gpu_buffers.rs @@ -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 (), } } diff --git a/src/physics.rs b/src/physics.rs index 7fae11c..147a183 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -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 , - kill: bool, + pub body: PhysicsBody, + pub triangles_hit: Vec , + 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 (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 (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 (¶ms, &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); + } + } +} diff --git a/src/prelude.rs b/src/prelude.rs index 9160d44..e1c9c6e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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, }; diff --git a/src/renderable_model.rs b/src/renderable_model.rs index 6ea9787..690b04f 100644 --- a/src/renderable_model.rs +++ b/src/renderable_model.rs @@ -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,