diff --git a/src/main.rs b/src/main.rs index fb795d4..7652097 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,11 +16,12 @@ use winit::{ // Represents a vertex with position and color struct Vertex { position: [f32; 3], // 3D position of the vertex <-- x, y, z - color: [f32; 3], // Color of the vertex in RGB format <-- r, g, b + color: [f32; 3], // Color of the vertex in RGB format <-- r, g, b } impl Vertex { - fn desc() -> wgpu::VertexBufferLayout<'static> { // Returns the vertex buffer layout for the Vertex structure - VertexBufferLayout Specifies an interpretation of the bytes of a vertex buffer as vertex attributes. + fn desc() -> wgpu::VertexBufferLayout<'static> { + // Returns the vertex buffer layout for the Vertex structure - VertexBufferLayout Specifies an interpretation of the bytes of a vertex buffer as vertex attributes. wgpu::VertexBufferLayout { array_stride: std::mem::size_of::() as wgpu::BufferAddress, // The size of a single vertex in bytes step_mode: wgpu::VertexStepMode::Vertex, // Indicates that each vertex has its own attributes if `VertexStepMode::Vertex` is used, or that the attributes are shared across instances if `VertexStepMode::Instance` is used. @@ -71,53 +72,81 @@ struct Ball { position: [f32; 2], velocity: [f32; 2], radius: f32, - color: [f32; 3], // RGB color + color: [f32; 3], // RGB color + manual_control: bool, // Add this field + speed: f32, // Add this field for manual control speed } impl Ball { fn new() -> Self { Self { - position: [0.0, 0.0], // Initial position at the center - velocity: [0.5, 0.5], // Initial velocity <-- x, y - radius: 0.1, // Radius of the ball + position: [0.0, 0.0], // Initial position at the center + velocity: [0.5, 0.5], // Initial velocity <-- x, y + radius: 0.1, // Radius of the ball color: [1.0, 0.5, 0.2], // Orange color + manual_control: false, // Initially not under manual control + speed: 1.0, // Default speed } } fn update(&mut self, dt: f32, window_width: f32, window_height: f32) { - // Update position - self.position[0] += self.velocity[0] * dt; // Update x position - self.position[1] += self.velocity[1] * dt; // Update y position - // Convert window dimensions to normalized coordinates let aspect_ratio = window_width / window_height; let bounds_x = aspect_ratio; let bounds_y = 1.0; // update ball size according to window size - self.radius = 0.1 * (window_width / 800.0); // // Assuming the original window width is 800px + self.radius = 0.1 * (window_width / 800.0); // Assuming the original window width is 800px - // Bounce off walls - if self.position[0] + self.radius > bounds_x || self.position[0] - self.radius < -bounds_x { - self.velocity[0] = -self.velocity[0] + 0.1 * rand::random::(); // Add a small random factor to the velocity - self.position[0] = self.position[0].clamp(-bounds_x + self.radius, bounds_x - self.radius); - // assign a new random color when bouncing off the wall - self.color = [ - rand::random::(), - rand::random::(), - rand::random::(), - ]; + if !self.manual_control { + // Update position for automatic movement + self.position[0] += self.velocity[0] * dt; + self.position[1] += self.velocity[1] * dt; + + // Bounce off walls + if self.position[0] + self.radius > bounds_x + || self.position[0] - self.radius < -bounds_x + { + self.velocity[0] = -self.velocity[0]; + // assign a new random color when bouncing off the wall + self.color = [ + 0.2 + 0.8 * (self.position[0].abs() / bounds_x), + 0.2 + 0.8 * (self.position[1].abs() / bounds_y), + 0.2 + 0.6 * ((self.position[0].abs() + self.position[1].abs()) / (bounds_x + bounds_y)), + ]; + } + // if the y position plus the radius is greater than the bounds_y or the y position minus the radius is less than -bounds_y, then bounce off the wall + if self.position[1] + self.radius > bounds_y + || self.position[1] - self.radius < -bounds_y + { + self.velocity[1] = -self.velocity[1]; + // assign a new random color when bouncing off the wall + self.color = [ + 0.2 + 0.8 * (self.position[0].abs() / bounds_x), + 0.2 + 0.8 * (self.position[1].abs() / bounds_y), + 0.2 + 0.6 * ((self.position[0].abs() + self.position[1].abs()) / (bounds_x + bounds_y)), + ]; + } } - // if the y position plus the radius is greater than the bounds_y or the y position minus the radius is less than -bounds_y, then bounce off the wall - if self.position[1] + self.radius > bounds_y || self.position[1] - self.radius < -bounds_y { - self.velocity[1] = -self.velocity[1] + 0.1 * rand::random::(); // Add a small random factor to the velocity - self.position[1] = self.position[1].clamp(-bounds_y + self.radius, bounds_y - self.radius); - // assign a new random color when bouncing off the wall - self.color = [ - rand::random::(), - rand::random::(), - rand::random::(), - ]; + + // Clamp position to bounds for both manual and automatic control + self.position[0] = self.position[0].clamp(-bounds_x + self.radius, bounds_x - self.radius); + self.position[1] = self.position[1].clamp(-bounds_y + self.radius, bounds_y - self.radius); + } + + fn move_ball(&mut self, direction: [f32; 2], dt: f32) { + if self.manual_control { + self.position[0] += direction[0] * self.speed * dt; + self.position[1] += direction[1] * self.speed * dt; + } + } + + fn toggle_control_mode(&mut self) { + self.manual_control = !self.manual_control; + if self.manual_control { + self.color = [0.2, 1.0, 0.2]; // Green when manually controlled + } else { + self.color = [1.0, 0.5, 0.2]; // Orange when automatic } } } @@ -137,6 +166,7 @@ struct State { uniforms: Uniforms, ball: Ball, last_frame_time: Instant, + movement_direction: [f32; 2], // Add this field } impl State { @@ -160,15 +190,13 @@ impl State { .await?; let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - label: None, - memory_hints: Default::default(), - trace: wgpu::Trace::default(), - } - ) + .request_device(&wgpu::DeviceDescriptor { + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + label: None, + memory_hints: Default::default(), + trace: wgpu::Trace::default(), + }) .await?; let surface_caps = surface.get_capabilities(&adapter); @@ -195,28 +223,28 @@ impl State { // Create circle vertices (for the ball) let mut vertices = Vec::new(); let mut indices = Vec::new(); - + let segments = 32; let radius = 0.1; - + // Center vertex vertices.push(Vertex { position: [0.0, 0.0, 0.0], color: [1.0, 0.2, 0.2], // Orange color }); - + // Circle vertices for i in 0..segments { let angle = 2.0 * std::f32::consts::PI * i as f32 / segments as f32; let x = radius * angle.cos(); let y = radius * angle.sin(); - + vertices.push(Vertex { position: [x, y, 0.0], color: [0.1, 0.5, 0.2], // Orange color }); } - + // Create triangles for i in 0..segments { indices.push(0); // Center @@ -338,6 +366,7 @@ impl State { uniforms, ball, last_frame_time: Instant::now(), + movement_direction: [0.0, 0.0], // Initialize movement direction }) } @@ -355,11 +384,14 @@ impl State { let dt = (now - self.last_frame_time).as_secs_f32(); self.last_frame_time = now; + self.ball.move_ball(self.movement_direction, dt); // Update ball physics - self.ball.update(dt, self.size.width as f32, self.size.height as f32); + self.ball + .update(dt, self.size.width as f32, self.size.height as f32); // Update uniforms - self.uniforms.update_position(self.ball.position[0], self.ball.position[1]); + self.uniforms + .update_position(self.ball.position[0], self.ball.position[1]); self.queue.write_buffer( &self.uniform_buffer, 0, @@ -369,25 +401,48 @@ impl State { self.update_vertex_colors(); } + fn handle_key_input(&mut self, key_code: KeyCode, pressed: bool) { + // Define key mappings: (axis_index, direction_value) + let key_mapping = match key_code { + // Some is a Rust Type Option that is used to indicate a valid mapping + KeyCode::KeyW => Some((1, 1.0)), // Y-axis, positive + KeyCode::KeyS => Some((1, -1.0)), // Y-axis, negative + KeyCode::KeyA => Some((0, -1.0)), // X-axis, negative + KeyCode::KeyD => Some((0, 1.0)), // X-axis, positive + KeyCode::Space if pressed => { + self.ball.toggle_control_mode(); + return; + } + _ => None, + }; + + // Update movement direction based on key input + if let Some((axis, direction)) = key_mapping { + if pressed { + self.movement_direction[axis] = direction; + } else if self.movement_direction[axis].signum() == direction.signum() { + self.movement_direction[axis] = 0.0; + } + } + } + fn update_vertex_colors(&mut self) { let mut vertices = Vec::new(); let segments = 32; // Number of segments for the circle let radius = self.ball.radius; - - // Center vertex with ball's color vertices.push(Vertex { position: [0.0, 0.0, 0.0], color: self.ball.color, }); - + // Circle vertices with ball's color for i in 0..segments { let angle = 2.0 * std::f32::consts::PI * i as f32 / segments as f32; let x = radius * angle.cos(); let y = radius * angle.sin(); - + vertices.push(Vertex { position: [x, y, 0.0], color: self.ball.color, @@ -395,11 +450,13 @@ impl State { } // Update the vertex buffer with new colors - self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(&vertices), - usage: wgpu::BufferUsages::VERTEX, - }); + self.vertex_buffer = self + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); } fn render(&mut self) -> Result<(), wgpu::SurfaceError> { @@ -483,6 +540,18 @@ impl ApplicationHandler for App { }, .. } => event_loop.exit(), + WindowEvent::KeyboardInput { + event: + KeyEvent { + state: key_state, + physical_key: PhysicalKey::Code(key_code), + .. + }, + .. + } => { + let pressed = key_state == ElementState::Pressed; + state.handle_key_input(key_code, pressed); + } WindowEvent::Resized(physical_size) => { state.resize(physical_size); } @@ -494,7 +563,7 @@ impl ApplicationHandler for App { Err(wgpu::SurfaceError::OutOfMemory) => event_loop.exit(), Err(e) => eprintln!("{:?}", e), } - + // Request another frame for continuous animation if let Some(window) = &self.window { window.request_redraw(); @@ -518,7 +587,7 @@ fn main() -> Result<()> { let event_loop = EventLoop::new()?; event_loop.set_control_flow(ControlFlow::Poll); - let mut app = App { + let mut app = App { state: None, window: None, }; @@ -526,4 +595,4 @@ fn main() -> Result<()> { event_loop.run_app(&mut app)?; Ok(()) -} \ No newline at end of file +}