use std::time::Instant; use physics::PhysicsInstruction; use render_thread::InputInstruction; use instruction::{TimedInstruction, InstructionConsumer}; mod bvh; mod aabb; mod model; mod model_graphics; mod zeroes; mod worker; mod integer; mod physics; mod graphics; mod settings; mod framework; mod primitives; mod instruction; mod load_roblox; mod render_thread; mod window; pub struct GlobalState{ start_time: std::time::Instant, manual_mouse_lock:bool, mouse:std::sync::Arc>, user_settings:settings::UserSettings, //Ideally the graphics thread worker description is: /* WorkerDescription{ input:Immediate, output:Realtime(PoolOrdering::Ordered(3)), } */ //up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order graphics_thread:worker::INWorker, physics_thread:worker::QNWorker>, } fn load_file(path: std::path::PathBuf)->Option{ println!("Loading file: {:?}", &path); //oh boy! let's load the map! if let Ok(file)=std::fs::File::open(path){ let mut input = std::io::BufReader::new(file); let mut first_8=[0u8;8]; //.rbxm roblox binary = "{ match match &first_8[4..8]{ b"lox!"=>rbx_binary::from_reader(input).map_err(|e|format!("{:?}",e)), b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(|e|format!("{:?}",e)), other=>Err(format!("Unknown Roblox file type {:?}",other)), }{ Ok(dom)=>Some(load_roblox::generate_indexed_models(dom)), Err(e)=>{ println!("Error loading roblox file:{:?}",e); None }, } }, //b"VBSP"=>Some(load_bsp::generate_indexed_models(input)), //b"SNFM"=>Some(sniffer::generate_indexed_models(input)), //b"SNFB"=>Some(sniffer::load_bot(input)), other=>{ println!("loser file {:?}",other); None }, } }else{ println!("Failed to read first 8 bytes and seek back to beginning of file."); None } }else{ println!("Could not open file"); None } } impl framework::Example for GlobalState { fn optional_features() -> wgpu::Features { wgpu::Features::TEXTURE_COMPRESSION_ASTC | wgpu::Features::TEXTURE_COMPRESSION_ETC2 } fn required_features() -> wgpu::Features { wgpu::Features::TEXTURE_COMPRESSION_BC } fn required_limits() -> wgpu::Limits { wgpu::Limits::default() //framework.rs was using goofy limits that caused me a multi-day headache } fn init() -> Self { //wee let user_settings=settings::read_user_settings(); let mut indexed_models = Vec::new(); indexed_models.append(&mut model::generate_indexed_model_list_from_obj(obj::ObjData::load_buf(&include_bytes!("../models/teslacyberv3.0.obj")[..]).unwrap(),glam::Vec4::ONE)); indexed_models.push(primitives::unit_sphere()); indexed_models.push(primitives::unit_cylinder()); indexed_models.push(primitives::unit_cube()); println!("models.len = {:?}", indexed_models.len()); indexed_models[0].instances.push(model::ModelInstance{ transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(10.,0.,-10.))).unwrap(), ..Default::default() }); //quad monkeys indexed_models[1].instances.push(model::ModelInstance{ transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(10.,5.,10.))).unwrap(), ..Default::default() }); indexed_models[1].instances.push(model::ModelInstance{ transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(20.,5.,10.))).unwrap(), color:glam::vec4(1.0,0.0,0.0,1.0), ..Default::default() }); indexed_models[1].instances.push(model::ModelInstance{ transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(10.,5.,20.))).unwrap(), color:glam::vec4(0.0,1.0,0.0,1.0), ..Default::default() }); indexed_models[1].instances.push(model::ModelInstance{ transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(20.,5.,20.))).unwrap(), color:glam::vec4(0.0,0.0,1.0,1.0), ..Default::default() }); //decorative monkey indexed_models[1].instances.push(model::ModelInstance{ transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(15.,10.,15.))).unwrap(), color:glam::vec4(0.5,0.5,0.5,0.5), attributes:model::CollisionAttributes::Decoration, ..Default::default() }); //teapot indexed_models[2].instances.push(model::ModelInstance{ transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_scale_rotation_translation(glam::vec3(0.5, 1.0, 0.2),glam::quat(-0.22248298016985793,-0.839457167990537,-0.05603504040830783,-0.49261857546227916),glam::vec3(-10.,7.,10.))).unwrap(), ..Default::default() }); //ground indexed_models[3].instances.push(model::ModelInstance{ transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(0.,0.,0.))*glam::Affine3A::from_scale(glam::vec3(160.0, 1.0, 160.0))).unwrap(), ..Default::default() }); let mut graphics=GraphicsState::new(); graphics.load_user_settings(&user_settings); let indexed_model_instances=model::IndexedModelInstances{ textures:Vec::new(), models:indexed_models, spawn_point:integer::Planar64Vec3::Y*50, modes:Vec::new(), }; //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(), user_settings, graphics, physics_thread, }; state.generate_model_graphics(&device,&queue,indexed_model_instances); let args:Vec=std::env::args().collect(); if args.len()==2{ let indexed_model_instances=load_file(std::path::PathBuf::from(&args[1])); state.render_thread=RenderThread::new(user_settings,indexed_model_instances); } return state; } #[allow(clippy::single_match)] fn update(&mut self, window: &winit::window::Window, event: winit::event::WindowEvent) { let time=integer::Time::from_nanos(self.start_time.elapsed().as_nanos() as i64); match event { winit::event::WindowEvent::DroppedFile(path)=>{ std::thread::spawn(move ||{ let indexed_model_instances=load_file(path); self.render_thread.send(Instruction::Die(indexed_model_instances)); }); }, winit::event::WindowEvent::Focused(state)=>{ //pause unpause //recalculate pressed keys on focus }, winit::event::WindowEvent::KeyboardInput { input:winit::event::KeyboardInput{state, virtual_keycode,..}, .. }=>{ let s=match state { winit::event::ElementState::Pressed => true, winit::event::ElementState::Released => false, }; match virtual_keycode{ Some(winit::event::VirtualKeyCode::Tab)=>{ if s{ self.manual_mouse_lock=false; 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), } match window.set_cursor_grab(winit::window::CursorGrabMode::None){ Ok(())=>(), Err(e)=>println!("Could not release cursor: {:?}",e), } }else{ //if cursor is outside window don't lock but apparently there's no get pos function //let pos=window.get_cursor_pos(); match window.set_cursor_grab(winit::window::CursorGrabMode::Locked){ Ok(())=>(), Err(_)=>{ match window.set_cursor_grab(winit::window::CursorGrabMode::Confined){ Ok(())=>(), Err(e)=>{ self.manual_mouse_lock=true; println!("Could not confine cursor: {:?}",e) }, } } } } window.set_cursor_visible(s); }, Some(winit::event::VirtualKeyCode::F11)=>{ if s{ if window.fullscreen().is_some(){ window.set_fullscreen(None); }else{ window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None))); } } }, Some(winit::event::VirtualKeyCode::Escape)=>{ if s{ self.manual_mouse_lock=false; match window.set_cursor_grab(winit::window::CursorGrabMode::None){ Ok(())=>(), Err(e)=>println!("Could not release cursor: {:?}",e), } window.set_cursor_visible(true); } }, Some(keycode)=>{ if let Some(input_instruction)=match keycode { winit::event::VirtualKeyCode::W => Some(InputInstruction::MoveForward(s)), winit::event::VirtualKeyCode::A => Some(InputInstruction::MoveLeft(s)), winit::event::VirtualKeyCode::S => Some(InputInstruction::MoveBack(s)), winit::event::VirtualKeyCode::D => Some(InputInstruction::MoveRight(s)), winit::event::VirtualKeyCode::E => Some(InputInstruction::MoveUp(s)), winit::event::VirtualKeyCode::Q => Some(InputInstruction::MoveDown(s)), winit::event::VirtualKeyCode::Space => Some(InputInstruction::Jump(s)), winit::event::VirtualKeyCode::Z => Some(InputInstruction::Zoom(s)), winit::event::VirtualKeyCode::R => if s{Some(InputInstruction::Reset)}else{None}, _ => None, }{ self.physics_thread.send(TimedInstruction{ time, instruction:input_instruction, }).unwrap(); } }, _=>(), } }, _=>(), } } fn device_event(&mut self, window: &winit::window::Window, event: winit::event::DeviceEvent) { //there's no way this is the best way get a timestamp. let time=integer::Time::from_nanos(self.start_time.elapsed().as_nanos() as i64); match event { 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.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), } } //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. let delta=glam::ivec2(delta.0 as i32,delta.1 as i32); self.mouse.pos+=delta; self.physics_thread.send(TimedInstruction{ time, instruction:InputInstruction::MoveMouse(self.mouse.pos), }).unwrap(); }, winit::event::DeviceEvent::MouseWheel { delta, } => { println!("mousewheel {:?}",delta); if false{//self.physics.style.use_scroll{ self.physics_thread.send(TimedInstruction{ time, 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(); } } _=>(), } } } fn main() { framework::run::( format!("Strafe Client v{}", env!("CARGO_PKG_VERSION") ).as_str() ); }