Something went wrong. Try again.
Homebrew 4D Game Engine In Rust, inspired by Engine4D
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495//! Rust4D - 4D Rendering Engine//!//! A 4D rendering engine that displays 3D cross-sections of 4D geometry.
use winit::{ application::ApplicationHandler, event::{DeviceEvent, DeviceId, WindowEvent}, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, keyboard::PhysicalKey, window::WindowId,};
use rust4d::input::{ camera_action_name, keycode_to_name, mouse_button_to_name, InputAction, InputMapper,};use rust4d::systems::{ build_geometry, RenderError, RenderSystem, ScriptSystem, ScriptUpdateResult, SimulationSystem, WindowSystem,};
use rust4d_core::SceneManager;use rust4d_game::{scene_helpers, CharacterConfig, CharacterController4D};use rust4d_input::CameraController;use rust4d_math::Vec4;use rust4d_render::{camera4d::Camera4D, RenderableGeometry};use rust4d_scripting::InputSnapshot;
use rust4d::config::AppConfig;
use std::collections::HashSet;use winit::keyboard::KeyCode;
/// Main application statestruct App { /// Application configuration config: AppConfig, /// Window system (created on resume) window_system: Option<WindowSystem>, /// Render system (created on resume) render_system: Option<RenderSystem>, /// Scene manager handling scene stack and physics scene_manager: SceneManager, /// Cached GPU geometry (rebuilt when world changes) geometry: RenderableGeometry, camera: Camera4D, controller: CameraController, /// Character controller for player movement (None if no player body) character: Option<CharacterController4D>, /// Simulation system for game loop simulation: SimulationSystem, /// Scripting + audio system (Lua lifecycle + optional audio engine) script_system: ScriptSystem, /// Per-frame input snapshot fed to Lua `input` bindings input_snapshot: InputSnapshot, /// Pressed key codes (for action-map derivation in the input snapshot) pressed_keycodes: HashSet<KeyCode>, /// Whether on_init has fired for the script system scripts_inited: bool,}
impl App { fn new(game_override: Option<String>) -> Self { // Load configuration let mut config = AppConfig::load().unwrap_or_else(|e| { log::warn!("Failed to load config: {}. Using defaults.", e); AppConfig::default() });
// `--game <dir>` CLI override takes precedence over config if let Some(dir) = game_override { log::info!("--game override: '{}'", dir); config.game.game_dir = dir; }
// Create scene manager and load scene from file // Pass physics config from TOML to the physics engine let mut scene_manager = SceneManager::new().with_physics(config.physics.to_physics_config());
// Load scene from configured path let scene_name = scene_manager .load_scene(&config.scene.path) .unwrap_or_else(|e| { panic!("Failed to load scene '{}': {}", config.scene.path, e); });
// Instantiate and activate the scene scene_manager .instantiate(&scene_name) .unwrap_or_else(|e| panic!("Failed to instantiate scene: {}", e)); scene_manager .push_scene(&scene_name) .unwrap_or_else(|e| panic!("Failed to push scene: {}", e));
// Create player body from scene spawn point using scene_helpers // (single source of truth for player body setup) if let Some(scene) = scene_manager.active_scene_mut() { if let Some(spawn) = scene.player_spawn { let spawn_pos = Vec4::new(spawn[0], spawn[1], spawn[2], spawn[3]); if let Some(physics) = scene.world.physics_mut() { let key = scene_helpers::create_player_body( physics, spawn_pos, config.scene.player_radius, ); scene.player_body_key = Some(key); } } }
// Get player start from scene's player_spawn let player_start = scene_manager .active_scene() .and_then(|s| s.player_spawn) .map(|spawn| Vec4::new(spawn[0], spawn[1], spawn[2], spawn[3])) .unwrap_or_else(|| { Vec4::new( config.camera.start_position[0], config.camera.start_position[1], config.camera.start_position[2], config.camera.start_position[3], ) });
// Build GPU geometry from the world let geometry = build_geometry(scene_manager.active_world().unwrap());
log::info!( "Loaded scene '{}' with {} entities", scene_name, scene_manager .active_world() .map(|w| w.entity_count()) .unwrap_or(0) ); log::info!( "Total geometry: {} vertices, {} tetrahedra", geometry.vertex_count(), geometry.tetrahedron_count() );
// Set camera with configured pitch limit and player start position let mut camera = Camera4D::with_pitch_limit(config.camera.pitch_limit.to_radians()); camera.position = player_start;
// Configure controller from config let controller = CameraController::new() .with_move_speed(config.input.move_speed) .with_w_move_speed(config.input.w_move_speed) .with_mouse_sensitivity(config.input.mouse_sensitivity) .with_w_rotation_sensitivity(config.input.w_rotation_sensitivity) .with_smoothing_half_life(config.input.smoothing_half_life) .with_smoothing(config.input.smoothing_enabled);
// Create character controller from the player body key (if the scene has a player) let character = scene_manager .active_scene() .and_then(|s| s.player_body_key) .map(|key| { CharacterController4D::new( key, CharacterConfig { move_speed: config.input.move_speed, w_move_speed: config.input.w_move_speed, jump_velocity: config.physics.jump_velocity, }, ) });
// Scripting + audio system. Constructed from game/audio/physics config. // Scripting degrades to disabled when game_dir is empty; audio degrades // to silent when no device is available. let script_system = ScriptSystem::new( &config.game, &config.audio, &config.physics.to_physics_config(), );
Self { config, window_system: None, render_system: None, scene_manager, geometry, camera, controller, character, simulation: SimulationSystem::new(), script_system, input_snapshot: InputSnapshot::new(), pressed_keycodes: HashSet::new(), scripts_inited: false, } }
/// Recompute the `actions` map in the input snapshot from the held key /// codes and the camera controller's action map. fn recompute_input_actions(&mut self) { let bindings = self.controller.action_map().bindings(); for &(key, action) in bindings { let value = if self.pressed_keycodes.contains(&key) { 1.0 } else { 0.0 }; self.input_snapshot .set_action(camera_action_name(action), value); } }
/// Mark actions bound to `key` as just-pressed this frame (rising edge). fn mark_actions_just_pressed(&mut self, key: KeyCode) { let bindings = self.controller.action_map().bindings(); for &(bound_key, action) in bindings { if bound_key == key { self.input_snapshot .mark_action_just_pressed(camera_action_name(action)); } } }}
impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.window_system.is_none() { // Create window system let window_system = WindowSystem::create(event_loop, &self.config.window) .expect("Failed to create window");
// Create render system let mut render_system = RenderSystem::new( window_system.window().clone(), self.config.rendering.clone(), self.config.camera.clone(), self.config.window.vsync, );
// Upload initial geometry render_system.upload_geometry(&self.geometry);
self.window_system = Some(window_system); self.render_system = Some(render_system);
// Fire on_init once the window/render surface is ready. if !self.scripts_inited { self.scripts_inited = true; let player = self.camera.position; if let Some(world) = self.scene_manager.active_world_mut() { if let Err(e) = self.script_system.init(world, player) { log::error!("[script] on_init error: {}", e); } } } } }
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { match event { WindowEvent::CloseRequested => { if let Err(e) = self.script_system.shutdown() { log::error!("[script] on_shutdown error: {}", e); } event_loop.exit(); }
WindowEvent::Resized(physical_size) => { if let Some(rs) = &mut self.render_system { rs.resize(physical_size.width, physical_size.height); } }
WindowEvent::KeyboardInput { event, .. } => { if let PhysicalKey::Code(key) = event.physical_key { // Track key state for the Lua input snapshot. let pressed = event.state == winit::event::ElementState::Pressed; if let Some(name) = keycode_to_name(key) { if pressed { self.pressed_keycodes.insert(key); self.input_snapshot.press_key(name); self.mark_actions_just_pressed(key); } else { self.pressed_keycodes.remove(&key); self.input_snapshot.release_key(name); } self.recompute_input_actions(); }
// Map to action via InputMapper let cursor_captured = self .window_system .as_ref() .map(|ws| ws.is_cursor_captured()) .unwrap_or(false);
if let Some(action) = InputMapper::map_keyboard(key, event.state, cursor_captured) { match action { InputAction::ToggleCursor => { if let Some(ws) = &mut self.window_system { ws.release_cursor(); } } InputAction::Exit => { event_loop.exit(); } InputAction::ResetCamera => { self.camera.reset(); log::info!("Camera reset to starting position"); } InputAction::ToggleFullscreen => { if let Some(ws) = &self.window_system { ws.toggle_fullscreen(); } } InputAction::ToggleSmoothing => { let enabled = self.controller.toggle_smoothing(); log::info!( "Input smoothing: {}", if enabled { "ON" } else { "OFF" } ); } } return; }
// Pass to controller for movement keys self.controller.process_keyboard(key, event.state); } }
WindowEvent::MouseInput { state, button, .. } => { // Track mouse button state for the Lua input snapshot. if let Some(name) = mouse_button_to_name(button) { if state == winit::event::ElementState::Pressed { self.input_snapshot.press_mouse(name); } else { self.input_snapshot.release_mouse(name); } }
// Map to action via InputMapper let cursor_captured = self .window_system .as_ref() .map(|ws| ws.is_cursor_captured()) .unwrap_or(false);
if let Some(action) = InputMapper::map_mouse_button(button, state, cursor_captured) { if action == InputAction::ToggleCursor { if let Some(ws) = &mut self.window_system { ws.capture_cursor(); } } } self.controller.process_mouse_button(button, state); }
WindowEvent::MouseWheel { delta, .. } => { // Scroll wheel adjusts slice offset let scroll = match delta { winit::event::MouseScrollDelta::LineDelta(_, y) => y, winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y as f32 / 100.0, }; self.camera.adjust_slice_offset(scroll * 0.1); }
WindowEvent::RedrawRequested => { // Run scripting BEFORE simulation so scripts can set // velocities/transforms before physics steps. let listener_pos = self.camera.position; let script_result = { let world = self.scene_manager.active_world_mut(); if let Some(world) = world { match self .script_system .update(world, &self.input_snapshot, listener_pos) { Ok(r) => r, Err(e) => { log::error!("[script] update error: {}", e); ScriptUpdateResult::default() } } } else { ScriptUpdateResult::default() } };
// Run simulation let cursor_captured = self .window_system .as_ref() .map(|ws| ws.is_cursor_captured()) .unwrap_or(false);
let result = self.simulation.update( &mut self.scene_manager, &mut self.camera, &mut self.controller, self.character.as_ref(), cursor_captured, );
// Rebuild geometry if entities changed (scripts or physics) let geometry_dirty = script_result.geometry_dirty || result.geometry_dirty; if geometry_dirty { self.geometry = build_geometry(self.scene_manager.active_world().unwrap()); if let Some(rs) = &mut self.render_system { rs.upload_geometry(&self.geometry); } if let Some(w) = self.scene_manager.active_world_mut() { w.clear_all_dirty(); } }
// Clear per-frame input edges now that scripts have consumed them. self.input_snapshot.end_frame();
// Update window title with debug info if let Some(ws) = &self.window_system { let pos = self.camera.position; ws.update_title([pos.x, pos.y, pos.z, pos.w], self.camera.get_slice_w()); }
// Render frame if let Some(rs) = &mut self.render_system { match rs.render_frame(&self.camera, &self.geometry) { Ok(()) => {} Err(RenderError::SurfaceLost) => { let (w, h) = rs.size(); rs.resize(w, h); } Err(RenderError::OutOfMemory) => { event_loop.exit(); return; } Err(e) => { log::warn!("Render error: {}", e); } } }
// Request next frame if let Some(ws) = &self.window_system { ws.request_redraw(); } }
_ => {} } }
fn device_event( &mut self, _event_loop: &ActiveEventLoop, _device_id: DeviceId, event: DeviceEvent, ) { if let DeviceEvent::MouseMotion { delta } = event { self.controller.process_mouse_motion(delta.0, delta.1); self.input_snapshot .add_mouse_motion(delta.0 as f32, delta.1 as f32); } }}
/// Parse `--game <dir>` from CLI args. Returns the directory if present.fn parse_game_arg() -> Option<String> { let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { if arg == "--game" { return args.next(); } else if let Some(rest) = arg.strip_prefix("--game=") { return Some(rest.to_string()); } } None}
fn main() { // Initialize logging env_logger::init(); log::info!("Starting Rust4D");
// Create event loop let event_loop = EventLoop::new().expect("Failed to create event loop"); event_loop.set_control_flow(ControlFlow::Poll);
// Create and run application let mut app = App::new(parse_game_arg()); event_loop.run_app(&mut app).expect("Event loop error");}