From 6a9af0441fc5930b88cf34f4ca8a52fe62878ac3 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 4 Oct 2023 20:04:04 -0700 Subject: [PATCH] move physics to its own thread --- src/main.rs | 256 +++++++++++++++------------------ src/physics.rs | 382 ++++++++++++++++++++++++++++++++++++------------- src/worker.rs | 10 +- 3 files changed, 397 insertions(+), 251 deletions(-) diff --git a/src/main.rs b/src/main.rs index 53c14e2..cfed437 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,14 +44,65 @@ pub struct GraphicsPipelines{ model: wgpu::RenderPipeline, } +pub struct GraphicsCamera{ + screen_size: glam::UVec2, + fov: glam::Vec2,//slope + //camera angles and such are extrapolated and passed in every time +} + +#[inline] +fn perspective_rh(fov_x_slope: f32, fov_y_slope: f32, z_near: f32, z_far: f32) -> glam::Mat4 { + //glam_assert!(z_near > 0.0 && z_far > 0.0); + let r = z_far / (z_near - z_far); + glam::Mat4::from_cols( + glam::Vec4::new(1.0/fov_x_slope, 0.0, 0.0, 0.0), + glam::Vec4::new(0.0, 1.0/fov_y_slope, 0.0, 0.0), + glam::Vec4::new(0.0, 0.0, r, -1.0), + glam::Vec4::new(0.0, 0.0, r * z_near, 0.0), + ) +} +impl GraphicsCamera{ + pub fn new(screen_size:glam::UVec2,fov_y:f32)->Self{ + Self{ + screen_size, + fov: glam::vec2(fov_y*(screen_size.x as f32)/(screen_size.y as f32),fov_y), + } + } + pub fn proj(&self)->glam::Mat4{ + perspective_rh(self.fov.x, self.fov.y, 0.5, 2000.0) + } + pub fn view(&self,pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{ + //f32 good enough for view matrix + glam::Mat4::from_translation(pos) * glam::Mat4::from_euler(glam::EulerRot::YXZ, angles.x, angles.y, 0f32) + } + pub fn set_screen_size(&mut self,screen_size:glam::UVec2){ + self.screen_size=screen_size; + self.fov.x=self.fov.y*(screen_size.x as f32)/(screen_size.y as f32); + } + + pub fn to_uniform_data(&self,(pos,angles): (glam::Vec3,glam::Vec2)) -> [f32; 16 * 3 + 4] { + let proj=self.proj(); + let proj_inv = proj.inverse(); + let view=self.view(pos,angles); + let view_inv = view.inverse(); + + let mut raw = [0f32; 16 * 3 + 4]; + raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]); + raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]); + raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view_inv)[..]); + raw[48..52].copy_from_slice(AsRef::<[f32; 4]>::as_ref(&view.col(3))); + raw + } +} + pub struct GraphicsState{ - screen_size: (u32, u32), pipelines: GraphicsPipelines, bind_groups: GraphicsBindGroups, bind_group_layouts: GraphicsBindGroupLayouts, samplers: GraphicsSamplers, - temp_squid_texture_view: wgpu::TextureView, + camera:GraphicsCamera, camera_buf: wgpu::Buffer, + temp_squid_texture_view: wgpu::TextureView, models: Vec, depth_view: wgpu::TextureView, staging_belt: wgpu::util::StagingBelt, @@ -66,8 +117,9 @@ impl GraphicsState{ pub struct GlobalState{ start_time: std::time::Instant, manual_mouse_lock:bool, + mouse:physics::MouseState, graphics:GraphicsState, - physics:physics::PhysicsState, + physics_thread:worker::Worker,physics::PhysicsOutputState>, } impl GlobalState{ @@ -95,77 +147,6 @@ impl GlobalState{ depth_texture.create_view(&wgpu::TextureViewDescriptor::default()) } - fn generate_model_physics(&mut self,indexed_models:&model::IndexedModelInstances){ - let mut starts=Vec::new(); - let mut spawns=Vec::new(); - let mut ordered_checkpoints=Vec::new(); - let mut unordered_checkpoints=Vec::new(); - for model in &indexed_models.models{ - //make aabb and run vertices to get realistic bounds - for model_instance in &model.instances{ - if let Some(model_physics)=physics::ModelPhysics::from_model(model,model_instance){ - let model_id=self.physics.models.len() as u32; - self.physics.models.push(model_physics); - for attr in &model_instance.temp_indexing{ - match attr{ - model::TempIndexedAttributes::Start{mode_id}=>starts.push((*mode_id,model_id)), - model::TempIndexedAttributes::Spawn{mode_id,stage_id}=>spawns.push((*mode_id,model_id,*stage_id)), - model::TempIndexedAttributes::OrderedCheckpoint{mode_id,checkpoint_id}=>ordered_checkpoints.push((*mode_id,model_id,*checkpoint_id)), - model::TempIndexedAttributes::UnorderedCheckpoint{mode_id}=>unordered_checkpoints.push((*mode_id,model_id)), - } - } - } - } - } - //I don't wanna write structs for temporary structures - //this code builds ModeDescriptions from the unsorted lists at the top of the function - starts.sort_by_key(|tup|tup.0); - let mut eshmep=std::collections::HashMap::new(); - let mut modedatas:Vec<(u32,Vec<(u32,u32)>,Vec<(u32,u32)>,Vec)>=starts.into_iter().enumerate().map(|(i,tup)|{ - eshmep.insert(tup.0,i); - (tup.1,Vec::new(),Vec::new(),Vec::new()) - }).collect(); - for tup in spawns{ - if let Some(mode_id)=eshmep.get(&tup.0){ - if let Some(modedata)=modedatas.get_mut(*mode_id){ - modedata.1.push((tup.2,tup.1)); - } - } - } - for tup in ordered_checkpoints{ - if let Some(mode_id)=eshmep.get(&tup.0){ - if let Some(modedata)=modedatas.get_mut(*mode_id){ - modedata.2.push((tup.2,tup.1)); - } - } - } - for tup in unordered_checkpoints{ - if let Some(mode_id)=eshmep.get(&tup.0){ - if let Some(modedata)=modedatas.get_mut(*mode_id){ - modedata.3.push(tup.1); - } - } - } - let num_modes=self.physics.modes.len(); - for (mode_id,mode) in eshmep{ - self.physics.mode_from_mode_id.insert(mode_id,num_modes+mode); - } - self.physics.modes.append(&mut modedatas.into_iter().map(|mut tup|{ - tup.1.sort_by_key(|tup|tup.0); - tup.2.sort_by_key(|tup|tup.0); - let mut eshmep1=std::collections::HashMap::new(); - let mut eshmep2=std::collections::HashMap::new(); - model::ModeDescription{ - start:tup.0, - spawns:tup.1.into_iter().enumerate().map(|(i,tup)|{eshmep1.insert(tup.0,i);tup.1}).collect(), - ordered_checkpoints:tup.2.into_iter().enumerate().map(|(i,tup)|{eshmep2.insert(tup.0,i);tup.1}).collect(), - unordered_checkpoints:tup.3, - spawn_from_stage_id:eshmep1, - ordered_checkpoint_from_checkpoint_id:eshmep2, - } - }).collect()); - println!("Physics Objects: {}",self.physics.models.len()); - } fn generate_model_graphics(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,indexed_models:model::IndexedModelInstances){ //generate texture view per texture @@ -408,20 +389,6 @@ fn get_instances_buffer_data(instances:&[ModelGraphicsInstance]) -> Vec { raw } -fn to_uniform_data(camera: &physics::Camera, pos: glam::Vec3) -> [f32; 16 * 3 + 4] { - let proj=camera.proj(); - let proj_inv = proj.inverse(); - let view=camera.view(pos); - let view_inv = view.inverse(); - - let mut raw = [0f32; 16 * 3 + 4]; - raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]); - raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]); - raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view_inv)[..]); - raw[48..52].copy_from_slice(AsRef::<[f32; 4]>::as_ref(&view.col(3))); - raw -} - impl framework::Example for GlobalState { fn optional_features() -> wgpu::Features { wgpu::Features::TEXTURE_COMPRESSION_ASTC @@ -582,25 +549,6 @@ impl framework::Example for GlobalState { source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); - let physics = physics::PhysicsState { - spawn_point:glam::vec3(0.0,50.0,0.0), - body: physics::Body::with_pva(glam::vec3(0.0,50.0,0.0),glam::vec3(0.0,0.0,0.0),glam::vec3(0.0,-100.0,0.0)), - time: 0, - style:physics::StyleModifiers::default(), - grounded: false, - contacts: std::collections::HashMap::new(), - intersects: std::collections::HashMap::new(), - models: Vec::new(), - walk: physics::WalkState::new(), - camera: physics::Camera::from_offset(glam::vec3(0.0,4.5-2.5,0.0),(config.width as f32)/(config.height as f32)), - mouse_interpolation: physics::MouseInterpolationState::new(), - controls: 0, - world:physics::WorldState{}, - game:physics::GameMechanicsState::default(), - modes:Vec::new(), - mode_from_mode_id:std::collections::HashMap::new(), - }; - //load textures let device_features = device.features(); @@ -795,7 +743,10 @@ impl framework::Example for GlobalState { multiview: None, }); - let camera_uniforms = to_uniform_data(&physics.camera,physics.body.extrapolated_position(0)); + let mut physics = physics::PhysicsState::default(); + + let camera=GraphicsCamera::new(glam::uvec2(config.width,config.height), 1.0); + let camera_uniforms = camera.to_uniform_data(physics.output().adjust_mouse(&physics.next_mouse)); let camera_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Camera"), contents: bytemuck::cast_slice(&camera_uniforms), @@ -811,6 +762,7 @@ impl framework::Example for GlobalState { ], label: Some("Camera"), }); + let skybox_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &skybox_texture_bind_group_layout, entries: &[ @@ -829,7 +781,6 @@ impl framework::Example for GlobalState { let depth_view = Self::create_depth_texture(config, device); let graphics=GraphicsState { - screen_size: (config.width,config.height), pipelines:GraphicsPipelines{ skybox:sky_pipeline, model:model_pipeline @@ -838,6 +789,7 @@ impl framework::Example for GlobalState { camera:camera_bind_group, skybox_texture:skybox_texture_bind_group, }, + camera, camera_buf, models: Vec::new(), depth_view, @@ -847,20 +799,30 @@ impl framework::Example for GlobalState { temp_squid_texture_view: squid_texture_view, }; - let mut state=GlobalState{ - start_time:Instant::now(), - manual_mouse_lock:false, - graphics, - physics, - }; - let indexed_model_instances=model::IndexedModelInstances{ textures:Vec::new(), models:indexed_models, spawn_point:glam::Vec3::Y*50.0, modes:Vec::new(), }; - state.generate_model_physics(&indexed_model_instances); + + //how to multithread + + //1. build + physics.generate_models(&indexed_model_instances); + + //2. move + let physics_thread=physics.into_worker(); + + //3. forget + + let mut state=GlobalState{ + start_time:Instant::now(), + manual_mouse_lock:false, + mouse:physics::MouseState::default(), + graphics, + physics_thread, + }; state.generate_model_graphics(&device,&queue,indexed_model_instances); let args:Vec=std::env::args().collect(); @@ -911,18 +873,20 @@ impl framework::Example for GlobalState { }{ let spawn_point=indexed_model_instances.spawn_point; //if generate_indexed_models succeeds, clear the previous ones - self.physics.clear(); self.graphics.clear(); - self.physics.game.stage_id=0; - self.physics.spawn_point=spawn_point; - self.generate_model_physics(&indexed_model_instances); + + let mut physics=physics::PhysicsState::default(); + physics.game.stage_id=0; + physics.spawn_point=spawn_point; + physics.process_instruction(instruction::TimedInstruction{ + time:physics.time, + instruction: PhysicsInstruction::Input(physics::PhysicsInputInstruction::Reset), + }); + physics.generate_models(&indexed_model_instances); + self.physics_thread=physics.into_worker(); + self.generate_model_graphics(device,queue,indexed_model_instances); //manual reset - let time=self.physics.time; - instruction::InstructionConsumer::process_instruction(&mut self.physics, instruction::TimedInstruction{ - time, - instruction: PhysicsInstruction::Input(InputInstruction::Reset), - }); }else{ println!("No modeldatas were generated"); } @@ -983,7 +947,7 @@ impl framework::Example for GlobalState { 15=>{//Tab if s{ self.manual_mouse_lock=false; - match window.set_cursor_position(winit::dpi::PhysicalPosition::new(self.graphics.screen_size.0 as f32/2.0, self.graphics.screen_size.1 as f32/2.0)){ + match window.set_cursor_position(winit::dpi::PhysicalPosition::new(self.graphics.camera.screen_size.x as f32/2.0, self.graphics.camera.screen_size.y as f32/2.0)){ Ok(())=>(), Err(e)=>println!("Could not set cursor position: {:?}",e), } @@ -1012,18 +976,17 @@ impl framework::Example for GlobalState { }, _ => {println!("scancode {}",keycode);None}, }{ - self.physics.run(time); - self.physics.process_instruction(TimedInstruction{ + self.physics_thread.send(TimedInstruction{ time, - instruction:PhysicsInstruction::Input(input_instruction), - }) + instruction:input_instruction, + }).unwrap(); } }, winit::event::DeviceEvent::MouseMotion { delta,//these (f64,f64) are integers on my machine } => { if self.manual_mouse_lock{ - match window.set_cursor_position(winit::dpi::PhysicalPosition::new(self.graphics.screen_size.0 as f32/2.0, self.graphics.screen_size.1 as f32/2.0)){ + match window.set_cursor_position(winit::dpi::PhysicalPosition::new(self.graphics.camera.screen_size.x as f32/2.0, self.graphics.camera.screen_size.y as f32/2.0)){ Ok(())=>(), Err(e)=>println!("Could not set cursor position: {:?}",e), } @@ -1031,21 +994,22 @@ impl framework::Example for GlobalState { //do not step the physics because the mouse polling rate is higher than the physics can run. //essentially the previous input will be overwritten until a true step runs //which is fine because they run all the time. - self.physics.process_instruction(TimedInstruction{ + let delta=glam::ivec2(delta.0 as i32,delta.1 as i32); + self.mouse.pos+=delta; + self.physics_thread.send(TimedInstruction{ time, - instruction:PhysicsInstruction::Input(InputInstruction::MoveMouse(glam::ivec2(delta.0 as i32,delta.1 as i32))), - }) + instruction:InputInstruction::MoveMouse(self.mouse.pos), + }).unwrap(); }, winit::event::DeviceEvent::MouseWheel { delta, } => { println!("mousewheel {:?}",delta); if false{//self.physics.style.use_scroll{ - self.physics.run(time); - self.physics.process_instruction(TimedInstruction{ + self.physics_thread.send(TimedInstruction{ time, - instruction:PhysicsInstruction::Input(InputInstruction::Jump(true)),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump - }) + instruction:InputInstruction::Jump(true),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump + }).unwrap(); } } _=>(), @@ -1059,8 +1023,7 @@ impl framework::Example for GlobalState { _queue: &wgpu::Queue, ) { self.graphics.depth_view = Self::create_depth_texture(config, device); - self.graphics.screen_size = (config.width, config.height); - self.physics.camera.set_fov_aspect(1.0,(config.width as f32)/(config.height as f32)); + self.graphics.camera.set_screen_size(glam::uvec2(config.width, config.height)); } fn render( @@ -1070,15 +1033,20 @@ impl framework::Example for GlobalState { queue: &wgpu::Queue, _spawner: &framework::Spawner, ) { + //ideally this would be scheduled to execute and finish right before the render. let time=self.start_time.elapsed().as_nanos() as i64; - - self.physics.run(time); + self.physics_thread.send(TimedInstruction{ + time, + instruction:InputInstruction::Idle, + }).unwrap(); + //update time lol + self.mouse.time=time; let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); // update rotation - let camera_uniforms = to_uniform_data(&self.physics.camera,self.physics.body.extrapolated_position(time)); + let camera_uniforms = self.graphics.camera.to_uniform_data(self.physics_thread.grab_clone().adjust_mouse(&self.mouse)); self.graphics.staging_belt .write_buffer( &mut encoder, diff --git a/src/physics.rs b/src/physics.rs index e3ee7b0..7d7bcae 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -13,7 +13,22 @@ pub enum PhysicsInstruction { // bool,//true = Force // ) //InputInstructions conditionally activate RefreshWalkTarget (by doing what SetWalkTargetVelocity used to do and then flagging it) - Input(InputInstruction), + Input(PhysicsInputInstruction), +} +#[derive(Debug)] +pub enum PhysicsInputInstruction { + ReplaceMouse(MouseState,MouseState), + SetNextMouse(MouseState), + SetMoveForward(bool), + SetMoveLeft(bool), + SetMoveBack(bool), + SetMoveRight(bool), + SetMoveUp(bool), + SetMoveDown(bool), + SetJump(bool), + SetZoom(bool), + Reset, + Idle, } #[derive(Debug)] pub enum InputInstruction { @@ -32,7 +47,7 @@ pub enum InputInstruction { //for interpolation / networking / playback reasons, most playback heads will always want //to be 1 instruction ahead to generate the next state for interpolation. } -#[derive(Clone,Debug)] +#[derive(Clone)] pub struct Body { position: glam::Vec3,//I64 where 2^32 = 1 u velocity: glam::Vec3,//I64 where 2^32 = 1 u/s @@ -91,49 +106,29 @@ impl crate::instruction::InstructionConsumer for InputState{ } */ -enum MouseInterpolation { - First,//just checks the last value - Lerp,//lerps between -} - //hey dumbass just use a delta -pub struct MouseInterpolationState { - interpolation: MouseInterpolation, - time0: TIME, - time1: TIME, - mouse0: glam::IVec2, - mouse1: glam::IVec2, +#[derive(Clone,Debug)] +pub struct MouseState { + pub pos: glam::IVec2, + pub time: TIME, } - -impl MouseInterpolationState { - pub fn new() -> Self { +impl Default for MouseState{ + fn default() -> Self { Self { - interpolation:MouseInterpolation::First, - time0:0, - time1:1,//ONE NANOSECOND!!!! avoid divide by zero - mouse0:glam::IVec2::ZERO, - mouse1:glam::IVec2::ZERO, + time:0, + pos:glam::IVec2::ZERO, } } - pub fn move_mouse(&mut self,time:TIME,delta:glam::IVec2){ - self.time0=self.time1; - self.mouse0=self.mouse1; - self.time1=time; - self.mouse1=self.mouse1+delta; - } - pub fn interpolated_position(&self,time:TIME) -> glam::IVec2 { - match self.interpolation { - MouseInterpolation::First => self.mouse0, - MouseInterpolation::Lerp => { - let m0=self.mouse0.as_i64vec2(); - let m1=self.mouse1.as_i64vec2(); - //these are deltas - let t1t=(self.time1-time) as i64; - let tt0=(time-self.time0) as i64; - let dt=(self.time1-self.time0) as i64; - ((m0*t1t+m1*tt0)/dt).as_ivec2() - } - } +} +impl MouseState { + pub fn lerp(&self,target:&MouseState,time:TIME)->glam::IVec2 { + let m0=self.pos.as_i64vec2(); + let m1=target.pos.as_i64vec2(); + //these are deltas + let t1t=(target.time-time) as i64; + let tt0=(time-self.time) as i64; + let dt=(target.time-self.time) as i64; + ((m0*t1t+m1*tt0)/dt).as_ivec2() } } @@ -156,15 +151,14 @@ impl WalkState { } } -// Note: we use the Y=up coordinate space in this example. -pub struct Camera { +#[derive(Clone)] +pub struct PhysicsCamera { offset: glam::Vec3, angles: glam::DVec2,//YAW AND THEN PITCH //punch: glam::Vec3, //punch_velocity: glam::Vec3, - fov: glam::Vec2,//slope sensitivity: glam::DVec2, - time: TIME, + mouse:MouseState, } #[inline] @@ -176,45 +170,22 @@ fn mat3_from_rotation_y_f64(angle: f64) -> glam::Mat3 { glam::Vec3::new(sina as f32, 0.0, cosa as f32), ) } -#[inline] -fn perspective_rh(fov_x_slope: f32, fov_y_slope: f32, z_near: f32, z_far: f32) -> glam::Mat4 { - //glam_assert!(z_near > 0.0 && z_far > 0.0); - let r = z_far / (z_near - z_far); - glam::Mat4::from_cols( - glam::Vec4::new(1.0/fov_x_slope, 0.0, 0.0, 0.0), - glam::Vec4::new(0.0, 1.0/fov_y_slope, 0.0, 0.0), - glam::Vec4::new(0.0, 0.0, r, -1.0), - glam::Vec4::new(0.0, 0.0, r * z_near, 0.0), - ) -} -impl Camera { - pub fn from_offset(offset:glam::Vec3,aspect:f32) -> Self { +impl PhysicsCamera { + pub fn from_offset(offset:glam::Vec3) -> Self { Self{ offset, angles: glam::DVec2::ZERO, - fov: glam::vec2(aspect,1.0), sensitivity: glam::dvec2(1.0/16384.0,1.0/16384.0), - time: 0, + mouse:MouseState{pos:glam::IVec2::ZERO,time:-1},//escape initialization hell divide by zero } } - fn simulate_move_angles(&self, delta: glam::IVec2) -> glam::DVec2 { - let mut a=self.angles-self.sensitivity*delta.as_dvec2(); + pub fn simulate_move_angles(&self, mouse_pos: glam::IVec2) -> glam::DVec2 { + let mut a=self.angles-self.sensitivity*(mouse_pos-self.mouse.pos).as_dvec2(); a.y=a.y.clamp(-std::f64::consts::FRAC_PI_2, std::f64::consts::FRAC_PI_2); return a } - fn simulate_move_rotation_y(&self, delta_x: i32) -> glam::Mat3 { - mat3_from_rotation_y_f64(self.angles.x-self.sensitivity.x*(delta_x as f64)) - } - pub fn proj(&self)->glam::Mat4{ - perspective_rh(self.fov.x, self.fov.y, 0.5, 2000.0) - } - pub fn view(&self,pos:glam::Vec3)->glam::Mat4{ - //f32 good enough for view matrix - glam::Mat4::from_translation(pos+self.offset) * glam::Mat4::from_euler(glam::EulerRot::YXZ, self.angles.x as f32, self.angles.y as f32, 0f32) - } - pub fn set_fov_aspect(&mut self,fov:f32,aspect:f32){ - self.fov.x=fov*aspect; - self.fov.y=fov; + fn simulate_move_rotation_y(&self, mouse_pos_x: i32) -> glam::Mat3 { + mat3_from_rotation_y_f64(self.angles.x-self.sensitivity.x*((mouse_pos_x-self.mouse.pos.x) as f64)) } } @@ -275,7 +246,7 @@ impl StyleModifiers{ const UP_DIR:glam::Vec3 = glam::Vec3::Y; fn get_control(&self,control:u32,controls:u32)->bool{ - controls&self.controls_mask&control!=0 + controls&self.controls_mask&control==control } fn get_control_dir(&self,controls:u32)->glam::Vec3{ @@ -319,8 +290,8 @@ pub struct PhysicsState{ pub intersects:std::collections::HashMap::, //pub intersections: Vec, //camera must exist in state because wormholes modify the camera, also camera punch - pub camera:Camera, - pub mouse_interpolation:MouseInterpolationState, + pub camera:PhysicsCamera, + pub next_mouse:MouseState,//Where is the mouse headed next pub controls:u32, pub walk:WalkState, pub grounded:bool, @@ -333,6 +304,16 @@ pub struct PhysicsState{ //This is not the same as Reset which teleports you to Spawn0 pub spawn_point:glam::Vec3, } +#[derive(Clone)] +pub struct PhysicsOutputState{ + camera:PhysicsCamera, + body:Body, +} +impl PhysicsOutputState{ + pub fn adjust_mouse(&self,mouse:&MouseState)->(glam::Vec3,glam::Vec2){ + (self.body.extrapolated_position(mouse.time)+self.camera.offset,self.camera.simulate_move_angles(mouse.pos).as_vec2()) + } +} #[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] pub enum AabbFace{ @@ -554,6 +535,29 @@ impl Body { } } +impl Default for PhysicsState{ + fn default() -> Self { + Self{ + spawn_point:glam::vec3(0.0,50.0,0.0), + body: Body::with_pva(glam::vec3(0.0,50.0,0.0),glam::vec3(0.0,0.0,0.0),glam::vec3(0.0,-100.0,0.0)), + time: 0, + style:StyleModifiers::default(), + grounded: false, + contacts: std::collections::HashMap::new(), + intersects: std::collections::HashMap::new(), + models: Vec::new(), + walk: WalkState::new(), + camera: PhysicsCamera::from_offset(glam::vec3(0.0,4.5-2.5,0.0)), + next_mouse: MouseState::default(), + controls: 0, + world:WorldState{}, + game:GameMechanicsState::default(), + modes:Vec::new(), + mode_from_mode_id:std::collections::HashMap::new(), + } + } +} + impl PhysicsState { pub fn clear(&mut self){ self.models.clear(); @@ -561,6 +565,173 @@ impl PhysicsState { self.contacts.clear(); self.intersects.clear(); } + + pub fn into_worker(mut self)->crate::worker::Worker,PhysicsOutputState>{ + let mut mouse_blocking=true; + let mut last_mouse_time=self.next_mouse.time; + let mut timeline=std::collections::VecDeque::new(); + crate::worker::Worker::new(self.output(),move |ins:TimedInstruction|{ + if if let Some(phys_input)=match ins.instruction{ + InputInstruction::MoveMouse(m)=>{ + if mouse_blocking{ + //tell the game state which is living in the past about its future + timeline.push_front(TimedInstruction{ + time:last_mouse_time, + instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:ins.time,pos:m}), + }); + }else{ + //mouse has just started moving again after being still for longer than 10ms. + //replace the entire mouse interpolation state to avoid an intermediate state with identical m0.t m1.t timestamps which will divide by zero + timeline.push_front(TimedInstruction{ + time:last_mouse_time, + instruction:PhysicsInputInstruction::ReplaceMouse( + MouseState{time:last_mouse_time,pos:self.next_mouse.pos}, + MouseState{time:ins.time,pos:m} + ), + }); + //delay physics execution until we have an interpolation target + mouse_blocking=true; + } + last_mouse_time=ins.time; + None + }, + InputInstruction::MoveForward(s)=>Some(PhysicsInputInstruction::SetMoveForward(s)), + InputInstruction::MoveLeft(s)=>Some(PhysicsInputInstruction::SetMoveLeft(s)), + InputInstruction::MoveBack(s)=>Some(PhysicsInputInstruction::SetMoveBack(s)), + InputInstruction::MoveRight(s)=>Some(PhysicsInputInstruction::SetMoveRight(s)), + InputInstruction::MoveUp(s)=>Some(PhysicsInputInstruction::SetMoveUp(s)), + InputInstruction::MoveDown(s)=>Some(PhysicsInputInstruction::SetMoveDown(s)), + InputInstruction::Jump(s)=>Some(PhysicsInputInstruction::SetJump(s)), + InputInstruction::Zoom(s)=>Some(PhysicsInputInstruction::SetZoom(s)), + InputInstruction::Reset=>Some(PhysicsInputInstruction::Reset), + InputInstruction::Idle=>Some(PhysicsInputInstruction::Idle), + }{ + //non-mouse event + timeline.push_back(TimedInstruction{ + time:ins.time, + instruction:phys_input, + }); + + if mouse_blocking{ + //assume the mouse has stopped moving after 10ms. + //shitty mice are 125Hz which is 8ms so this should cover that. + //setting this to 100us still doesn't print even though it's 10x lower than the polling rate, + //so mouse events are probably not handled separately from drawing and fire right before it :( + if 10_000_000PhysicsOutputState{ + PhysicsOutputState{ + body:self.body.clone(), + camera:self.camera.clone(), + } + } + + pub fn generate_models(&mut self,indexed_models:&crate::model::IndexedModelInstances){ + let mut starts=Vec::new(); + let mut spawns=Vec::new(); + let mut ordered_checkpoints=Vec::new(); + let mut unordered_checkpoints=Vec::new(); + for model in &indexed_models.models{ + //make aabb and run vertices to get realistic bounds + for model_instance in &model.instances{ + if let Some(model_physics)=ModelPhysics::from_model(model,model_instance){ + let model_id=self.models.len() as u32; + self.models.push(model_physics); + for attr in &model_instance.temp_indexing{ + match attr{ + crate::model::TempIndexedAttributes::Start{mode_id}=>starts.push((*mode_id,model_id)), + crate::model::TempIndexedAttributes::Spawn{mode_id,stage_id}=>spawns.push((*mode_id,model_id,*stage_id)), + crate::model::TempIndexedAttributes::OrderedCheckpoint{mode_id,checkpoint_id}=>ordered_checkpoints.push((*mode_id,model_id,*checkpoint_id)), + crate::model::TempIndexedAttributes::UnorderedCheckpoint{mode_id}=>unordered_checkpoints.push((*mode_id,model_id)), + } + } + } + } + } + //I don't wanna write structs for temporary structures + //this code builds ModeDescriptions from the unsorted lists at the top of the function + starts.sort_by_key(|tup|tup.0); + let mut eshmep=std::collections::HashMap::new(); + let mut modedatas:Vec<(u32,Vec<(u32,u32)>,Vec<(u32,u32)>,Vec)>=starts.into_iter().enumerate().map(|(i,tup)|{ + eshmep.insert(tup.0,i); + (tup.1,Vec::new(),Vec::new(),Vec::new()) + }).collect(); + for tup in spawns{ + if let Some(mode_id)=eshmep.get(&tup.0){ + if let Some(modedata)=modedatas.get_mut(*mode_id){ + modedata.1.push((tup.2,tup.1)); + } + } + } + for tup in ordered_checkpoints{ + if let Some(mode_id)=eshmep.get(&tup.0){ + if let Some(modedata)=modedatas.get_mut(*mode_id){ + modedata.2.push((tup.2,tup.1)); + } + } + } + for tup in unordered_checkpoints{ + if let Some(mode_id)=eshmep.get(&tup.0){ + if let Some(modedata)=modedatas.get_mut(*mode_id){ + modedata.3.push(tup.1); + } + } + } + let num_modes=self.modes.len(); + for (mode_id,mode) in eshmep{ + self.mode_from_mode_id.insert(mode_id,num_modes+mode); + } + self.modes.append(&mut modedatas.into_iter().map(|mut tup|{ + tup.1.sort_by_key(|tup|tup.0); + tup.2.sort_by_key(|tup|tup.0); + let mut eshmep1=std::collections::HashMap::new(); + let mut eshmep2=std::collections::HashMap::new(); + crate::model::ModeDescription{ + start:tup.0, + spawns:tup.1.into_iter().enumerate().map(|(i,tup)|{eshmep1.insert(tup.0,i);tup.1}).collect(), + ordered_checkpoints:tup.2.into_iter().enumerate().map(|(i,tup)|{eshmep2.insert(tup.0,i);tup.1}).collect(), + unordered_checkpoints:tup.3, + spawn_from_stage_id:eshmep1, + ordered_checkpoint_from_checkpoint_id:eshmep2, + } + }).collect()); + println!("Physics Objects: {}",self.models.len()); + } + pub fn get_mode(&self,mode_id:u32)->Option<&crate::model::ModeDescription>{ if let Some(&mode)=self.mode_from_mode_id.get(&mode_id){ self.modes.get(mode) @@ -1004,13 +1175,15 @@ impl crate::instruction::InstructionEmitter for PhysicsState impl crate::instruction::InstructionConsumer for PhysicsState { fn process_instruction(&mut self, ins:TimedInstruction) { match &ins.instruction { - PhysicsInstruction::StrafeTick => (), - PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (), + PhysicsInstruction::Input(PhysicsInputInstruction::Idle) + |PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) + |PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_)) + |PhysicsInstruction::StrafeTick => (), _=>println!("{}|{:?}",ins.time,ins.instruction), } //selectively update body match &ins.instruction { - PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (),//dodge time for mouse movement + //PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (),//dodge time for mouse movement PhysicsInstruction::Input(_) |PhysicsInstruction::ReachWalkTargetVelocity |PhysicsInstruction::CollisionStart(_) @@ -1032,10 +1205,8 @@ impl crate::instruction::InstructionConsumer for PhysicsStat _ => (), }, } - match &general.booster{ - Some(booster)=>self.body.velocity+=booster.velocity, - None=>(), - } + //check ground + self.contacts.insert(c.model,c); match &general.stage_element{ Some(stage_element)=>{ if stage_element.force||self.game.stage_id for PhysicsStat }, None=>(), } - //check ground - self.contacts.insert(c.model,c); //flatten v let mut v=self.body.velocity; self.contact_constrain_velocity(&mut v); + match &general.booster{ + Some(booster)=>{ + v+=booster.velocity; + self.contact_constrain_velocity(&mut v); + }, + None=>(), + } self.body.velocity=v; if self.grounded&&self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){ self.jump(); @@ -1105,7 +1281,7 @@ impl crate::instruction::InstructionConsumer for PhysicsStat } }, PhysicsInstruction::StrafeTick => { - let camera_mat=self.camera.simulate_move_rotation_y(self.mouse_interpolation.interpolated_position(self.time).x-self.mouse_interpolation.mouse0.x); + let camera_mat=self.camera.simulate_move_rotation_y(self.camera.mouse.lerp(&self.next_mouse,self.time).x); let control_dir=camera_mat*self.style.get_control_dir(self.controls); let d=self.body.velocity.dot(control_dir); if d for PhysicsStat let mut refresh_walk_target=true; let mut refresh_walk_target_velocity=true; match input_instruction{ - InputInstruction::MoveMouse(m) => { - self.camera.angles=self.camera.simulate_move_angles(self.mouse_interpolation.mouse1-self.mouse_interpolation.mouse0); - self.mouse_interpolation.move_mouse(self.time,m); + PhysicsInputInstruction::SetNextMouse(m) => { + self.camera.angles=self.camera.simulate_move_angles(self.next_mouse.pos); + (self.camera.mouse,self.next_mouse)=(self.next_mouse.clone(),m); }, - InputInstruction::MoveForward(s) => self.set_control(StyleModifiers::CONTROL_MOVEFORWARD,s), - InputInstruction::MoveLeft(s) => self.set_control(StyleModifiers::CONTROL_MOVELEFT,s), - InputInstruction::MoveBack(s) => self.set_control(StyleModifiers::CONTROL_MOVEBACK,s), - InputInstruction::MoveRight(s) => self.set_control(StyleModifiers::CONTROL_MOVERIGHT,s), - InputInstruction::MoveUp(s) => self.set_control(StyleModifiers::CONTROL_MOVEUP,s), - InputInstruction::MoveDown(s) => self.set_control(StyleModifiers::CONTROL_MOVEDOWN,s), - InputInstruction::Jump(s) => { + PhysicsInputInstruction::ReplaceMouse(m0,m1) => { + self.camera.angles=self.camera.simulate_move_angles(m0.pos); + (self.camera.mouse,self.next_mouse)=(m0,m1); + }, + PhysicsInputInstruction::SetMoveForward(s) => self.set_control(StyleModifiers::CONTROL_MOVEFORWARD,s), + PhysicsInputInstruction::SetMoveLeft(s) => self.set_control(StyleModifiers::CONTROL_MOVELEFT,s), + PhysicsInputInstruction::SetMoveBack(s) => self.set_control(StyleModifiers::CONTROL_MOVEBACK,s), + PhysicsInputInstruction::SetMoveRight(s) => self.set_control(StyleModifiers::CONTROL_MOVERIGHT,s), + PhysicsInputInstruction::SetMoveUp(s) => self.set_control(StyleModifiers::CONTROL_MOVEUP,s), + PhysicsInputInstruction::SetMoveDown(s) => self.set_control(StyleModifiers::CONTROL_MOVEDOWN,s), + PhysicsInputInstruction::SetJump(s) => { self.set_control(StyleModifiers::CONTROL_JUMP,s); if self.grounded{ self.jump(); } refresh_walk_target_velocity=false; }, - InputInstruction::Zoom(s) => { + PhysicsInputInstruction::SetZoom(s) => { self.set_control(StyleModifiers::CONTROL_ZOOM,s); refresh_walk_target=false; }, - InputInstruction::Reset => { + PhysicsInputInstruction::Reset => { //temp self.body.position=self.spawn_point; self.body.velocity=glam::Vec3::ZERO; @@ -1160,12 +1340,12 @@ impl crate::instruction::InstructionConsumer for PhysicsStat self.grounded=false; refresh_walk_target=false; }, - InputInstruction::Idle => {refresh_walk_target=false;},//literally idle! + PhysicsInputInstruction::Idle => {refresh_walk_target=false;},//literally idle! } if refresh_walk_target{ //calculate walk target velocity if refresh_walk_target_velocity{ - let camera_mat=self.camera.simulate_move_rotation_y(self.mouse_interpolation.interpolated_position(self.time).x-self.mouse_interpolation.mouse0.x); + let camera_mat=self.camera.simulate_move_rotation_y(self.camera.mouse.lerp(&self.next_mouse,self.time).x); let control_dir=camera_mat*self.style.get_control_dir(self.controls); self.walk.target_velocity=self.style.walkspeed*control_dir; } diff --git a/src/worker.rs b/src/worker.rs index a28b77b..52e0348 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -6,13 +6,13 @@ use parking_lot::Mutex; //The worker thread publishes the result of its work back to the worker object for every item in the work queue. //The physics (target use case) knows when it has not changed the body, so not updating the value is also an option. -struct Worker { +pub struct Worker { sender: mpsc::Sender, value:Arc>, } impl Worker { - fn newValue+Send+'static>(value:Value,f:F) -> Self { + pub fn newValue+Send+'static>(value:Value,mut f:F) -> Self { let (sender, receiver) = mpsc::channel::(); let ret=Self { sender, @@ -23,8 +23,6 @@ impl Worker { loop { match receiver.recv() { Ok(task) => { - println!("Worker got a task"); - // Process the task let v=f(task);//make sure function is evaluated before lock is acquired *value.lock()=v; } @@ -38,11 +36,11 @@ impl Worker { ret } - fn send(&self,task:Task)->Result<(), mpsc::SendError>{ + pub fn send(&self,task:Task)->Result<(), mpsc::SendError>{ self.sender.send(task) } - fn grab_clone(&self)->Value{ + pub fn grab_clone(&self)->Value{ self.value.lock().clone() } }