added manual ball controll with keyboard

This commit is contained in:
Elmar Kresse
2025-07-06 20:44:45 +02:00
parent f967a2d8a1
commit c115a1c1ed

View File

@ -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::<Vertex>() 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::<f32>(); // 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::<f32>(),
rand::random::<f32>(),
rand::random::<f32>(),
];
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::<f32>(); // 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::<f32>(),
rand::random::<f32>(),
rand::random::<f32>(),
];
// 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(())
}
}