diff --git a/src/controller.rs b/src/controller.rs index 9bc1ef5..cac5115 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,4 +1,7 @@ -use winit::event::{MouseButton, MouseScrollDelta, WindowEvent}; +use winit::{ + event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}, + keyboard::{Key, NamedKey}, +}; use crate::camera::Camera; @@ -12,9 +15,10 @@ pub struct CameraController { cursor_start: (f32, f32), movement: (f32, f32), is_moving: bool, + is_ctrl_pressed: bool, movement_total: (f32, f32), - zoom_total: f32, + pub zoom_total: f32, } impl CameraController { @@ -27,6 +31,7 @@ impl CameraController { cursor_start: (0.0, 0.0), movement: (0.0, 0.0), is_moving: false, + is_ctrl_pressed: false, movement_total: (0.0, 0.0), zoom_total: 1.0, @@ -80,20 +85,33 @@ impl CameraController { } WindowEvent::MouseWheel { delta, .. } => match delta { MouseScrollDelta::LineDelta(_, y) => { - self.zoom_total += y; - if self.zoom_total >= 0.0 && self.zoom_total <= MAX_ZOOM_LEVEL { - self.zoom = *y; + if !self.is_ctrl_pressed { + self.zoom_total += y; + if self.zoom_total >= 0.0 && self.zoom_total <= MAX_ZOOM_LEVEL { + self.zoom = *y; + } + self.zoom_total = self.zoom_total.clamp(0.0, MAX_ZOOM_LEVEL); } - self.zoom_total = self.zoom_total.clamp(0.0, MAX_ZOOM_LEVEL); true } _ => false, }, + WindowEvent::KeyboardInput { event, .. } => { + self.is_ctrl_pressed = false; + if event.state == ElementState::Pressed { + let key = &event.logical_key; + if let Key::Named(NamedKey::Control) = key { + self.is_ctrl_pressed = true; + } + } + true + } + _ => false, } } - pub fn update_camera(&mut self, camera: &mut Camera) { + pub fn update(&mut self, camera: &mut Camera) { use cgmath::InnerSpace; let forward = camera.target - camera.eye; diff --git a/src/lib.rs b/src/lib.rs index 3b65455..bdce0b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ use winit::event_loop::{ControlFlow, EventLoop}; pub mod app; pub mod camera; pub mod controller; +pub mod spotlight; pub mod state; pub mod texture; pub mod vertex; diff --git a/src/shader.wgsl b/src/shader.wgsl index df770d5..0ad2a4e 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,7 +1,7 @@ // Vertex shader struct CameraUniform { - view_proj: mat4x4, + view_proj: mat4x4, }; @group(1) @binding(0) @@ -21,20 +21,35 @@ struct VertexOutput { fn vs_main( model: VertexInput, ) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; + var out: VertexOutput; + out.tex_coords = model.tex_coords; out.position = camera.view_proj * vec4(model.position, 0.0, 1.0); - return out; + return out; } // Fragment shader +struct SpotlightUniform { + cursor_position: vec2, + size: f32, + zoom_level: f32, + strength: f32 +} + @group(0) @binding(0) var t_diffuse: texture_2d; @group(0) @binding(1) var s_diffuse: sampler; +@group(2) @binding(0) +var spotlight: SpotlightUniform; @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return textureSample(t_diffuse, s_diffuse, in.tex_coords); + var cursor4 = vec4(spotlight.cursor_position, 0.0, 1.0); + var texColor = textureSample(t_diffuse, s_diffuse, in.tex_coords); + var shadowColor = vec4(0.0, 0.0, 0.0, 1.0); + var dist_check = length(cursor4 - in.position) < (spotlight.size * spotlight.zoom_level); + var spotlightColor = select(spotlight.strength, 0.0, dist_check); + var finalColor = mix(texColor, shadowColor, spotlightColor); + return finalColor; } diff --git a/src/spotlight.rs b/src/spotlight.rs new file mode 100644 index 0000000..8a564d7 --- /dev/null +++ b/src/spotlight.rs @@ -0,0 +1,124 @@ +use wgpu::util::DeviceExt; +use winit::{ + event::{ElementState, MouseScrollDelta, WindowEvent}, + keyboard::{Key, NamedKey}, +}; + +use crate::controller::CameraController; + +pub struct Spotlight { + cursor_position: (f32, f32), + size: f32, + + is_ctrl_pressed: bool, +} + +pub struct SpotlightInfo { + pub uniform: SpotlightUniform, + pub buffer: wgpu::Buffer, + pub layout: wgpu::BindGroupLayout, + pub bind_group: wgpu::BindGroup, +} + +impl Spotlight { + pub fn new(device: &wgpu::Device) -> (Self, SpotlightInfo) { + let spotlight = Self { + cursor_position: (960.0, 540.0), + size: 50.0, + + is_ctrl_pressed: false, + }; + + let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("spotlight_bind_group_layout"), + }); + + let uniform = SpotlightUniform { + cursor_position: [0.0, 0.0], + size: 50.0, + zoom_level: 1.0, + strength: 0.0, + padding: 0.0, + }; + + let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Spotlight Buffer"), + contents: bytemuck::cast_slice(&[uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + label: Some("spotlight_bind_group"), + }); + + let info = SpotlightInfo { + layout, + bind_group, + buffer, + uniform, + }; + + (spotlight, info) + } + + pub fn process_events(&mut self, event: &WindowEvent) { + match event { + WindowEvent::CursorMoved { position, .. } => { + self.cursor_position = (position.x as f32, position.y as f32); + } + WindowEvent::KeyboardInput { event, .. } => { + self.is_ctrl_pressed = false; + if event.state == ElementState::Pressed { + let key = &event.logical_key; + if let Key::Named(NamedKey::Control) = key { + self.is_ctrl_pressed = true; + } + } + } + WindowEvent::MouseWheel { delta, .. } => { + if let MouseScrollDelta::LineDelta(_, y) = delta { + if self.is_ctrl_pressed { + self.size += y * 5.0; + } + } + } + _ => {} + } + } + + pub fn update(&mut self) {} +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct SpotlightUniform { + cursor_position: [f32; 2], + size: f32, + zoom_level: f32, + strength: f32, + padding: f32, +} + +impl SpotlightUniform { + pub fn update(&mut self, spotlight: &Spotlight, controller: &CameraController) { + self.cursor_position = [spotlight.cursor_position.0, spotlight.cursor_position.1]; + self.size = spotlight.size; + self.zoom_level = controller.zoom_total.max(1.0); + self.strength = if spotlight.is_ctrl_pressed { 0.9 } else { 0.0 }; + } +} diff --git a/src/state.rs b/src/state.rs index 8ee112c..d467d3f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use pollster::FutureExt; -use wgpu::util::DeviceExt; +use wgpu::{util::DeviceExt, DynamicOffset}; use winit::{ event::{ElementState, WindowEvent}, keyboard::Key, @@ -11,6 +11,7 @@ use winit::{ use crate::{ camera::{Camera, CameraInfo}, controller::CameraController, + spotlight::{Spotlight, SpotlightInfo}, texture::Texture, vertex::Vertex, }; @@ -54,9 +55,11 @@ pub struct State<'a> { render_pipeline: wgpu::RenderPipeline, vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, - bind_group: wgpu::BindGroup, + texture_bind_group: wgpu::BindGroup, pub camera: Camera, pub camera_info: CameraInfo, + pub spotlight: Spotlight, + pub spotlight_info: SpotlightInfo, pub zoom: f32, pub camera_controller: CameraController, } @@ -117,6 +120,8 @@ impl<'a> State<'a> { let (camera, camera_info) = Camera::new(&config, &device); let camera_controller = CameraController::new(ZOOM_SPEED, MOVE_SPEED); + let (spotlight, spotlight_info) = Spotlight::new(&device); + let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let texture = Texture::new(image, &device, &queue); @@ -124,7 +129,7 @@ impl<'a> State<'a> { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&texture.layout, &camera_info.layout], + bind_group_layouts: &[&texture.layout, &camera_info.layout, &spotlight_info.layout], push_constant_ranges: &[], }); @@ -192,9 +197,11 @@ impl<'a> State<'a> { render_pipeline, vertex_buffer, index_buffer, - bind_group: texture.group, + texture_bind_group: texture.bind_group, camera, camera_info, + spotlight, + spotlight_info, camera_controller, zoom: 1.0, } @@ -212,6 +219,7 @@ impl<'a> State<'a> { pub fn input(&mut self, event: &winit::event::WindowEvent) -> bool { self.camera_controller.process_events(self.wsize, event); + self.spotlight.process_events(event); match event { WindowEvent::KeyboardInput { event: key_event, .. @@ -232,13 +240,24 @@ impl<'a> State<'a> { } pub fn update(&mut self) { - self.camera_controller.update_camera(&mut self.camera); + self.camera_controller.update(&mut self.camera); + self.camera_info.uniform.update_view_proj(&self.camera); self.queue.write_buffer( &self.camera_info.buffer, 0, bytemuck::cast_slice(&[self.camera_info.uniform]), ); + + self.spotlight.update(); + self.spotlight_info + .uniform + .update(&self.spotlight, &self.camera_controller); + self.queue.write_buffer( + &self.spotlight_info.buffer, + 0, + bytemuck::cast_slice(&[self.spotlight_info.uniform]), + ); } pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { @@ -274,8 +293,9 @@ impl<'a> State<'a> { }); render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.set_bind_group(0, &self.texture_bind_group, &[]); render_pass.set_bind_group(1, &self.camera_info.bind_group, &[]); + render_pass.set_bind_group(2, &self.spotlight_info.bind_group, &[]); render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); diff --git a/src/texture.rs b/src/texture.rs index 63ff89e..e0192ea 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -2,7 +2,7 @@ use image::GenericImageView; pub struct Texture { pub layout: wgpu::BindGroupLayout, - pub group: wgpu::BindGroup, + pub bind_group: wgpu::BindGroup, } impl Texture { @@ -53,31 +53,30 @@ impl Texture { ..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, + let 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 }, }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, + layout: &layout, entries: &[ wgpu::BindGroupEntry { binding: 0, @@ -91,9 +90,6 @@ impl Texture { label: Some("diffuse_bind_group"), }); - Self { - layout: texture_bind_group_layout, - group: bind_group, - } + Self { layout, bind_group } } }