diff --git a/Cargo.lock b/Cargo.lock index ce454a9..84ec05e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,6 +181,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bit-set" version = "0.9.1" @@ -258,6 +264,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -626,7 +638,8 @@ dependencies = [ "console_log", "env_logger", "glam", - "image", + "gltf", + "image 0.24.9", "log", "mars", "pollster", @@ -691,6 +704,45 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gltf" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" +dependencies = [ + "base64", + "byteorder", + "gltf-json", + "image 0.25.10", + "lazy_static", + "serde_json", + "urlencoding", +] + +[[package]] +name = "gltf-derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" +dependencies = [ + "inflections", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gltf-json" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "glutin_wgl_sys" version = "0.6.1" @@ -795,7 +847,22 @@ dependencies = [ "color_quant", "jpeg-decoder", "num-traits", - "png", + "png 0.17.16", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.1", + "zune-core", + "zune-jpeg", ] [[package]] @@ -808,12 +875,24 @@ dependencies = [ "hashbrown 0.17.1", ] +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "jiff" version = "0.2.24" @@ -941,6 +1020,12 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.186" @@ -1037,6 +1122,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "naga" version = "29.0.3" @@ -1504,6 +1599,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.11.0" @@ -1569,6 +1677,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + [[package]] name = "quick-xml" version = "0.39.4" @@ -1796,6 +1910,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2055,6 +2182,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2807,3 +2940,24 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index b6eda9b..0073a97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ name = "game" path = "src/main.rs" [dependencies] +gltf = "1.4.1" anyhow = "1.0" winit = { version = "0.30", features = ["android-native-activity"] } env_logger = "0.11.10" diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..37f3ede --- /dev/null +++ b/src/app.rs @@ -0,0 +1,312 @@ +use std::collections::HashMap; +// Credit of most code to https://sotrh.github.io/learn-wgpu/ since I'm not familiar with wgpu +use std::sync::Arc; +use glam::{Vec2, Affine3A, Vec3, Vec4, Mat4, EulerRot, Quat}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; +use winit::dpi::PhysicalPosition; +#[cfg(target_arch = "wasm32")] +use winit::platform::web::EventLoopExtWebSys; +use winit::{ + application::ApplicationHandler, + event::*, + event_loop::{ActiveEventLoop, EventLoop}, + keyboard::{KeyCode, PhysicalKey}, + window::Window, +}; +use crate::render; +use crate::render::{Renderer, SimpleTexture}; +use crate::world::{SimpleObject, World}; + +struct Controller { + buttons: HashMap, + keys: HashMap, + mouse: Vec2, +} + +impl Controller { + fn new() -> Controller { + Controller { + buttons: Default::default(), + keys: HashMap::new(), + mouse: Vec2::new(0.0,0.0), + } + } +} + +pub struct AppState { + world: World, + controller: Controller, + window: Arc, + clients: Vec, +} + +const BLOCKS: i32 = 50; + +impl AppState { + // We don't need this to be async right now, + // but we will in the next tutorial + pub async fn new(window: Arc) -> Result> { + let size = window.inner_size(); + let mut world = World::new(); + world.add_renderer(Renderer::new(&window).await?); + let mut clients = Vec::new(); + { + let file = world.renderer.as_mut().unwrap().load_from_gltf( include_bytes!("assets/cube.glb")); + let block = file.first_object().unwrap(); + //let color = &renderer.load_texture_from_bytes(include_bytes!("assets/plank/color.png")); + //let normal = &renderer.load_texture_from_bytes(include_bytes!("assets/plank/normal.png")); + //let roughness = &renderer.load_texture_from_bytes(include_bytes!("assets/plank/roughness.png")); + //let mat = renderer.new_material(color,normal,roughness); + for x in -BLOCKS..BLOCKS { + for y in -BLOCKS..BLOCKS { + let block = block.hard_clone(); + world.add_object(block.clone()); + { + let mut model = block.0.borrow_mut(); + model.model.as_mut().unwrap().instance.transform = Mat4::from_translation(Vec3::new(-x as f32 * 3.0,0.0,-y as f32 * 3.0)) + * Mat4::from_rotation_z((x * y) as f32 / 1.23); + model.model.as_mut().unwrap().instance.color = Vec4::new(0.3,(x + BLOCKS) as f32 / BLOCKS as f32,(y + BLOCKS) as f32 / BLOCKS as f32,1.0); + } + clients.push(block) + } + } + } + Ok(Self { + world, + clients, + controller: Controller::new(), + window, + }) + } + + pub fn bounds(&self) -> (u32,u32) { + let size = self.window.inner_size(); + (size.width,size.height) + } + + pub fn resize(&mut self, width: u32, height: u32) { + if width > 0 && height > 0 { + let max = 2048; + self.world.renderer.as_mut().unwrap().resize(width,height); + } + } + + fn update(&mut self) { + // ... + } + + fn handle_key(&mut self, event_loop: &ActiveEventLoop, code: KeyCode, is_pressed: bool) { + match (code, is_pressed) { + (KeyCode::Escape, true) => event_loop.exit(), + (KeyCode::Space, true) => {}, + (KeyCode::KeyR, true) => { + self.world.renderer.as_mut().unwrap().eye.frame = Affine3A::IDENTITY + } + _ => {} + } + self.controller.keys.insert(code, is_pressed); + } + + fn handle_mouse_moved(&mut self, position: PhysicalPosition) { + if let Some(true) = self.controller.buttons.get(&MouseButton::Right) { + self.world.renderer.as_mut().unwrap().eye.rotate(position.x as f32 - self.controller.mouse.x, position.y as f32 - self.controller.mouse.y); + } + self.controller.mouse = Vec2::new(position.x as f32, position.y as f32); + } + + fn handle_mouse_button(&mut self, button: MouseButton, state: ElementState ) { + self.controller.buttons.insert(button,state.is_pressed()); + } +} + +pub struct App { + #[cfg(target_arch = "wasm32")] + proxy: Option>, + state: Option, +} + +impl App { + pub fn new(#[cfg(target_arch = "wasm32")] event_loop: &EventLoop) -> Self { + #[cfg(target_arch = "wasm32")] + let proxy = Some(event_loop.create_proxy()); + Self { + state: None, + #[cfg(target_arch = "wasm32")] + proxy, + } + } +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + #[allow(unused_mut)] + let mut window_attributes = Window::default_attributes(); + + #[cfg(target_arch = "wasm32")] + { + use wasm_bindgen::JsCast; + use winit::platform::web::WindowAttributesExtWebSys; + + const CANVAS_ID: &str = "canvas"; + + let window = wgpu::web_sys::window().unwrap_throw(); + let document = window.document().unwrap_throw(); + let canvas = document.get_element_by_id(CANVAS_ID).unwrap_throw(); + let html_canvas_element = canvas.unchecked_into(); + window_attributes = window_attributes.with_canvas(Some(html_canvas_element)); + } + + let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); + + #[cfg(not(target_arch = "wasm32"))] + { + // If we are not on web we can use pollster to + // await the window creation + self.state = Some(pollster::block_on(AppState::new(window)).unwrap()); + } + + #[cfg(target_arch = "wasm32")] + { + // Run the future asynchronously and use the + // proxy to send the results to the event loop + if let Some(proxy) = self.proxy.take() { + wasm_bindgen_futures::spawn_local(async move { + assert!( + proxy + .send_event( + AppState::new(window) + .await + .expect("Unable to create canvas!!!") + ) + .is_ok() + ) + }); + } + } + } + + #[allow(unused_mut)] + fn user_event(&mut self, _event_loop: &ActiveEventLoop, mut event: AppState) { + // This is where proxy.send_event() ends up + #[cfg(target_arch = "wasm32")] + { + event.window.request_redraw(); + event.resize( + event.window.inner_size().width, + event.window.inner_size().height, + ); + } + self.state = Some(event); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: winit::window::WindowId, + event: WindowEvent, + ) { + let state = match &mut self.state { + Some(canvas) => canvas, + None => return, + }; + + match event { + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::Resized(size) => state.resize(size.width, size.height), + WindowEvent::RedrawRequested => { + state.update(); + let mut movement = Vec3::new(0.0,0.0,0.0); + + let pressed = |keycode: KeyCode| { + if let Some(true) = state.controller.keys.get(&keycode) { + true + } else { + false + } + }; + + if pressed(KeyCode::KeyA) { + movement.x -= 1.0; + } + if pressed(KeyCode::KeyD) { + movement.x += 1.0; + } + if pressed(KeyCode::KeyW) { + movement.z += 1.0; + } + if pressed(KeyCode::KeyS) { + movement.z -= 1.0; + } + if pressed(KeyCode::KeyE) { + movement.y += 1.0; + } + if pressed(KeyCode::KeyQ) { + movement.y -= 1.0; + } + + state.world.renderer.as_mut().unwrap().eye.control(movement * 0.1); + match state.world.renderer.as_mut().unwrap().render(&state.window) { + Ok(_) => {} + Err(e) => { + // Log the error and exit gracefully + log::error!("{e}"); + event_loop.exit(); + } + } + for object in state.clients.iter() { + object.0.borrow_mut().model.as_mut().unwrap().instance.transform *= Mat4::from_rotation_translation( + Quat::from_euler(EulerRot::XYZ,0.001,-0.001,0.001), + Vec3::new(0.0,0.0,0.0) + ); + } + } + WindowEvent::MouseInput { button, state: element, .. } => state.handle_mouse_button(button,element), + WindowEvent::CursorMoved { position: pos, .. } => state.handle_mouse_moved(pos), + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: PhysicalKey::Code(code), + state: key_state, + .. + }, + .. + } => state.handle_key(event_loop, code, key_state.is_pressed()), + _ => {} + } + } +} + +pub fn run() -> anyhow::Result<()> { + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::init(); + } + #[cfg(target_arch = "wasm32")] + { + console_log::init_with_level(log::Level::Info).unwrap_throw(); + } + + let event_loop = EventLoop::with_user_event().build()?; + #[cfg(not(target_arch = "wasm32"))] + { + let mut app = App::new(); + event_loop.run_app(&mut app)?; + } + #[cfg(target_arch = "wasm32")] + { + let app = App::new(&event_loop); + event_loop.spawn_app(app); + } + + Ok(()) +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(start)] +pub fn run_web() -> Result<(), wasm_bindgen::JsValue> { + console_error_panic_hook::set_once(); + run().unwrap_throw(); + + Ok(()) +} diff --git a/src/assets/SimpleShader.wgsl b/src/assets/SimpleShader.wgsl new file mode 100644 index 0000000..1f22a81 --- /dev/null +++ b/src/assets/SimpleShader.wgsl @@ -0,0 +1,97 @@ +struct Environment { + ambient: vec4, + light: vec4, + dir: vec4, +} + +struct View { + view: mat4x4, + frame: mat4x4, +} + +// Vertex shader +@group(0) @binding(0) +var view: View; +@group(0) @binding(1) +var environment: Environment; + +struct VertexInput { + @location(0) position: vec3, + @location(1) normal: vec3, + @location(2) tex_coords: vec2, +} + +struct InstanceInput { + @location(4) model_matrix_0: vec4, + @location(5) model_matrix_1: vec4, + @location(6) model_matrix_2: vec4, + @location(7) model_matrix_3: vec4, + @location(8) model_color: vec4, +} + + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, + @location(1) color: vec4, + @location(2) world_normal: vec3, + @location(3) world_position: vec3, +} + +@vertex +fn vs_main( + model: VertexInput, + instance: InstanceInput, +) -> VertexOutput { + var out: VertexOutput; + let model_matrix = mat4x4( + instance.model_matrix_0, + instance.model_matrix_1, + instance.model_matrix_2, + instance.model_matrix_3, + ); + let model_rot_matrix = mat3x3( + instance.model_matrix_0.xyz, + instance.model_matrix_1.xyz, + instance.model_matrix_2.xyz, + ); + out.tex_coords = model.tex_coords; + out.color = instance.model_color; + out.world_normal = model_rot_matrix * model.normal; + var world_position: vec4 = model_matrix * vec4(model.position, 1.0); + out.world_position = world_position.xyz; + out.clip_position = view.view * world_position; + return out; +} + +// Fragment shader + +@group(1) @binding(0) +var s_diffuse: sampler; +@group(1) @binding(1) +var t_diffuse: texture_2d; +@group(1) @binding(2) +var t_normal: texture_2d; +@group(1) @binding(3) +var t_rough: texture_2d; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); + let object_normal: vec4 = textureSample(t_normal, s_diffuse, in.tex_coords); + + let tangent_normal = object_normal.xyz * 2.0 - 1.0; + let light_dir = normalize(environment.dir.xyz); + let view_dir = normalize(view.frame[3].xyz - in.world_position); + let half_dir = normalize(view_dir + light_dir); + + let diffuse_strength = max(dot(tangent_normal, light_dir), 0.0); + let diffuse_color = environment.light.xyz * diffuse_strength; + + let specular_strength = pow(max(dot(tangent_normal, half_dir), 0.0), 32.0); + let specular_color = specular_strength * environment.light.xyz; + + let result = (environment.ambient.xyz + diffuse_color.xyz + specular_color.xyz) * object_color.xyz; + + return vec4(result.xyz,object_color.a); +} \ No newline at end of file diff --git a/src/assets/plank/color.png b/src/assets/plank/color.png new file mode 100644 index 0000000..f3a41b5 Binary files /dev/null and b/src/assets/plank/color.png differ diff --git a/src/assets/plank/normal.png b/src/assets/plank/normal.png new file mode 100644 index 0000000..b0e5721 Binary files /dev/null and b/src/assets/plank/normal.png differ diff --git a/src/assets/plank/roughness.png b/src/assets/plank/roughness.png new file mode 100644 index 0000000..d8175bb Binary files /dev/null and b/src/assets/plank/roughness.png differ diff --git a/src/assets/shader.wgsl b/src/assets/shader.wgsl deleted file mode 100644 index 0aa3136..0000000 --- a/src/assets/shader.wgsl +++ /dev/null @@ -1,35 +0,0 @@ -// Vertex shader -@group(1) @binding(0) -var view: mat4x4; - -struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.clip_position = view * vec4(model.position, 1.0); - return out; -} - -// Fragment shader - -@group(0) @binding(0) -var t_diffuse: texture_2d; -@group(0) @binding(1) -var s_diffuse: sampler; - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return textureSample(t_diffuse, s_diffuse, in.tex_coords); -} \ No newline at end of file diff --git a/src/assets/shader2.wgsl b/src/assets/shader2.wgsl deleted file mode 100644 index bd92243..0000000 --- a/src/assets/shader2.wgsl +++ /dev/null @@ -1,21 +0,0 @@ -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) color: vec4 -}; - -@vertex -fn vs_main( - @builtin(vertex_index) in_vertex_index: u32, -) -> VertexOutput { - var out: VertexOutput; - let x = f32(1 - i32(in_vertex_index)) * 0.5; - let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; - out.clip_position = vec4(x, y, 0.0, 1.0); - out.color = vec4(0.3,x,y,1.0); - return out; -} - -@fragment -fn fs_main(@location(0) col: vec4) -> @location(0) vec4 { - return col; -} \ No newline at end of file diff --git a/src/engine.rs b/src/engine.rs deleted file mode 100644 index a4a625a..0000000 --- a/src/engine.rs +++ /dev/null @@ -1,807 +0,0 @@ -use std::collections::HashMap; -// Credit of most code to https://sotrh.github.io/learn-wgpu/ since I'm not familiar with wgpu -use std::sync::Arc; -use glam::camera::lh::proj::directx::perspective; -use wgpu::util::DeviceExt; -use glam::prelude::*; - -struct Controller { - buttons: HashMap, - keys: HashMap, - mouse: Vec2, -} - -impl Controller { - fn new() -> Controller { - Controller { - buttons: Default::default(), - keys: HashMap::new(), - mouse: Vec2::new(0.0,0.0), - } - } -} - -struct Camera { - frame: Affine3A, - aspect_ratio: f32, - z_near: f32, - z_far: f32, - fov_y: f32, -} - -impl Camera { - fn view(&self) -> Mat4 { - let projection = perspective(self.fov_y, self.aspect_ratio, self.z_near, self.z_far); - projection * self.frame.inverse() - } - fn new(window: &Arc) -> Camera { - Camera { - aspect_ratio: window.inner_size().width as f32 / window.inner_size().height as f32, - frame: Affine3A::IDENTITY, - fov_y: 90.0, - z_near: 0.1, - z_far: 1000.0 - } - } - fn resize(&mut self, window: &Arc) { - self.aspect_ratio = window.inner_size().width as f32 / window.inner_size().height as f32 - } - fn control(&mut self, delta: Vec3) { - self.frame = self.frame * Affine3A::from_translation(delta); - } - fn rotate(&mut self, yaw: f32, pitch: f32) { - let (mut y,mut x,z) = self.frame.matrix3.to_euler(EulerRot::YXZ); - x = (x + pitch * 0.005).clamp(-1.4,1.4); - y = y + yaw * 0.005; - self.frame.matrix3 = Mat3A::from_euler(EulerRot::YXZ,y,x,z); - } -} - - -const VERTICES: &[Vertex] = &[ - // Changed - Vertex { position: [-0.5,-0.5,-0.5], tex_coords: [0,0]} -]; - -const INDICES: &[u16] = &[ - 0, 1, 4, - 1, 2, 4, - 2, 3, 4, -]; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; -use winit::dpi::PhysicalPosition; -#[cfg(target_arch = "wasm32")] -use winit::platform::web::EventLoopExtWebSys; -use winit::{ - application::ApplicationHandler, - event::*, - event_loop::{ActiveEventLoop, EventLoop}, - keyboard::{KeyCode, PhysicalKey}, - window::Window, -}; - -type Kt = HashMap>; - -struct Renderer { - surface: (wgpu::Surface<'static>,bool), - config: wgpu::SurfaceConfiguration, - device: wgpu::Device, - queue: wgpu::Queue, - camera: Camera, - clear_color: wgpu::Color, -} - -struct UniformPlan { - bind_group: wgpu::BindGroup, - buffer: wgpu::Buffer, -} - -struct TexturePlan { - bind_group: wgpu::BindGroup, - texture: wgpu::Texture, -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -struct SimpleVertex { - pos: [f32; 3], - uv: [f32; 2], - norm: [f32; 3], -} - -impl SimpleVertex { - fn desc() -> wgpu::VertexBufferLayout<'static> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - wgpu::VertexAttribute { - offset: 0, - shader_location: 0, - format: wgpu::VertexFormat::Float32x3, - }, - wgpu::VertexAttribute { - offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x2, - }, - wgpu::VertexAttribute { - offset: std::mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, - shader_location: 2, - format: wgpu::VertexFormat::Float32x3, - } - ] - } - } -} - -struct SimpleMesh { - vertex_buffer: wgpu::Buffer, - index_buffer: wgpu::Buffer, - indices_count: u32, -} - -struct SimpleMaterial { - albedo: TexturePlan, - normal: Option, - specular: Option, -} - -struct SimpleModel { - affine: Affine3A, - material: mars::gc::Gc, - -} - -struct SimpleRenderPlan { - pipeline: wgpu::RenderPipeline, - // todo: clients: Kt>> - clients: Vec -} - -struct FluidRigidBody { - -} - -pub struct State { - renderer: Renderer, - controller: Controller, - window: Arc, - diffuse_bind_group: wgpu::BindGroup, - sky: wgpu::Color, -} - -impl State { - // We don't need this to be async right now, - // but we will in the next tutorial - pub async fn new(window: Arc) -> anyhow::Result { - let size = window.inner_size(); - - // The instance is a handle to our GPU - // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - #[cfg(not(target_arch = "wasm32"))] - backends: wgpu::Backends::PRIMARY, - #[cfg(target_arch = "wasm32")] - backends: wgpu::Backends::GL, - flags: Default::default(), - memory_budget_thresholds: Default::default(), - backend_options: Default::default(), - display: None, - }); - - let surface = instance.create_surface(window.clone())?; - - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await?; - - let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - experimental_features: wgpu::ExperimentalFeatures::disabled(), - // WebGL doesn't support all of wgpu's features, so if - // we're building for the web we'll have to disable some. - required_limits: if cfg!(target_arch = "wasm32") { - wgpu::Limits::downlevel_webgl2_defaults() - } else { - wgpu::Limits::default() - }, - memory_hints: Default::default(), - trace: wgpu::Trace::Off, - }) - .await?; - - let surface_caps = surface.get_capabilities(&adapter); - // Shader code in this tutorial assumes an sRGB surface texture. Using a different - // one will result in all the colors coming out darker. If you want to support non - // sRGB surfaces, you'll need to account for that when drawing to the frame. - let surface_format = surface_caps - .formats - .iter() - .find(|f| f.is_srgb()) - .copied() - .unwrap_or(surface_caps.formats[0]); - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - - let diffuse_bytes = include_bytes!("assets/test.png"); - let diffuse_image = image::load_from_memory(diffuse_bytes)?; - let diffuse_rgba = diffuse_image.to_rgba8(); - - use image::GenericImageView; - let dimensions = diffuse_image.dimensions(); - - println!("{:?}",dimensions); - - let texture_size = wgpu::Extent3d { - width: dimensions.0, - height: dimensions.1, - // All textures are stored as 3D, we represent our 2D texture - // by setting depth to 1. - depth_or_array_layers: 1, - }; - let diffuse_texture = device.create_texture( - &wgpu::TextureDescriptor { - size: texture_size, - mip_level_count: 1, // We'll talk about this a little later - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - // Most images are stored using sRGB, so we need to reflect that here. - format: wgpu::TextureFormat::Rgba8UnormSrgb, - // TEXTURE_BINDING tells wgpu that we want to use this texture in shaders - // COPY_DST means that we want to copy data to this texture - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: Some("diffuse_texture"), - // This is the same as with the SurfaceConfig. It - // specifies what texture formats can be used to - // create TextureViews for this texture. The base - // texture format (Rgba8UnormSrgb in this case) is - // always supported. Note that using a different - // texture format is not supported on the WebGL2 - // backend. - view_formats: &[], - } - ); - - queue.write_texture( - // Tells wgpu where to copy the pixel data - wgpu::TexelCopyTextureInfo { - texture: &diffuse_texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - // The actual pixel data - &diffuse_rgba, - // The layout of the texture - wgpu::TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(4 * dimensions.0), - rows_per_image: Some(dimensions.1), - }, - texture_size, - ); - - let diffuse_texture_view = diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default()); - let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::Repeat, - address_mode_v: wgpu::AddressMode::Repeat, - address_mode_w: wgpu::AddressMode::Repeat, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::MipmapFilterMode::Nearest, - ..Default::default() - }); - - let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - // This should match the filterable field of the - // corresponding Texture entry above. - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); - - let diffuse_bind_group = device.create_bind_group( - &wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_sampler), - } - ], - label: Some("diffuse_bind_group"), - } - ); - - let camera = Camera::new(&window); - - let camera_buffer = device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Camera Buffer"), - contents: bytemuck::cast_slice(&[camera.view()]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - } - ); - - let camera_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - } - ], - label: Some("camera_bind_group_layout"), - }); - - let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &camera_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: camera_buffer.as_entire_binding(), - } - ], - label: Some("camera_bind_group"), - }); - - let vertex_buffer = device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(VERTICES), - usage: wgpu::BufferUsages::VERTEX, - } - ); - - let index_buffer = device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(INDICES), - usage: wgpu::BufferUsages::INDEX, - } - ); - - let num_indices = INDICES.len() as u32; - - let shader1 = device.create_shader_module(wgpu::include_wgsl!("assets/shader.wgsl")); - - let render_pipeline_layout1 = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[ - Some(&texture_bind_group_layout), - Some(&camera_bind_group_layout), - ], - immediate_size: 0, - }); - - /*let water_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Water Pipeline"), - layout: Some(&water_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader1, - entry_point: Some("vs_main"), - buffers: &[ - - ], - compilation_options: wgpu::PipelineCompilationOptions::default(); - } - });*/ - - let render_pipeline1 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&render_pipeline_layout1), - vertex: wgpu::VertexState { - module: &shader1, - entry_point: Some("vs_main"), - buffers: &[ - Vertex::desc(), - ], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - // 3. - module: &shader1, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - // 4. - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, // 1. - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, // 2. - cull_mode: Some(wgpu::Face::Back), - // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE - polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL - unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION - conservative: false, - }, - depth_stencil: None, // 1. - multisample: wgpu::MultisampleState { - count: 1, // 2. - mask: !0, // 3. - alpha_to_coverage_enabled: false, // 4. - }, - multiview_mask: None, // 5. - cache: None, // 6. - }); - - Ok(Self { - surface, - camera_buffer, - camera_bind_group, - diffuse_bind_group, - index_buffer, - vertex_buffer, - controller: Controller::new(), - device, - queue, - config, - is_surface_configured: false, - window, - num_indices, - render_pipeline: render_pipeline1, - sky: wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.3, - a: 1.0, - }, - camera, - }) - } - - pub fn resize(&mut self, width: u32, height: u32) { - if width > 0 && height > 0 { - let max = 2048; - self.config.width = width.min(max); - self.config.height = height.min(max); - self.surface.configure(&self.device, &self.config); - self.camera.resize(&self.window); - self.is_surface_configured = true; - } - } - - fn update(&mut self) { - // ... - } - - fn render(&mut self) -> anyhow::Result<()> { - self.window.request_redraw(); - - // We can't render unless the surface is configured - if !self.is_surface_configured { - return Ok(()); - } - - let output = match self.surface.get_current_texture() { - wgpu::CurrentSurfaceTexture::Success(surface_texture) => surface_texture, - wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => { - self.surface.configure(&self.device, &self.config); - surface_texture - } - wgpu::CurrentSurfaceTexture::Timeout - | wgpu::CurrentSurfaceTexture::Occluded - | wgpu::CurrentSurfaceTexture::Validation => { - // Skip this frame - return Ok(()); - } - wgpu::CurrentSurfaceTexture::Outdated => { - self.surface.configure(&self.device, &self.config); - return Ok(()); - } - wgpu::CurrentSurfaceTexture::Lost => { - // You could recreate the devices and all resources - // created with it here, but we'll just bail - anyhow::bail!("Lost device"); - } - }; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - depth_slice: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(self.sky), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - multiview_mask: None, - }); - - let mut movement = Vec3::new(0.0,0.0,0.0); - - let pressed = |keycode: KeyCode| { - if let Some(true) = self.controller.keys.get(&keycode) { - true - } else { - false - } - }; - - if pressed(KeyCode::KeyA) { - movement.x -= 1.0; - } - if pressed(KeyCode::KeyD) { - movement.x += 1.0; - } - if pressed(KeyCode::KeyW) { - movement.z += 1.0; - } - if pressed(KeyCode::KeyS) { - movement.z -= 1.0; - } - if pressed(KeyCode::KeyE) { - movement.y += 1.0; - } - if pressed(KeyCode::KeyQ) { - movement.y -= 1.0; - } - - println!("{:?} {:?} {:?}",movement,self.camera.frame.translation,self.camera.frame.matrix3.to_euler(EulerRot::YXZ)); - - self.camera.control(movement * 0.1); - - self.queue.write_buffer(&self.camera_buffer,0,bytemuck::cast_slice(&[self.camera.view()])); - - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(1, &self.camera_bind_group, &[]); - render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); - render_pass.draw_indexed(0..self.num_indices, 0, 0..1); - } - - // submit will accept anything that implements IntoIter - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); - - Ok(()) - } - - fn handle_key(&mut self, event_loop: &ActiveEventLoop, code: KeyCode, is_pressed: bool) { - match (code, is_pressed) { - (KeyCode::Escape, true) => event_loop.exit(), - (KeyCode::Space, true) => {}, - (KeyCode::KeyR, true) => { - self.camera.frame = Affine3A::IDENTITY - } - _ => {} - } - self.controller.keys.insert(code, is_pressed); - } - - fn handle_mouse_moved(&mut self, position: PhysicalPosition) { - self.sky = wgpu::Color { - r: 0.3, - g: position.x / 1000.0, - b: position.y / 1000.0, - a: 1.0, - }; - if let Some(true) = self.controller.buttons.get(&MouseButton::Right) { - self.camera.rotate(position.x as f32 - self.controller.mouse.x, position.y as f32 - self.controller.mouse.y); - } - self.controller.mouse = Vec2::new(position.x as f32, position.y as f32); - } - - fn handle_mouse_button(&mut self, button: MouseButton, state: ElementState ) { - self.controller.buttons.insert(button,state.is_pressed()); - } -} - -pub struct App { - #[cfg(target_arch = "wasm32")] - proxy: Option>, - state: Option, -} - -impl App { - pub fn new(#[cfg(target_arch = "wasm32")] event_loop: &EventLoop) -> Self { - #[cfg(target_arch = "wasm32")] - let proxy = Some(event_loop.create_proxy()); - Self { - state: None, - #[cfg(target_arch = "wasm32")] - proxy, - } - } -} - -impl ApplicationHandler for App { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - #[allow(unused_mut)] - let mut window_attributes = Window::default_attributes(); - - #[cfg(target_arch = "wasm32")] - { - use wasm_bindgen::JsCast; - use winit::platform::web::WindowAttributesExtWebSys; - - const CANVAS_ID: &str = "canvas"; - - let window = wgpu::web_sys::window().unwrap_throw(); - let document = window.document().unwrap_throw(); - let canvas = document.get_element_by_id(CANVAS_ID).unwrap_throw(); - let html_canvas_element = canvas.unchecked_into(); - window_attributes = window_attributes.with_canvas(Some(html_canvas_element)); - } - - let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); - - #[cfg(not(target_arch = "wasm32"))] - { - // If we are not on web we can use pollster to - // await the window creation - self.state = Some(pollster::block_on(State::new(window)).unwrap()); - } - - #[cfg(target_arch = "wasm32")] - { - // Run the future asynchronously and use the - // proxy to send the results to the event loop - if let Some(proxy) = self.proxy.take() { - wasm_bindgen_futures::spawn_local(async move { - assert!( - proxy - .send_event( - State::new(window) - .await - .expect("Unable to create canvas!!!") - ) - .is_ok() - ) - }); - } - } - } - - #[allow(unused_mut)] - fn user_event(&mut self, _event_loop: &ActiveEventLoop, mut event: State) { - // This is where proxy.send_event() ends up - #[cfg(target_arch = "wasm32")] - { - event.window.request_redraw(); - event.resize( - event.window.inner_size().width, - event.window.inner_size().height, - ); - } - self.state = Some(event); - } - - fn window_event( - &mut self, - event_loop: &ActiveEventLoop, - _window_id: winit::window::WindowId, - event: WindowEvent, - ) { - let state = match &mut self.state { - Some(canvas) => canvas, - None => return, - }; - - match event { - WindowEvent::CloseRequested => event_loop.exit(), - WindowEvent::Resized(size) => state.resize(size.width, size.height), - WindowEvent::RedrawRequested => { - state.update(); - match state.render() { - Ok(_) => {} - Err(e) => { - // Log the error and exit gracefully - log::error!("{e}"); - event_loop.exit(); - } - } - } - WindowEvent::MouseInput { button, state: element, .. } => state.handle_mouse_button(button,element), - WindowEvent::CursorMoved { position: pos, .. } => state.handle_mouse_moved(pos), - WindowEvent::KeyboardInput { - event: - KeyEvent { - physical_key: PhysicalKey::Code(code), - state: key_state, - .. - }, - .. - } => state.handle_key(event_loop, code, key_state.is_pressed()), - _ => {} - } - } -} - -pub fn run() -> anyhow::Result<()> { - #[cfg(not(target_arch = "wasm32"))] - { - env_logger::init(); - } - #[cfg(target_arch = "wasm32")] - { - console_log::init_with_level(log::Level::Info).unwrap_throw(); - } - - let event_loop = EventLoop::with_user_event().build()?; - #[cfg(not(target_arch = "wasm32"))] - { - let mut app = App::new(); - event_loop.run_app(&mut app)?; - } - #[cfg(target_arch = "wasm32")] - { - let app = App::new(&event_loop); - event_loop.spawn_app(app); - } - - Ok(()) -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen(start)] -pub fn run_web() -> Result<(), wasm_bindgen::JsValue> { - console_error_panic_hook::set_once(); - run().unwrap_throw(); - - Ok(()) -} diff --git a/src/lib.rs b/src/lib.rs index db89b9b..6c22f2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,5 @@ #![recursion_limit = "256"] -pub mod engine; \ No newline at end of file +pub mod app; +pub mod render; +pub mod web; +pub mod world; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 899b868..9ae830d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ #![recursion_limit = "256"] use game::*; - fn main() { - engine::run().unwrap(); + app::run().unwrap(); } diff --git a/src/notes/old_lang.rs b/src/notes/old_lang.rs deleted file mode 100644 index afa743d..0000000 --- a/src/notes/old_lang.rs +++ /dev/null @@ -1,353 +0,0 @@ -// Macro for expanding syntax form into a struct -macro_rules! definition { - {@build ($n:ident structure) ($($t:tt)*) null () } => { - #[allow(unused)] - #[derive(Debug)] - struct $n ($($t)*); - }; - {@build ($n:ident enumeration new ($($f:tt)*) ($m:ident($($mt:tt)*)$($r:tt)*)) () null ()} => { - definition!{@build ($n enumeration ($($f)*) ($m($($mt)*)$($r)*)) () null () $($mt)*} - }; - {@build ($n:ident enumeration ($($f:tt)*) ($m:ident($($mt:tt)*)$($r:tt)*)) ($($t:tt)*) null ()} => { - definition!{@build ($n enumeration new ($($f)*$m($($t)*)) ($($r)*)) () null () } - }; - {@build ($n:ident enumeration new ($($m:ident($($mt:tt)*))*) ()) () null () } => { - definition!{@build ($n enumeration filter () ($($m($($mt)*))*))} - }; - {@build ($n:ident enumeration filter ($($e:tt)*) ($m:ident($($mt:tt)+)$($t:tt)*))} => { - definition!{@build ($n enumeration filter ($($e)*$m($($mt)*),) ($($t)*))} - }; - {@build ($n:ident enumeration filter ($($e:tt)*) ($m:ident()$($t:tt)*))} => { - definition!{@build ($n enumeration filter ($($e)*$m,) ($($t)*))} - }; - {@build ($n:ident enumeration filter ($($e:tt)*) ())} => { - #[allow(unused)] - #[derive(Debug)] - enum $n { - $($e)* - } - }; - {@build $m:tt ($($t:tt)*) null () $l:literal $($r:tt)*} => { - definition!{@build $m ($($t)*) null () $($r)*} - }; - {@build $m:tt ($($t:tt)*) null () $i:ident $($r:tt)*} => { - definition!{@build $m ($($t)*Box<$i>,) null () $($r)*} - }; - {@build $m:tt ($($t:tt)*) option ($a:tt) () $($r:tt)*} => { - definition!{@build $m ($($t)*Option<$a>,) null () $($r)*} - }; - {@build $m:tt ($($t:tt)*) vector ($a:tt) () $($r:tt)*} => { - definition!{@build $m ($($t)*Vec<$a>,) null () $($r)*} - }; - {@build $m:tt ($($t:tt)*) option ($($a:tt)*) () $($r:tt)*} => { - definition!{@build $m ($($t)*Option<($($a),*)>,) null () $($r)*} - }; - {@build $m:tt ($($t:tt)*) vector ($($a:tt)*) () $($r:tt)*} => { - definition!{@build $m ($($t)*Vec<($($a),*)>,) null () $($r)*} - }; - {@build $m:tt ($($t:tt)*) null () [$($b:tt)*] $($r:tt)*} => { - definition!{@build $m ($($t)*) option () ($($b)*) $($r)*} - }; - {@build $m:tt ($($t:tt)*) null () {$($b:tt)*} $($r:tt)*} => { - definition!{@build $m ($($t)*) vector () ($($b)*) $($r)*} - }; - {@build $m:tt ($($t:tt)*) $i:ident ($($a:tt)*) ($l:literal $($b:tt)*) $($r:tt)*} => { - definition!{@build $m ($($t)*) $i ($($a)*) ($($b)*) $($r)*} - }; - {@build $m:tt ($($t:tt)*) $i:ident ($($a:tt)*) ($n:ident $($b:tt)*) $($r:tt)*} => { - definition!{@build $m ($($t)*) $i ($($a)*$n) ($($b)*) $($r)*} - }; -} -macro_rules! implement { - {@begin $n:ident structure $($t:tt)*} => { - implement!{@build (state result) (structure $n) {} {} $($t)*} - }; - {@begin $n:ident enumeration ($m:ident ($($t2:tt)*)$($t:tt)*)} => { - implement!{@build (state result) (enumeration $m {} ($($t)*) $n) {} {} $($t2)*} - }; - {@build ($st:ident $r:ident) $m:tt $f:tt {$($f2:tt)*} $s:literal $($t:tt)*} => { - implement!{@build ($st $r) $m $f { - $($f2)* - $st = $st.trim_start(); - if $st.starts_with($s) { - $st = &$st[$s.len()..] - } else { - return Err("error".to_string()) - } - } $($t)*} - }; - {@build ($s:ident $r:ident) $m:tt {$($f:tt)*} {$($f2:tt)*} $i:ident $($t:tt)*} => { - implement!{@build ($s $r) $m {$($f)*{$($f2)*}} { - let $r; - match $i::consume($s) { - Ok((r,s)) => { - $s = s; - $r = r; - } - Err(error) => { - return Err(error); - } - } - } $($t)*} - }; - {@build $va:tt ($($m:tt)*) {$($f:tt)*} {$($f2:tt)*} {$($r:tt)*} $($t:tt)*} => { - implement!{@build $va (vector {$($f)*{$($f2)*}} ($($t)*) $($m)*) {} {} $($r)*} - }; - {@build $va:tt ($($m:tt)*) {$($f:tt)*} {$($f2:tt)*} [$($r:tt)*] $($t:tt)*} => { - implement!{@build $va (option {$($f)*{$($f2)*}} ($($t)*) $($m)*) {} {} $($r)*} - }; - {@finish ($s:ident $re:ident) (option {$($l:tt)*} ($($t:tt)*) $($m:tt)*) {{$($f:tt)*}$({$($r:tt)*})*} } => { - implement!{@build ($s $re) ($($m)*) {$($l)*} { - let mut $re; - let taker = move |_state| { - let mut $s: &str = _state; - $($f)* - Ok((( - $({$($r)*$re}),* - ),$s)) - }; - match taker($s) { - Ok((r,s)) => { - $s = s; - $re = Some(r); - } - Err(_) => { - $re = None; - } - } - } $($t)*} - }; - {@finish ($s:ident $re:ident) (vector {$($l:tt)*} ($($t:tt)*) $($m:tt)*) {{$($f:tt)*}$({$($r:tt)*})*} } => { - implement!{@build ($s $re) ($($m)*) {$($l)*} { - let mut $re = Vec::new(); - let taker = move |_state| { - let mut $s: &str = _state; - $($f)* - Ok((( - $({$($r)*$re}),* - ),$s)) - }; - while !$s.is_empty() { - match taker($s) { - Ok((r,s)) => { - $s = s; - $re.push(r); - } - Err(error) => { - break; - } - }; - } - } $($t)*} - }; - {@build $va:tt $m:tt {$($r:tt)*} $r2:tt} => { - implement!{@finish $va $m {$($r)*$r2}} - }; - {@finish ($s:ident $re:ident) (structure $n:ident) {{$($f:tt)*}$({$($r:tt)*})*} } => { - #[allow(unused)] - impl Parse for $n { - fn consume(_state: &str) -> Result<(Self, &str), Error> where Self: Sized { - let mut $s: &str = _state; - $($f)* - Ok(( - $n($({$($r)*$re.into()}),*),$s - )) - } - } - }; - {@finish ($s:ident $re:ident) (enumeration $m2:ident {$($l:tt)*} ($m:ident ($($t2:tt)*)$($t:tt)*) $n:ident) {$f:tt$({$($r:tt)*})+} } => { - implement!{@build ($s $re) (enumeration $m {$($l)*{ - $f - Ok(( - $n::$m2($({$($r)*$re.into()}),+),$s - )) - }} ($($t)*) $n) {} {} $($t2)*} - }; - {@finish ($s:ident $re:ident) (enumeration $m3:ident {$($l:tt)*} ($m:ident ($($t2:tt)*)$($t:tt)*) $n:ident) {$f:tt} } => { - implement!{@build ($s $re) (enumeration $m {$($l)*{ - $f - Ok(( - $n::$m3,$s - )) - }} ($($t)*) $n) {} {} $($t2)*} - }; - {@finish ($s:ident $re:ident) (enumeration $m2:ident {$($l:tt)*} () $n:ident) {$f:tt$({$($r:tt)*})+}} => { - implement!{@finish ($s $re) (enumeration {$($l)*{ - $f - Ok(( - $n::$m2($({$($r)*$re.into()}),+),$s - )) - }} () $n)} // straight to below - }; - {@finish ($s:ident $re:ident) (enumeration $m2:ident {$($l:tt)*} () $n:ident) {$f:tt}} => { - implement!{@finish ($s $re) (enumeration {$($l)*{ - $f - Ok(( - $n::$m2,$s - )) - }} () $n)} // straight to below - }; - {@finish ($s:ident $re:ident) (enumeration {$({$($f:tt)*})*} () $n:ident)} => { - #[allow(unused)] - impl Parse for $n { - fn consume(_state: &str) -> Result<(Self, &str), Error> where Self: Sized { - let mut $s: &str = _state; - $( - let case = (||{ - let mut state: &str = _state; - $($f)* - })(); // put it into a closure and run it (makes returning easier) - if case.is_ok() { - return case; - } - )* - Err("error".into()) - } - } - }; -} -macro_rules! class { - ($i:ident($($t:tt)*)) => { - definition!{@build ($i structure) () null () $($t)*} - implement!{@begin $i structure $($t)*} - //display!{@begin $i structure $($t)*} - }; - ($i:ident{$($n:ident($($t:tt)*)),*}) => { - definition!{@build ($i enumeration new () ($($n($($t)*))*)) () null ()} - implement!{@begin $i enumeration ($($n($($t)*))*)} - //display!{@begin $i enumeration ($($n($($t)*))*)} - } -} -macro_rules! parser { - () => {}; - ($i:ident$t:tt;$($tail:tt)*) => { - class!($i$t); - parser!($($tail)*); - }; -} - -// todo: reimplement attrib here -// https://www.lua.org/manual/5.5/manual.html#9 -parser!{ - // LiteralString -> "..." - // LiteralNumber -> 0x... 123.456 - Chunk ( Block ); - Block ( {Statement} [Return] ); - Statement { - Semicolon (";"), - Assignment (Variables "=" Expressions), - Call (RootAtom CallChain), - Label (Label), - Break ("break"), - Goto ("goto" Name), - Do ("do" Block "end"), - While ("while" Expression "do" Block "end"), - Repeat ("repeat" Block "until" Expression), - If ("if" Expression "then" Block {"elseif" Expression "then" Block} ["else" Block] "end"), - ForRange ("for" Name "=" Expression "," Expression ["," Expression] "do" Block "end"), - ForIn ("for" Names "in" Expressions "do" Block "end"), - Function ("function" FunctionName FunctionBody), - LocalFunction ("local" "function" Name FunctionBody), - GlobalFunction ("global" "function" Name FunctionBody), - Declaration ("local" Names ["=" Expressions]), - Global ("global" Names) - }; - Return( "return" [Expressions] [";"] ); - Label( "::" Name "::" ); - FunctionName( Name {"." Name} [":" Name] ); - Variables( Variable {"," Variable} ); - Names( Name {"," Name} ); - Expressions( Expression {"," Expression} ); - // DEVIATION: This parser is greedy, so the manual's syntax breaks it. - RootAtom { - Variable (Name), - Expression ("(" Expression ")") - }; - IndexAtom { - Dot ("." Name), - Index ("[" Expression "]") - }; - CallAtom { - Normal (Arguments), - Method (":" Name Arguments) - }; - PrefixAtom { - Call (CallAtom), - Index (IndexAtom) - }; - VariableChain { - Link (PrefixAtom VariableChain), - Finish (IndexAtom) - }; - CallChain { - Link (PrefixAtom CallChain), - Finish (CallAtom) - }; - Variable (RootAtom VariableChain); - Expression { - Nil ("nil"), - False ("false"), - True ("true"), - VarArg ("..."), - Number (Number), - String (String), - FunctionDef (FunctionDef), - Prefix (RootAtom {PrefixAtom}), - Table (Table), - Binary (Expression BinaryOp Expression), // todo: . - Unary (UnaryOp Expression) - }; - // ----------------------- - Arguments { - Expressions ("(" [Expressions] ")"), - Table (Table), - String (String) - }; - FunctionDef( "function" FunctionBody ); - FunctionBody( "(" [Parameters] ")" Block "end" ); - Parameters { - Names (Names ["," VarArg]), - VarArg (VarArg) - }; - VarArg( "..." [Name] ); - Table( "{" [Fields] "}" ); - Fields( Field {FieldSep Field} [FieldSep] ); - Field { - ExpressionIndex ("[" Expression "]" "=" Expression), - Name (Name "=" Expression), - Expression (Expression) - }; - FieldSep { - Comma (","), - Semicolon (";") - }; - BinaryOp { - Plus ("+"), - Minus ("-"), - Mul ("*"), - Divide ("/"), - DivFloor ("//"), - Caret ("^"), - Modulo ("%"), - Ampersand ("&"), - Tilde ("~"), - Pipe ("|"), - ShiftRight (">>"), - ShiftLeft ("<<"), - Concat (".."), - Less ("<"), - LessEqual ("<="), - Greater (">"), - GreatEqual (">="), - Equivalent ("=="), - NotEqual ("~="), - And ("and"), - Or ("or") - }; - UnaryOp { - Negate ("-"), - Not ("not"), - Ampersand ("#"), - Tilde ("~") - }; -} \ No newline at end of file diff --git a/src/render/eye.rs b/src/render/eye.rs new file mode 100644 index 0000000..4d03e14 --- /dev/null +++ b/src/render/eye.rs @@ -0,0 +1,131 @@ +use bytemuck::{Pod, Zeroable}; +use env_logger::Env; +use glam::{Affine3A, EulerRot, Mat3A, Mat4, Vec3, Vec4}; +use glam::camera::lh::proj::directx::perspective; +use wgpu::{Device, Queue}; +use wgpu::util::DeviceExt; + +pub(crate) struct Eye { + pub(crate) frame: Affine3A, + pub(crate) environment: Environment, + aspect_ratio: f32, + z_near: f32, + z_far: f32, + fov_y: f32, + pub(crate) environment_buffer: wgpu::Buffer, + pub(crate) buffer: wgpu::Buffer, + pub(crate) layout: wgpu::BindGroupLayout, + pub(crate) group: wgpu::BindGroup, +} + +#[repr(C)] +#[derive(Pod, Copy, Clone, Zeroable)] +struct Environment { + ambient: Vec4, + light: Vec4, + dir: Vec4, +} + +impl Eye { + pub(crate) fn view(&self) -> Mat4 { + let projection = perspective(self.fov_y, self.aspect_ratio, self.z_near, self.z_far); + projection * self.frame.inverse() + } + pub(crate) fn write(&mut self, queue: &Queue) { + queue.write_buffer(&self.buffer,0,bytemuck::cast_slice(&[ + self.view(), + Mat4::from_mat3_translation(self.frame.matrix3.into(), Vec3::from(self.frame.translation)) + ])); + queue.write_buffer(&self.environment_buffer,0,bytemuck::cast_slice(&[self.environment])); + } + pub(crate) fn new(device: &Device, width: u32, height: u32) -> Eye { + let buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Camera Buffer"), + contents: bytemuck::cast_slice(&[Mat4::from_translation(Vec3::new(0.0,2.0,-8.0)),Mat4::IDENTITY]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + } + ); + + let dir = Vec3::new(1.0,0.5,1.0).normalize(); + let environment = Environment { + ambient: Vec4::new(0.15,0.15,0.15, 0.0), + light: Vec4::new(1.0,1.0,1.0, 0.0), + dir: Vec4::new(dir.x,dir.y,dir.z,0.0), + }; + + let environment_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some(""), + contents: bytemuck::cast_slice(&[environment]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + } + ); + + let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + } + ], + label: Some("eye_bind_group_layout"), + }); + + let group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: environment_buffer.as_entire_binding(), + } + ], + label: Some("eye_bind_group"), + }); + + Eye { + aspect_ratio: width as f32 / height as f32, + frame: Affine3A::IDENTITY, + fov_y: 90.0, + z_near: 0.1, + z_far: 1000.0, + buffer, + group, + layout, + environment, + environment_buffer, + } + } + pub(crate) fn resize(&mut self, width: u32, height: u32) { + self.aspect_ratio = width as f32 / height as f32 + } + pub(crate) fn control(&mut self, delta: Vec3) { + self.frame *= Affine3A::from_translation(delta); + } + pub(crate) fn rotate(&mut self, yaw: f32, pitch: f32) { + let (mut y,mut x,z) = self.frame.matrix3.to_euler(EulerRot::YXZ); + x = (x + pitch * 0.005).clamp(-1.4,1.4); + y += yaw * 0.005; + self.frame.matrix3 = Mat3A::from_euler(EulerRot::YXZ,y,x,z); + } +} \ No newline at end of file diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..b35b4a5 --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,1043 @@ +pub mod eye; + +use std::cell::RefCell; +use glam::prelude::*; +use std::collections::{HashMap}; +use std::error::Error; +use std::fmt::{Debug, Display, Formatter}; +use std::hash::{BuildHasherDefault, Hash, Hasher}; +use std::ops::{Range}; +use std::rc::Rc; +use std::sync::Arc; +use bytemuck::{Pod, Zeroable}; +use gltf::{Gltf, Node, Semantic}; +use image::EncodableLayout; +use wgpu::{Device, Queue}; +use wgpu::naga::{FastHashMap, FastHashSet}; +use wgpu::util::DeviceExt; +use winit::window::Window; +use crate::render::eye::Eye; +use crate::world::{Shape, SimpleColliderData, SimpleLight, SimpleObject, SimpleObjectData}; + +const DEFAULT_VERTICES: [SimpleVertex;0] = []; + +#[repr(C)] +#[derive(Pod,Zeroable,Copy,Clone)] +pub struct SimpleModelInstance { + pub transform: Mat4, + pub color: Vec4, + pub lights: [u16;16], + pub point_lights: u32, + pub spot_lights: u32, + pub metal: f32, + pub rough: f32, +} + +#[repr(C)] +#[derive(Pod,Copy,Clone,Zeroable)] +pub struct SimpleLightInstance { + pub location: Vec4, + pub rotation: Vec4, + pub color: Vec4, +} + +#[derive(Clone)] +pub struct SimpleLightData { + pub index: usize, + pub instance: SimpleLightInstance, + pub transform: Affine3, +} + +#[derive(Clone)] +pub struct SimpleModelData { + pub instance: SimpleModelInstance, + pub material: Id, + pub mesh: Id, +} + +pub struct SimpleInstances { + light_count: usize, + light_buffer: wgpu::Buffer, + instance_count: usize, + instance_buffer: wgpu::Buffer, + //light_ref_last: usize, + //light_ref_buffer: wgpu::Buffer, + objects: FastHashMap,FastHashMap,FastHashSet>>, + lights: FastHashMap, +} + +enum SimpleRenderCode { + Material(wgpu::BindGroup), + Mesh((wgpu::Buffer,u32),Option<(wgpu::Buffer,u32)>), + Draw(Range) +} + +struct SimpleRenderProgram(Vec); + +impl SimpleRenderProgram { + fn push(&mut self, item: SimpleRenderCode) { + self.0.push(item) + } + fn new() -> SimpleRenderProgram { + SimpleRenderProgram(Vec::new()) + } + fn render(self, pass: &mut wgpu::RenderPass) { + let mut count = 0; + let mut indexed = false; + for code in self.0 { + match code { + SimpleRenderCode::Material(material) => { + pass.set_bind_group(Renderer::SIMPLE_RENDER_TEXTURE_GROUP_POSITION,&material,&[]) + } + SimpleRenderCode::Mesh(vertices,indices) => { + pass.set_vertex_buffer(0, vertices.0.slice(..)); + if let Some(indices) = indices { + pass.set_index_buffer(indices.0.slice(..),wgpu::IndexFormat::Uint32); + count = indices.1; + indexed = true; + } else { + count = vertices.1; + indexed = false; + } + } + SimpleRenderCode::Draw(instances) => { + if indexed { + pass.draw_indexed(0..count, 0, instances) + } else { + pass.draw(0..count, instances); + } + } + } + } + } +} + +impl SimpleInstances { + const MIN_SIZE: u64 = 64; + pub fn add_object(&mut self, object: SimpleObject) { + if let Some(ref model) = object.0.borrow().model { + self.instance_count += 1; + self.objects + .entry(model.mesh.clone()).or_insert(FastHashMap::with_hasher(BuildHasherDefault::new())) + .entry(model.material.clone()).or_insert(FastHashSet::with_hasher(BuildHasherDefault::new())) + .insert(object.clone()); + } + } + + pub fn remove_object(&mut self, object: SimpleObject) { + if let Some(ref model) = object.0.borrow().model { + self.instance_count -= 1; + self.objects + .entry(model.mesh.clone()).or_insert(FastHashMap::with_hasher(BuildHasherDefault::new())) + .entry(model.material.clone()).or_insert(FastHashSet::with_hasher(BuildHasherDefault::new())) + .remove(&object); + } + } + + pub fn add_light(&mut self, light: SimpleLight) { + self.light_count += 1; + self.lights.insert(light,0); + } + + pub fn remove_light(&mut self, light: &SimpleLight) { + self.light_count -= 1; + self.lights.remove(light); + } + + pub fn reallocate_buffer(device: &wgpu::Device, queue: &wgpu::Queue, buffer: &mut wgpu::Buffer, count: usize, item_size: usize) { + let size = buffer.size() / item_size as wgpu::BufferAddress; + if count > SimpleInstances::MIN_SIZE as usize { + let mut reallocate: Option = None; + if count < (size / 2) as usize { + reallocate = Some(count / 2); + } else if count > size as usize { + reallocate = Some(count * 2); + } + if let Some(new_size) = reallocate { + *buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Instance Buffer"), + size: (item_size * new_size) as wgpu::BufferAddress, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX, + mapped_at_creation: false, + }); + } + } + } + pub fn write_lights(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) { + //SimpleInstances::reallocate_buffer(device, queue, &mut self.light_ref_buffer, self.light_ref_last, size_of::()); + /* + let mut light_ref_buffer = queue.write_buffer_with( + &self.light_ref_buffer, + 0 as wgpu::BufferAddress, + wgpu::BufferSize::new(self.instance_buffer.size()).unwrap() + ).unwrap(); + */ + SimpleInstances::reallocate_buffer(device, queue, &mut self.light_buffer, self.light_count, size_of::()); + let mut buffer = queue.write_buffer_with( + &self.light_buffer, + 0 as wgpu::BufferAddress, + wgpu::BufferSize::new(self.light_buffer.size()).unwrap(), + ).unwrap(); + let stride = size_of::(); + for (new_index,(light,index)) in self.lights.iter_mut().enumerate() { + *index = new_index + 1; + let begin = *index * stride; + buffer.slice(begin..begin + stride).copy_from_slice(bytemuck::cast_slice(&[light.0.borrow().instance])); + } + } + pub fn write_instances(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) -> SimpleRenderProgram { + let mut program = SimpleRenderProgram::new(); + SimpleInstances::reallocate_buffer(device, queue, &mut self.instance_buffer, self.instance_count, size_of::()); + let mut buffer = queue.write_buffer_with( + &self.instance_buffer, + 0 as wgpu::BufferAddress, + wgpu::BufferSize::new(self.instance_buffer.size()).unwrap() + ).unwrap(); + let mut index: u32 = 0; + let stride = size_of::(); + for (mesh,materials) in self.objects.iter() { + program.push(SimpleRenderCode::Mesh(mesh.it.vertices.clone(), mesh.it.indices.clone())); + for (material, objects) in materials.iter() { + program.push(SimpleRenderCode::Material(material.it.group.clone())); + let before = index; + for object in objects.iter() { + let begin = index as usize * stride; + buffer.slice(begin .. begin + stride).copy_from_slice(bytemuck::cast_slice(&[object.0.borrow().model.as_ref().unwrap().instance])); + index += 1; + } + program.push(SimpleRenderCode::Draw(Range::from(before..index))); + } + }; + program + } + + fn desc() -> wgpu::VertexBufferLayout<'static> { + use std::mem; + wgpu::VertexBufferLayout { + array_stride: size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &[ // todo: is this too big? + wgpu::VertexAttribute { + offset: 0, + shader_location: 4, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: size_of::<[f32; 4]>() as wgpu::BufferAddress, + shader_location: 5, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: size_of::<[f32; 8]>() as wgpu::BufferAddress, + shader_location: 6, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: size_of::<[f32; 12]>() as wgpu::BufferAddress, + shader_location: 7, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: size_of::<[f32; 16]>() as wgpu::BufferAddress, + shader_location: 8, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: size_of::<[f32; 16]>() as wgpu::BufferAddress, + shader_location: 9, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: size_of::<[f32; 16]>() as wgpu::BufferAddress, + shader_location: 10, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: size_of::<[f32; 16]>() as wgpu::BufferAddress, + shader_location: 11, + format: wgpu::VertexFormat::Float32x4, + }, + ], + } + } + + pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> SimpleInstances { + let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Instance Buffer"), + size: (size_of::() * SimpleInstances::MIN_SIZE as usize) as wgpu::BufferAddress, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let light_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Light Buffer"), + size: (size_of::() * SimpleInstances::MIN_SIZE as usize) as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let light_ref_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Light Ref Buffer"), + size: (size_of::() * SimpleInstances::MIN_SIZE as usize) as wgpu::BufferAddress, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false + }); + SimpleInstances { + light_count: 0, + //light_ref_last: 0, + instance_count: 0, + light_buffer, + instance_buffer, + //light_ref_buffer, + objects: FastHashMap::with_hasher(BuildHasherDefault::new()), + lights: Default::default(), + } + } +} + +pub struct Renderer { + id_count: u64, + surface: wgpu::Surface<'static>, + config: wgpu::SurfaceConfiguration, + device: wgpu::Device, + queue: wgpu::Queue, + pub(crate) eye: eye::Eye, + material_layout: wgpu::BindGroupLayout, + default_texture: SimpleTexture, + pipeline: wgpu::RenderPipeline, + depth_texture: SimpleTexture, + pub(crate) instances: SimpleInstances, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct SimpleVertex { + position: [f32; 3], + normal: [f32; 3], + tex_coord: [f32; 2], + tangent: [f32; 3], + bitangent: [f32; 3], +} + +impl SimpleVertex { + fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: size_of::<[f32; 6]>() as wgpu::BufferAddress, + shader_location: 2, + format: wgpu::VertexFormat::Float32x2, + } + ] + } + } +} +#[derive(Debug, Clone, PartialEq)] +pub struct SimpleMesh { + indices: Option<(wgpu::Buffer,u32)>, + vertices: (wgpu::Buffer, u32), +} +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub(crate) struct SimpleTexture { + texture: wgpu::Texture, + view: wgpu::TextureView, +} +struct TextureProperties { + width: u32, + height: u32, +} +impl SimpleTexture { + pub fn load(device: &Device, queue: &Queue, slice: impl AsRef<[u8]>, properties: TextureProperties) -> SimpleTexture { + let size = wgpu::Extent3d { + width: properties.width, + height: properties.height, + depth_or_array_layers: 1, + }; + let diffuse_texture = device.create_texture( + &wgpu::TextureDescriptor { + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + label: Some("texture"), + view_formats: &[], + } + ); + let diffuse_texture_view = diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default()); + queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture: &diffuse_texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + slice.as_ref(), + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(4 * size.width), + rows_per_image: Some(size.height), + }, + size, + ); + SimpleTexture { + texture: diffuse_texture, + view: diffuse_texture_view, + } + } +} + +#[derive(Clone)] +pub struct SimpleMaterial { + group: wgpu::BindGroup, +} + +#[derive(Clone)] +pub struct Id { + id: u64, + it: T, +} + +impl Hash for Id { + fn hash(&self, state: &mut H) { + self.id.hash(state) + } +} + +impl PartialEq for Id { + fn eq(&self, other: &Self) -> bool { + other.id == self.id + } +} + +impl Eq for Id {} + +struct MaterialProperties { + edge: wgpu::AddressMode, + filter: wgpu::FilterMode, +} + +impl SimpleMaterial { + fn new(renderer: &Renderer, base: &SimpleTexture, normal: &SimpleTexture, reflect: &SimpleTexture, config: MaterialProperties) -> SimpleMaterial { + let sampler = renderer.device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: config.edge, + address_mode_v: config.edge, + address_mode_w: config.edge, + mag_filter: config.filter, + min_filter: config.filter, + mipmap_filter: wgpu::MipmapFilterMode::Nearest, + ..Default::default() + }); + let group = renderer.device.create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &renderer.material_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&base.view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(&normal.view), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::TextureView(&reflect.view), + }, + ], + label: Some("diffuse_bind_group"), + } + ); + SimpleMaterial { + group, + } + } +} + +#[derive(Debug)] +struct PoolError(String); + +impl Display for PoolError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f,"{}",self.0) + } +} + +impl Error for PoolError {} + +pub struct SimpleTreeNode { + object: Option, + children: Vec, + name: String, +} + +impl SimpleTreeNode { + pub fn first_object(&self) -> Option { + if let Some(object) = self.object.clone() { + Some(object) + } else { + for child in self.children.iter() { + if let Some(object) = child.first_object() { + return Some(object) + } + } + None + } + } +} + +type GltfVertexBufferKey = (Option,Option,Option,Option); + +impl Renderer { + const SIMPLE_RENDER_EYE_GROUP_POSITION: u32 = 0; + const SIMPLE_RENDER_TEXTURE_GROUP_POSITION: u32 = 1; + const SIMPLE_RENDER_MODEL_GROUP_POSITION: u32 = 2; + pub fn new_id(&mut self, value: T) -> Id { + self.id_count += 1; + Id { + it: value, + id: self.id_count, + } + } + pub fn load_texture_from_bytes(&mut self, slice: impl AsRef<[u8]>) -> SimpleTexture { + let image = image::load_from_memory(slice.as_ref()); + if let Ok(data) = image { + let data = data.into_rgba8(); + SimpleTexture::load(&self.device, &self.queue, data.as_bytes(), TextureProperties { + width: data.width(), + height: data.height(), + }) + } else { + self.default_texture.clone() + } + } + pub fn new_material(&mut self, base: &SimpleTexture, normal: &SimpleTexture, reflect: &SimpleTexture) -> Id { + self.new_id(SimpleMaterial::new(self, base, normal, reflect, MaterialProperties { + edge: wgpu::AddressMode::Repeat, + filter: wgpu::FilterMode::Linear, + })) + } + pub fn new_texture_from_gltf(&mut self, info: &gltf::texture::Texture, images: &Vec) -> SimpleTexture { + if let Some(image) = images.get(info.source().index()) { + let mut new_pixels = Vec::new(); + let pixels: &Vec; + match image.format { + gltf::image::Format::R8G8B8 => { + for pixel in image.pixels.chunks(3) { + new_pixels.push(pixel[0]); + new_pixels.push(pixel[0]); + new_pixels.push(pixel[0]); + new_pixels.push(255); + } + pixels = &new_pixels; + } + gltf::image::Format::R8G8B8A8 => { + pixels = &image.pixels + } + _ => return self.default_texture.clone() + } + SimpleTexture::load(&self.device, &self.queue, pixels.as_slice(), TextureProperties { + width: image.width, + height: image.height, + }) + } else { + self.default_texture.clone() + } + } + pub fn new_mesh_from_gltf( + &mut self, + primitive: gltf::Primitive, + meshes: &mut HashMap, + buffers: &[gltf::buffer::Data] + ) -> Id { + let position_index = primitive.get(&Semantic::Positions).and_then(|it| it.view()).and_then(|it| Some(it.buffer().index())); + let normals_index = primitive.get(&Semantic::Normals).and_then(|it| it.view()).and_then(|it| Some(it.buffer().index())); + let tex_coords_index = primitive.get(&Semantic::TexCoords(0)).and_then(|it| it.view()).and_then(|it| Some(it.buffer().index())); + let indices_index = primitive.indices().and_then(|it| it.view()).and_then(|it| Some(it.buffer().index())); + let tangent_index = primitive.get(&Semantic::Tangents).and_then(|it| it.view()).and_then(|it| Some(it.buffer().index())); + let key = (position_index,normals_index,tex_coords_index,indices_index); + self.new_id(meshes.entry(key).or_insert_with(|| { + let mut tangents = false; + let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); + let mut vertex_data: Vec; + if let Some(positions) = reader.read_positions() { + vertex_data = Vec::with_capacity(positions.len()); + let mut normal = reader.read_normals().map(|it| it.into_iter()); + let mut tex_coord = reader.read_tex_coords(0).map(|it| it.into_f32().into_iter()); + let mut tangent = reader.read_tangents().map(|it| it.into_iter()); + tangents = tangent.is_some(); + for position in positions { + vertex_data.push(SimpleVertex { + position, + normal: if let Some(ref mut normals) = normal { + if let Some(normal) = normals.next() { + normal + } else { + [0.0;3] + } + } else { + [0.0;3] + }, + tex_coord: if let Some(ref mut tex_coords) = tex_coord { + tex_coords.next() + } else { + None + }.unwrap_or([0.0;2]), + tangent: [0.0;3], // todo: tangent from model + bitangent: [0.0;3], + }) + } + } else { + vertex_data = Vec::new(); + } + let vertex_buffer = self.device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(vertex_data.as_slice()), + usage: wgpu::BufferUsages::VERTEX, + } + ); + let vertices_result = (vertex_buffer,vertex_data.len() as u32); + let indices_result = if let Some(indices) = reader.read_indices() { + let index_data: Vec = indices.into_u32().collect(); + Some((self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(index_data.as_slice()), + usage: wgpu::BufferUsages::INDEX, + }), index_data.len() as u32)) + } else { + None + }; + SimpleMesh { + indices: indices_result, + vertices: vertices_result + } + }).clone()) + } + pub fn load_node_from_gltf( + &mut self, + node: gltf::Node, + meshes: &mut HashMap, + textures: &mut HashMap, + images: &Vec, + buffers: &Vec, + ) -> SimpleTreeNode { + let mut tree_node = SimpleTreeNode { + object: None, + children: Vec::new(), + name: node.name().unwrap_or("Node").to_string() + }; + if let Some(mesh) = node.mesh() { + let len = mesh.primitives().len(); + for primitive in mesh.primitives() { + let pbr = primitive.material().pbr_metallic_roughness(); + let material = primitive.material(); + let color = pbr.base_color_factor(); + let metal = pbr.metallic_factor(); + let rough = pbr.roughness_factor(); + let light = primitive.material().emissive_factor(); + let base = match pbr.base_color_texture() { + Some(info) => textures.entry(info.texture().source().index()).or_insert_with(|| self.new_texture_from_gltf(&info.texture(), &images)).clone(), + None => self.default_texture.clone() + }; + let reflect = match pbr.metallic_roughness_texture() { + Some(info) => textures.entry(info.texture().source().index()).or_insert_with(|| self.new_texture_from_gltf(&info.texture(), &images)).clone(), + None => self.default_texture.clone() + }; + let normal = match material.normal_texture() { + Some(info) => textures.entry(info.texture().source().index()).or_insert_with(|| self.new_texture_from_gltf(&info.texture(), &images)).clone(), + None => self.default_texture.clone() + }; + let material = self.new_material(&base, &normal, &reflect); + let mesh = self.new_mesh_from_gltf(primitive,meshes,buffers); + let object = SimpleObject(Rc::new(RefCell::new(SimpleObjectData { + model: Some(SimpleModelData { + instance: SimpleModelInstance { + transform: Mat4::default(), + color: Vec4::from_array(color), + lights: [0;16], + point_lights: 0, + spot_lights: 0, + metal, + rough, + }, + material, + mesh, + }), + collider: SimpleColliderData { + transform: Mat4::default(), + shape: Shape::None, + radius: 0.0, + }, + }))); + if len == 1 { + tree_node.object = Some(object); + } else { + tree_node.children.push(SimpleTreeNode { + object: Some(object), + children: Vec::new(), + name: "Primitive".to_string() + }) + } + } + } + for node in node.children() { + tree_node.children.push(self.load_node_from_gltf(node,meshes,textures,images,buffers)); + } + tree_node + } + pub fn load_from_gltf(&mut self, slice: impl AsRef<[u8]>) -> SimpleTreeNode { + let mut root = SimpleTreeNode { + object: None, + children: Vec::new(), + name: "Root".to_string(), + }; + if let Ok((document, buffers, images)) = gltf::import_slice(slice) { + let mut meshes: HashMap = HashMap::new(); + let mut textures: HashMap = HashMap::new(); + for scene in document.scenes() { + let mut scene_node = SimpleTreeNode { + object: None, + children: Vec::new(), + name: scene.name().unwrap_or("Scene").to_string(), + }; + for node in scene.nodes() { + scene_node.children.push(self.load_node_from_gltf(node, &mut meshes, &mut textures, &images, &buffers)); + } + root.children.push(scene_node) + } + } + root + } + pub fn resize(&mut self, width: u32, height: u32) { + self.surface.configure(&self.device, &self.config); + self.eye.resize(width, height); + } + pub async fn new(window: &Arc) -> anyhow::Result { + let bounds = window.inner_size(); + let width = bounds.width; + let height = bounds.height; + // The instance is a handle to our GPU + // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + #[cfg(not(target_arch = "wasm32"))] + backends: wgpu::Backends::PRIMARY, + #[cfg(target_arch = "wasm32")] + backends: wgpu::Backends::GL, + flags: Default::default(), + memory_budget_thresholds: Default::default(), + backend_options: Default::default(), + display: None, + }); + + let surface = instance.create_surface(window.clone())?; + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await?; + + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), + // WebGL doesn't support all of wgpu's features, so if + // we're building for the web we'll have to disable some. + required_limits: if cfg!(target_arch = "wasm32") { + wgpu::Limits::downlevel_webgl2_defaults() + } else { + wgpu::Limits::default() + }, + memory_hints: Default::default(), + trace: wgpu::Trace::Off, + }) + .await?; + + let surface_caps = surface.get_capabilities(&adapter); + // Shader code in this tutorial assumes an sRGB surface texture. Using a different + // one will result in all the colors coming out darker. If you want to support non + // sRGB surfaces, you'll need to account for that when drawing to the frame. + let surface_format = surface_caps + .formats + .iter() + .find(|f| f.is_srgb()) + .copied() + .unwrap_or(surface_caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width, + height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + let eye = Eye::new(&device, width, height); + + let shader = device.create_shader_module(wgpu::include_wgsl!("../assets/SimpleShader.wgsl")); + + let texture = SimpleTexture::load(&device, &queue, [255,255,255,255], TextureProperties { + width: 1, + height: 1, + }); + + let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + } + ], + label: Some("texture_bind_group_layout"), + }); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[ + Some(&eye.layout), + Some(&texture_bind_group_layout), + ], + immediate_size: 0, + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[ + SimpleVertex::desc(),SimpleInstances::desc(), + ], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + // 3. + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + // 4. + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: Some(true), + depth_compare: Some(wgpu::CompareFunction::Less), + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview_mask: None, + cache: None, + }); + + let size = wgpu::Extent3d { // 2. + width: config.width.max(1), + height: config.height.max(1), + depth_or_array_layers: 1, + }; + let desc = wgpu::TextureDescriptor { + label: Some("depth texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT // 3. + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }; + let depth_texture = device.create_texture(&desc); + let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + let depth_sampler = device.create_sampler( + &wgpu::SamplerDescriptor { // 4. + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::MipmapFilterMode::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), // 5. + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + ..Default::default() + } + ); + + let instances = SimpleInstances::new(&device,&queue); + + let staging_belt = wgpu::util::StagingBelt::new(device.clone(), 0x1024); + + let buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&DEFAULT_VERTICES), + usage: wgpu::BufferUsages::VERTEX, + } + ); + + Ok(Renderer { + id_count: 0, + material_layout: texture_bind_group_layout, + default_texture: texture, + depth_texture: SimpleTexture { + texture: depth_texture, + view: depth_view, + }, + instances, + pipeline, + surface, + config, + device, + queue, + eye, + }) + } + pub(crate) fn render(&mut self, window: &Arc) -> anyhow::Result<()> { + window.request_redraw(); + + let output = match self.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(surface_texture) => surface_texture, + wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => { + self.surface.configure(&self.device, &self.config); + surface_texture + } + wgpu::CurrentSurfaceTexture::Timeout + | wgpu::CurrentSurfaceTexture::Occluded + | wgpu::CurrentSurfaceTexture::Validation => { + // Skip this frame + return Ok(()); + } + wgpu::CurrentSurfaceTexture::Outdated => { + self.surface.configure(&self.device, &self.config); + return Ok(()); + } + wgpu::CurrentSurfaceTexture::Lost => { + // You could recreate the devices and all resources + // created with it here, but we'll just bail + anyhow::bail!("Lost device"); + } + }; + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let program = self.instances.write_instances(&self.device,&self.queue); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(Default::default()), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, + }); + + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.eye.group, &[]); + pass.set_vertex_buffer(1, self.instances.instance_buffer.slice(..)); + program.render(&mut pass); + } + + self.queue.submit(Some(encoder.finish())); + + self.eye.write(&self.queue); + + output.present(); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/web.rs b/src/web.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/world/mod.rs b/src/world/mod.rs new file mode 100644 index 0000000..94875fa --- /dev/null +++ b/src/world/mod.rs @@ -0,0 +1,223 @@ +use std::cell::RefCell; +use std::hash::{BuildHasherDefault, Hash, Hasher}; +use std::ops::{Div, Mul}; +use std::rc::Rc; +use glam::{DVec3, IVec3, Mat4, UVec3, Vec3, Vec4Swizzles}; +use wgpu::naga::{FastHashMap, FastHashSet}; +use crate::render::{SimpleModelData, SimpleLightData, Renderer}; + +#[derive(Clone)] +pub enum Shape { + Block(Vec3), + Sphere(f32), + None, +} + +#[derive(Clone)] +pub struct SimpleColliderData { + pub transform: Mat4, + pub shape: Shape, + pub radius: f32, +} + +#[derive(Clone)] +pub struct SimpleObjectData { + pub model: Option, + pub collider: SimpleColliderData, +} + +#[derive(Clone)] +pub struct SimpleLight(pub Rc>); + +impl PartialEq for SimpleLight { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for SimpleLight {} + +impl Hash for SimpleLight { + fn hash(&self, state: &mut H) { + (self.0.as_ptr() as *const RefCell).hash(state) + } +} + +#[derive(Clone)] +pub struct SimpleObject(pub Rc>); + +impl SimpleObject { + pub(crate) fn hard_clone(&self) -> SimpleObject { + SimpleObject { + 0: Rc::new(RefCell::new(SimpleObjectData { + model: self.0.borrow().model.clone(), + collider: self.0.borrow().collider.clone(), + })), + } + } +} + +impl PartialEq for SimpleObject { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0,&other.0) + } +} + +impl Eq for SimpleObject {} + +impl Hash for SimpleObject { + fn hash(&self, state: &mut H) { + (self.0.as_ptr() as *const RefCell).hash(state); + } +} + +#[derive(Eq, Hash, PartialEq, Clone)] +enum Interest { + Light(SimpleLight), + Object(SimpleObject), +} + +struct Block { + debug: Option, + volume: u8, + velocity: Vec3, + material: u8, + interests: FastHashSet, + blocks: [Option>;64], +} + +const TREE_ATTACK: usize = 4; +const TREE_FLOOR: usize = 4; +impl Block { + fn new() -> Block { + Block { + debug: None, + volume: 0, + velocity: Vec3::ZERO, + material: 0, + interests: FastHashSet::with_hasher(BuildHasherDefault::default()), + blocks: [const { None }; const { TREE_ATTACK * TREE_ATTACK * TREE_ATTACK }], + } + } + fn place_pos(&mut self, interest: Interest, pos: Vec3, rad: f32, block_size: f32) { + let cs = block_size / const { TREE_ATTACK as f32 }; + if rad < cs || block_size >= TREE_FLOOR as f32 { + let rel = ((pos + block_size / 2.0) / cs).round().as_uvec3(); + let new_pos = pos - (rel.as_vec3() + cs / 2.0); + let index = (rel.x + rel.y * 4 + rel.z * 16) as usize; + match self.blocks[index] { + Some(ref mut block) => { + block.place_pos(interest,new_pos,rad,cs); + }, + None => { + let mut block = Box::new(Block::new()); + block.place_pos(interest,new_pos,rad,cs); + self.blocks[index] = Some(block); + } + } + } else { + self.interests.insert(interest); + } + } + fn debug_step(&mut self, value: bool) { + match self.debug { + Some(ref mut debug) => { + if value { + + } else { + + } + }, + None => { + if value { + + } else { + + } + } + } + } +} + +pub struct World { + chunk_size: u32, + map: FastHashMap, + pub renderer: Option, + debug_world_map: bool, + debug_world_map_object: Option, +} + +impl World { + pub fn new() -> World { + World { + chunk_size: 512, + map: FastHashMap::with_hasher(BuildHasherDefault::default()), + renderer: None, + debug_world_map: false, + debug_world_map_object: None, + } + } + pub fn add_renderer(&mut self, renderer: Renderer) { + self.renderer = Some(renderer); + self.debug_world_map_object = self.renderer.as_mut().unwrap().load_from_gltf(include_bytes!("../assets/debug.glb")).first_object() + } + pub fn add_object(&mut self, object: SimpleObject) { + self.place(Interest::Object(object.clone())); + if let Some(ref mut renderer) = self.renderer { + renderer.instances.add_object(object) + } + } + pub fn set_debug(&mut self, value: bool) { + self.debug_world_map = value; + } + pub fn light_step(&mut self) { + + } + pub fn object_step(&mut self) { + + } + pub fn debug_step(&mut self) { + self.light_step(); + self.object_step(); + for (index, item) in self.map.iter_mut() { + item.debug_step(self.debug_world_map); + } + } + fn place_pos(&mut self, interest: Interest, pos: Vec3, rad: f32) { + let cs = self.chunk_size as f32; + let c_rad = ((cs / 2.0) * (cs / 2.0)) * 3.0; + for x in (pos.x - rad).div(cs) as i32..=(pos.x + rad).div(cs).ceil() as i32 { + for y in (pos.y - rad).div(cs) as i32..=(pos.y + rad).div(cs).ceil() as i32 { + for z in (pos.z - rad).div(cs) as i32..=(pos.z + rad).div(cs).ceil() as i32 { + let block_pos = IVec3::new(x,y,z); + let block_pos_f32 = block_pos.as_vec3(); + if block_pos_f32.distance_squared(pos) < rad + c_rad { + self.map.entry(block_pos).or_insert_with(|| { + let mut block = Block::new(); + block.place_pos( + interest.clone(), + pos - block_pos_f32, + rad, + self.chunk_size as f32, + ); + block + }); + } + } + } + } + } + fn place(&mut self, interest: Interest) { + let (pos,rad) = match &interest { + Interest::Light(light) => { + let light = light.0.borrow(); + (light.instance.location.xyz(),light.instance.color.length()) + }, + Interest::Object(object) => { + let collider = &object.0.borrow().collider; + (collider.transform.to_scale_rotation_translation().2,collider.radius) + }, + }; + self.place_pos(interest,pos,rad); + } +} \ No newline at end of file