1 extern crate env_logger; 2 extern crate imgui; 3 extern crate imgui_wgpu; 4 #[macro_use] 5 extern crate lazy_static; 6 #[macro_use] 7 extern crate serde_derive; 8 extern crate tobj; 9 extern crate toml; 10 extern crate winit_24; 11 12 use std::collections::HashMap; 13 use std::f32::consts::PI; 14 use std::fs; 15 use std::sync::{Arc, Mutex}; 16 #[cfg(not(target_arch = "wasm32"))] 17 use std::time::{Duration, Instant}; 18 19 use cgmath::{ 20 Decomposed, Deg, Euler, InnerSpace, Point3, Quaternion, Rad, Rotation3, SquareMatrix, 21 }; 22 use futures::executor::block_on; 23 use futures::task::LocalSpawn; 24 use futures::FutureExt; 25 use gilrs::Event as GilEvent; 26 use gilrs::{Gamepad, Gilrs}; 27 use imgui::FontSource; 28 use imgui::__core::convert::TryInto; 29 use imgui::*; 30 use imgui_wgpu::{Renderer as ImguiRenderer, RendererConfig as ImguiRendererConfig}; 31 use legion::systems::{SyncResources, UnsafeResources}; 32 use legion::*; 33 use log::LevelFilter; 34 use rapier3d::counters::Timer; 35 use rapier3d::dynamics::{ 36 IntegrationParameters, JointSet, RigidBody, RigidBodyBuilder, RigidBodyHandle, RigidBodySet, 37 }; 38 use rapier3d::geometry::{BroadPhase, ColliderBuilder, ColliderHandle, ColliderSet, NarrowPhase}; 39 use rapier3d::math; 40 use rapier3d::math::Point; 41 use rapier3d::na::{Isometry, Isometry3, Vector, Vector3}; 42 use rapier3d::pipeline::PhysicsPipeline; 43 use wgpu::{BindGroup, Buffer, TextureView}; 44 use wgpu_subscriber; 45 use winit_24::event::DeviceEvent::MouseMotion; 46 use winit_24::event::{ElementState, VirtualKeyCode}; 47 use winit_24::event_loop::EventLoopProxy; 48 use winit_24::platform::unix::x11::ffi::Time; 49 use winit_24::window::Window; 50 use winit_24::{ 51 event::{self, WindowEvent}, 52 event_loop::{ControlFlow, EventLoop}, 53 }; 54 55 use crate::camera::{Camera, CameraController}; 56 use crate::components::{Collider, ImguiWindow, LoopState, Physics, Position}; 57 use crate::geometry::{load_obj, RawMesh}; 58 use crate::imgui_supp::extended_winit_imgui_support; 59 use crate::imgui_supp::imgui_support::{ImguiContext, ImguiPlatform}; 60 use crate::owned_event::{OwnedEvent, OwnedEventExtension}; 61 use crate::physics::state::PhysicsState; 62 use crate::render::system::ImguiPerformanceProfilerLine; 63 use crate::runtime::state::RuntimeState; 64 use winit_24::dpi::PhysicalSize; 65 66 mod camera; 67 mod components; 68 mod geometry; 69 mod imgui_supp; 70 mod light; 71 mod owned_event; 72 mod physics; 73 mod render; 74 mod runtime; 75 76 /* 77 78 Collision detection 79 https://nphysics.org/rigid_body_simulations_with_contacts/ 80 81 Obj file format 82 http://paulbourke.net/dataformats/obj/ 83 84 tobj obj loader 85 https://docs.rs/tobj/2.0.3/tobj/index.html 86 87 mesh generator lib, might be useful 88 https://docs.rs/genmesh/0.6.2/genmesh/ 89 90 legion ECS 91 https://github.com/amethyst/legion 92 93 94 ECS 95 animation 96 config / save loading (sorta!) 97 render 3d (good!) 98 input/io (yep!) 99 collision / physics (yep!) 100 entities & behaviours (got the entities!) 101 scripting! 102 103 104 Todo: 105 Load scene imgui interface w/ toml files 106 FPS graph port from voxel raycaster 107 better imgui interface with components & systems 108 Figure out eventing, GameInput, passing all events, etc. 109 + texturing 110 111 I need to figure out the way that I want to do 2d graphics in a 3d engine... 112 I suppose I will need sprites. And those are just 2 polygons which are textured 113 114 115 116 */ 117 118 //log::info!(""); 119 120 // ImGUI works on more or less an unsafe global state. which is MegaLame 121 static mut CURRENT_UI: Option<imgui::Ui<'static>> = None; 122 pub unsafe fn current_ui<'a>() -> Option<&'a imgui::Ui<'a>> { 123 CURRENT_UI.as_ref() 124 } 125 126 127 128 fn main() { 129 let logger = env_logger::builder() 130 .filter(Some("minimal_viable_game_engine"), LevelFilter::Info) 131 .init(); 132 133 let mut world = World::default(); 134 135 let mut imgui_prepare_schedule = Schedule::builder() 136 .add_system(render::system::imgui_prepare_system()) 137 .build(); 138 139 let mut load_schedule = Schedule::builder() 140 .add_system(runtime::system::runtime_load_system()) 141 .add_system(runtime::system::runtime_spawn_system()) 142 .flush() 143 .build(); 144 145 let mut render_schedule = Schedule::builder() 146 .add_system(render::system::render_imgui_system()) 147 .add_system(render::system::render_test_system()) 148 .add_system(render::system::render_performance_flag_system()) 149 .build(); 150 151 let mut update_schedule = Schedule::builder() 152 .add_system(physics::system::update_camera_system()) 153 .add_system(physics::system::run_physics_system()) 154 .add_system(physics::system::update_models_system()) 155 // next system here, gamelogic update system? 156 .build(); 157 158 let mut event_schedule = Schedule::builder() 159 .add_system(owned_event::event_dispatch_system()) 160 .build(); 161 162 let event_loop = EventLoop::<OwnedEventExtension>::with_user_event(); 163 let mut builder = winit_24::window::WindowBuilder::new(); 164 builder = builder.with_title("MVGE"); 165 builder = builder.with_inner_size(PhysicalSize::new(1200,900)); 166 167 let window = builder.build(&event_loop).unwrap(); 168 169 let mut resources = Resources::default(); 170 171 172 173 // Load up all the resources 174 { 175 let mut imgui_context = imgui::Context::create(); 176 let mut platform = extended_winit_imgui_support::WinitPlatform::init(&mut imgui_context); 177 platform.attach_window( 178 imgui_context.io_mut(), 179 &window, 180 extended_winit_imgui_support::HiDpiMode::Default, 181 ); 182 183 // imgui_supp rendering context 184 let mut imgui_context = ImguiContext { 185 context: imgui_context, 186 }; 187 let mut imgui_platform = ImguiPlatform { platform: platform }; 188 let font_size = 20.0 as f32; 189 imgui_context.context.io_mut().font_global_scale = 1.0 as f32; 190 imgui_context 191 .context 192 .fonts() 193 .add_font(&[FontSource::DefaultFontData { 194 config: Some(imgui::FontConfig { 195 oversample_h: 1, 196 pixel_snap_h: true, 197 size_pixels: font_size, 198 ..Default::default() 199 }), 200 }]); 201 imgui_context.context.set_ini_filename(None); 202 203 // The renderer 204 let mut renderer = render::state::RenderState::init(&window, &mut imgui_context); 205 206 resources.insert(renderer); 207 resources.insert(Arc::new(Mutex::new(imgui_context))); 208 resources.insert(Arc::new(Mutex::new(imgui_platform))); 209 resources.insert(window); 210 211 // Physics 212 let (physics_state, physics_pipeline) = 213 PhysicsState::build(rapier3d::math::Vector::new(0.0, -9.81, 0.0)); 214 resources.insert(physics_state); 215 resources.insert(physics_pipeline); 216 217 // Loop data 218 resources.insert(LoopState { 219 delta_time: Default::default(), 220 start_time: Instant::now(), 221 step_size: 0.01666, // 60hz 222 }); 223 224 // And our event stack 225 resources.insert(Vec::<OwnedEvent<OwnedEventExtension>>::new()); 226 227 // Our init and runtime data 228 resources.insert(RuntimeState::new()) 229 }; 230 231 setup_gamepad(&event_loop); 232 233 let mut elapsed_time: f32 = { 234 // deltatime since last frame 235 let loop_state = resources.get::<LoopState>().unwrap(); 236 loop_state.start_time.elapsed() 237 } 238 .as_secs_f32(); 239 240 let mut delta_time: f32 = 0.0; 241 let mut accumulator_time: f32 = 0.0; 242 let mut current_time: f32 = elapsed_time; 243 244 event_loop.run(move |event, _, control_flow| { 245 *control_flow = ControlFlow::Poll; 246 match event { 247 event::Event::NewEvents(cause) => { 248 if cause == winit_24::event::StartCause::Init { 249 load_schedule.execute(&mut world, &mut resources); 250 } 251 } 252 253 // This is the big boy section of the event loop 254 // We : dispatch events and clear the queue, query the loops 255 // time data and prep the dt data. Loop the dt locked 256 // conditionally, and run the fps locked renderer 257 event::Event::MainEventsCleared => { 258 event_schedule.execute(&mut world, &mut resources); 259 resources 260 .get_mut::<Vec<OwnedEvent<OwnedEventExtension>>>() 261 .unwrap() 262 .clear(); 263 264 imgui_prepare_schedule.execute(&mut world, &mut resources); 265 266 let (step_size, elapsed_time) = { 267 let mut loop_state = resources.get_mut::<LoopState>().unwrap(); 268 ( 269 loop_state.step_size, 270 loop_state.start_time.elapsed().as_secs_f32(), 271 ) 272 }; 273 delta_time = elapsed_time - current_time; 274 275 { 276 let mut loop_state = resources.get_mut::<LoopState>().unwrap(); 277 loop_state.delta_time = Duration::from_secs_f32(delta_time); 278 } 279 current_time = elapsed_time; 280 if delta_time > 0.02 { 281 delta_time = 0.02; 282 } 283 accumulator_time += delta_time; 284 285 while accumulator_time - step_size >= step_size { 286 accumulator_time -= step_size; 287 288 // ==== DELTA TIME LOCKED ==== 289 update_schedule.execute(&mut world, &mut resources); 290 } 291 // ==== FPS LOCKED ==== 292 render_schedule.execute(&mut world, &mut resources); 293 } 294 // Resizing will queue a request_redraw 295 event::Event::WindowEvent { 296 event: WindowEvent::Resized(size), 297 .. 298 } => { 299 let width = size.width; 300 let height = size.height; 301 302 resources 303 .get_mut::<render::state::RenderState>() 304 .unwrap() 305 .resize(width, height); 306 } 307 event::Event::DeviceEvent { 308 event: winit_24::event::DeviceEvent::Key(keyboard_input), 309 .. 310 } => { 311 if keyboard_input.virtual_keycode.is_some() { 312 match keyboard_input.virtual_keycode.unwrap() { 313 VirtualKeyCode::Escape => { 314 if keyboard_input.state == ElementState::Pressed { 315 *control_flow = ControlFlow::Exit; 316 } else { 317 //d 318 } 319 } 320 _ => (), 321 } 322 } 323 } 324 event::Event::WindowEvent { 325 event: WindowEvent::CloseRequested, 326 .. 327 } => *control_flow = ControlFlow::Exit, 328 event::Event::RedrawRequested(_) => { 329 // Call the render system 330 // imgui_prepare_schedule.execute(&mut world, &mut resources); 331 // render_schedule.execute(&mut world, &mut resources); 332 } 333 _ => {} 334 } 335 336 resources 337 .get_mut::<Vec<OwnedEvent<OwnedEventExtension>>>() 338 .unwrap() 339 .push(event.into()); 340 }); 341 } 342 343 pub fn setup_gamepad(event_loop: &EventLoop<OwnedEventExtension>) { 344 let event_loop_proxy = event_loop.create_proxy(); 345 346 std::thread::spawn(move || { 347 let mut gilrs = Gilrs::new().unwrap(); 348 // Iterate over all connected gamepads 349 let mut gamepad: Option<Gamepad> = None; 350 for (_id, gamepad_) in gilrs.gamepads() { 351 if gamepad_.name() == "PS4" { 352 gamepad = Some(gamepad_); 353 } 354 // println!( 355 // "{} is {:?} {:?}", 356 // gamepad_.name(), 357 // gamepad_.power_info(), 358 // gamepad_.id() 359 // ); 360 } 361 let mut active_gamepad = None; 362 363 loop { 364 while let Some(GilEvent { id, event, time }) = gilrs.next_event() { 365 //println!("{:?} New event from {}: {:?}", time, id, event); 366 active_gamepad = Some(id); 367 event_loop_proxy 368 .send_event(OwnedEventExtension::GamepadEvent { 369 gil_event: GilEvent { id, event, time }, 370 }) 371 .ok(); 372 } 373 374 // // You can also use cached gamepad state 375 // if let Some(gamepad) = active_gamepad.map(|id| gilrs.gamepad(id)) { 376 // if gamepad.is_pressed(Button::South) { 377 // println!("Button South is pressed (XBox - A, PS - X)"); 378 // } 379 // } 380 381 std::thread::sleep(std::time::Duration::from_millis(50)); 382 } 383 }); 384 } 385