zenyx-engine/engine/src/core/render/mod.rs
2025-04-19 21:04:46 +02:00

355 lines
12 KiB
Rust

use std::ops::Deref;
use std::sync::Arc;
use ctx::{Renderer, Vertex};
use std::env;
use std::fs;
use std::path::PathBuf;
use tobj::{LoadOptions, Model};
use tracing::{debug, error, info, trace, warn};
use wgpu::rwh::HasWindowHandle;
use winit::application::ApplicationHandler;
use winit::event::{KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::Window;
use winit::window::WindowId;
pub mod ctx;
struct WindowContext<'window> {
window: Arc<Window>,
ctx: Renderer<'window>,
main_window: bool,
}
impl Deref for WindowContext<'_> {
type Target = winit::window::Window;
fn deref(&self) -> &Self::Target {
self.window.as_ref()
}
}
impl WindowContext<'_> {
pub fn is_main_window(&self) -> bool {
self.main_window
}
}
#[derive(Default)]
pub struct App<'window> {
windows: ahash::AHashMap<WindowId, WindowContext<'window>>,
}
static CUBE_OBJ: &str = "
# Blender 4.2.3 LTS
# www.blender.org
mtllib cube.mtl
o Cube
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000
vn -0.0000 1.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vt 0.625000 0.500000
vt 0.875000 0.500000
vt 0.875000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.125000 0.500000
vt 0.375000 0.500000
vt 0.125000 0.750000
s 0
usemtl Material
f 1/1/1 5/2/1 7/3/1 3/4/1
f 4/5/2 3/4/2 7/6/2 8/7/2
f 8/8/3 7/9/3 5/10/3 6/11/3
f 6/12/4 2/13/4 4/5/4 8/14/4
f 2/13/5 1/1/5 3/4/5 4/5/5
f 6/11/6 5/10/6 1/1/6 2/13/6
";
impl App<'_> {
fn create_main_window(&mut self, event_loop: &ActiveEventLoop) {
let win_attr = Window::default_attributes().with_title("Zenyx");
match event_loop.create_window(win_attr) {
Ok(window) => {
let window = Arc::new(window);
let window_id = window.id();
match Renderer::new_blocking(window.clone()) {
Ok(mut wgpu_ctx) => {
let obj = match tobj::load_obj(
"Pumpkin.obj",
&LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
},
) {
Ok(obj) => obj,
Err(e) => {
error!("{e}");
panic!()
}
};
let (combined_vertices, combined_indices) = parse_obj(&obj.0);
wgpu_ctx.add_model(&combined_vertices, &combined_indices);
self.windows.insert(
window_id,
WindowContext {
window,
ctx: wgpu_ctx,
main_window: true,
},
);
info!("Main window created: {:?}", window_id);
}
Err(e) => error!("Failed to create WGPU context: {:?}", e),
}
}
Err(e) => error!("Failed to create main window: {:?}", e),
}
}
fn handle_close_requested(&mut self, window_id: WindowId) {
if self.windows.remove(&window_id).is_some() {
debug!("Window {:?} closed", window_id);
} else {
warn!("Tried to close non-existent window {:?}", window_id);
}
}
fn handle_keyboard_input(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
key_event: KeyEvent,
) {
if !key_event.state.is_pressed() {
return;
}
match key_event.physical_key {
winit::keyboard::PhysicalKey::Code(code) => match code {
winit::keyboard::KeyCode::Space => {
self.toggle_background(window_id);
}
winit::keyboard::KeyCode::Escape => {
self.spawn_child_window(event_loop);
}
other => error!("Unimplemented keycode: {:?}", other),
},
_ => debug!("Received a keyboard event with no physical key"),
}
}
fn toggle_background(&mut self, window_id: WindowId) {
if let Some(window_context) = self.windows.get_mut(&window_id) {
let current_color = window_context.ctx.bg_color();
let new_color = match current_color {
&wgpu::Color::WHITE => wgpu::Color::BLACK,
&wgpu::Color::BLACK => wgpu::Color::WHITE,
_ => wgpu::Color::WHITE,
};
let new_text_color = match window_context.ctx.text_color() {
&wgpu::Color::WHITE => wgpu::Color::BLACK,
&wgpu::Color::BLACK => wgpu::Color::WHITE,
_ => wgpu::Color::WHITE,
};
println!("new text color {new_text_color:#?}");
window_context.ctx.set_bg_color(new_color);
window_context.ctx.set_text_color(new_text_color);
debug!("Toggled background color for window {:?}", window_id);
} else {
warn!("No window context for toggling background: {:?}", window_id);
}
}
fn spawn_child_window(&mut self, event_loop: &ActiveEventLoop) {
if let Some(main_ctx) = self.windows.values().find(|ctx| ctx.is_main_window()) {
let title = format!("Zenyx - New Window {}", self.windows.len());
// TODO: Verify that this is safe instead of matching on it
let win_attr = unsafe {
let base = Window::default_attributes().with_title(title);
match main_ctx.window_handle() {
Ok(handle) => base.with_parent_window(Some(handle.as_raw())),
Err(_) => base,
}
};
match event_loop.create_window(win_attr) {
Ok(window) => {
let window = Arc::new(window);
let window_id = window.id();
match Renderer::new_blocking(window.clone()) {
Ok(mut wgpu_ctx) => {
{
let mut tmp_path: PathBuf = env::temp_dir();
tmp_path.push("cube.obj");
if let Err(e) = fs::write(&tmp_path, CUBE_OBJ) {
error!("Failed to write cube OBJ to temp: {:?}", e);
}
let load_options = tobj::LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
};
match tobj::load_obj(tmp_path.to_str().unwrap(), &load_options) {
Ok(cube_model) => {
let (cube_vertices, cube_indices) =
parse_obj(&cube_model.0);
wgpu_ctx.add_model(&cube_vertices, &cube_indices);
}
Err(e) => {
error!("Failed to load cube OBJ from temp file: {:?}", e)
}
}
}
self.windows.insert(
window_id,
WindowContext {
window,
ctx: wgpu_ctx,
main_window: false,
},
);
debug!("Spawned new child window: {:?}", window_id);
}
Err(e) => error!("Failed to create WGPU context for child window: {:?}", e),
}
}
Err(e) => error!("Failed to create child window: {:?}", e),
}
} else {
error!("No main window found. Cannot spawn a child window.");
}
}
fn handle_redraw_requested(&mut self, window_id: WindowId) {
if let Some(window_context) = self.windows.get_mut(&window_id) {
window_context.ctx.draw();
window_context.request_redraw();
trace!(
"Redrew window {:?} with title: {}",
window_id,
window_context.window.title()
);
} else {
warn!("Received redraw for unknown window {:?}", window_id);
}
}
fn handle_resize(&mut self, window_id: WindowId, new_size: winit::dpi::PhysicalSize<u32>) {
if let Some(window_context) = self.windows.get_mut(&window_id) {
window_context.ctx.resize(new_size.into());
window_context.window.request_redraw();
debug!(
"Resized window {:?} to {}x{}",
window_id, new_size.width, new_size.height
);
} else {
warn!("Received resize for unknown window {:?}", window_id);
}
}
fn handle_destroyed(&mut self, event_loop: &ActiveEventLoop) {
if self.windows.is_empty() || !self.windows.iter().any(|(_, ctx)| ctx.is_main_window()) {
self.windows.clear();
debug!("All main windows are closed. Exiting event loop.");
event_loop.exit();
}
}
}
fn parse_obj(obj: &Vec<Model>) -> (Vec<Vertex>, Vec<u32>) {
let mut combined_vertices = Vec::new();
let mut combined_indices = Vec::new();
let mut vertex_offset = 0;
for object in obj {
let mesh: &_ = &object.mesh;
let vertices: Vec<Vertex> = (0..mesh.positions.len() / 3)
.map(|i| Vertex {
position: [
mesh.positions[i * 3],
mesh.positions[i * 3 + 1],
mesh.positions[i * 3 + 2],
],
normal: if !mesh.normals.is_empty() {
[
mesh.normals[i * 3],
mesh.normals[i * 3 + 1],
mesh.normals[i * 3 + 2],
]
} else {
[0.0; 3]
},
})
.collect();
combined_vertices.extend(vertices);
combined_indices.extend(mesh.indices.iter().map(|&index| index + vertex_offset));
vertex_offset += (mesh.positions.len() as u32) / 3;
}
(combined_vertices, combined_indices)
}
impl ApplicationHandler for App<'_> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.windows.is_empty() {
self.create_main_window(event_loop);
}
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
self.handle_close_requested(window_id);
}
WindowEvent::KeyboardInput {
event: key_event, ..
} => {
self.handle_keyboard_input(event_loop, window_id, key_event);
}
WindowEvent::RedrawRequested => {
self.handle_redraw_requested(window_id);
}
WindowEvent::Resized(new_size) => {
self.handle_resize(window_id, new_size);
}
WindowEvent::Destroyed => {
self.handle_destroyed(event_loop);
}
_ => trace!("Unhandled window event for window {:?}", window_id),
}
}
}
pub fn init_renderer(event_loop: EventLoop<()>) {
event_loop.set_control_flow(ControlFlow::Wait);
let mut app = App::default();
if let Err(e) = event_loop.run_app(&mut app) {
error!("Failed to run application: {:?}", e);
}
}