// SPDX-License-Identifier: AGPL-3.0-only //! Main entry point for the Project Catalyst client. //! //! This crate handles window creation, input processing, and drives the //! renderer to display the game world. mod meshing; use anyhow::{Context, Result}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use tracing::{error, info}; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::window::{Window, WindowId}; #[derive(Default)] struct App { renderer: Option, window: Option, } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let attributes = Window::default_attributes().with_title("Project Catalyst"); let window = match event_loop.create_window(attributes) { Ok(w) => w, Err(e) => { error!("Failed to create window: {e}"); event_loop.exit(); return; } }; let display_handle = match event_loop.display_handle() { Ok(h) => h.as_raw(), Err(e) => { error!("Failed to get display handle: {e}"); event_loop.exit(); return; } }; let window_handle = match window.window_handle() { Ok(h) => h.as_raw(), Err(e) => { error!("Failed to get window handle: {e}"); event_loop.exit(); return; } }; let required_extensions = match ash_window::enumerate_required_extensions(display_handle) { Ok(exts) => exts, Err(e) => { error!("Failed to enumerate required extensions: {e}"); event_loop.exit(); return; } }; let size = window.inner_size(); let renderer = match renderer::Renderer::new( display_handle, window_handle, size.width, size.height, required_extensions, ) { Ok(r) => r, Err(e) => { error!("Failed to initialize Vulkan renderer: {e}"); event_loop.exit(); return; } }; self.window = Some(window); self.renderer = Some(renderer); #[allow(clippy::expect_used)] let config_str = std::fs::read_to_string("assets/data/worldgen/default.json") .expect("Failed to read worldgen config"); #[allow(clippy::expect_used)] let worldgen_config: shared::generator::WorldGenConfig = serde_json::from_str(&config_str).expect("Failed to parse worldgen config"); let seed = 4_813_530; let generator = shared::generator::VoxelGenerator::new(worldgen_config, seed); let chunk = generator.generate_chunk(shared::world::ChunkPos::new(0, 0, 0)); let (vertices, indices) = meshing::generate_mesh(&chunk); tracing::info!( "Generated Mesh with {} vertices and {} indices!", vertices.len(), indices.len() ); #[allow(clippy::expect_used)] self.renderer .as_mut() .expect("Renderer initialized") .update_mesh(&vertices, &indices) .expect("Failed to upload terrain to GPU"); } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { match event { WindowEvent::CloseRequested => { event_loop.exit(); } WindowEvent::RedrawRequested => { if let Some(Err(e)) = self.renderer.as_mut().map(renderer::Renderer::draw_frame) { error!("Failed to draw frame: {e}"); event_loop.exit(); } if let Some(window) = self.window.as_ref() { window.request_redraw(); } } _ => (), } } } fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init(); info!("Starting Project Catalyst client"); let event_loop = EventLoop::new().context("Failed to create event loop")?; event_loop.set_control_flow(ControlFlow::Poll); let mut app = App::default(); event_loop .run_app(&mut app) .context("Failed to run event loop")?; Ok(()) }