diff --git a/simulator/Cargo.toml b/simulator/Cargo.toml index fb549be..2949409 100644 --- a/simulator/Cargo.toml +++ b/simulator/Cargo.toml @@ -4,4 +4,11 @@ version = "0.1.0" edition = "2021" [dependencies] -solar_engine = { path = "../solar_engine" } \ No newline at end of file +solar_engine = { path = "../solar_engine" } +pixels = "0.15.0" +winit = "0.30.10" +log = "0.4" +env_logger = "0.11.8" +bytemuck = "1.23.0" +wgpu = "25.0" +pollster = "0.4.0" \ No newline at end of file diff --git a/simulator/src/main.rs b/simulator/src/main.rs index ab168c9..3f9cb51 100644 --- a/simulator/src/main.rs +++ b/simulator/src/main.rs @@ -1,10 +1,193 @@ -use solar_engine::Body; +use std::sync::Arc; + +use pollster::FutureExt; +use wgpu::{Adapter, Device, Instance, PresentMode, Queue, Surface, SurfaceCapabilities}; +use winit::application::ApplicationHandler; +use winit::dpi::PhysicalSize; +use winit::event::WindowEvent; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::window::{Window, WindowId}; + +pub async fn run() { + let event_loop = EventLoop::new().unwrap(); + + let mut window_state = StateApplication::new(); + let _ = event_loop.run_app(&mut window_state); +} + +struct StateApplication<'a> { + state: Option>, +} + +impl<'a> StateApplication<'a> { + pub fn new() -> Self { + Self { + state: None, + } + } +} + +impl<'a> ApplicationHandler for StateApplication<'a>{ + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window = event_loop.create_window(Window::default_attributes().with_title("Hello!")).unwrap(); + self.state = Some(State::new(window)); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { + let window = self.state.as_ref().unwrap().window(); + + if window.id() == window_id { + match event { + WindowEvent::CloseRequested => { + event_loop.exit(); + } + WindowEvent::Resized(physical_size) => { + self.state.as_mut().unwrap().resize(physical_size); + } + WindowEvent::RedrawRequested => { + self.state.as_mut().unwrap().render().unwrap(); + } + _ => {} + } + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + let window = self.state.as_ref().unwrap().window(); + window.request_redraw(); + } +} + +struct State<'a> { + surface: Surface<'a>, + device: Device, + queue: Queue, + config: wgpu::SurfaceConfiguration, + + size: PhysicalSize, + window: Arc, +} + +impl<'a> State<'a> { + pub fn new (window: Window) -> Self { + let window_arc = Arc::new(window); + let size = window_arc.inner_size(); + let instance = Self::create_gpu_instance(); + let surface = instance.create_surface(window_arc.clone()).unwrap(); + let adapter = Self::create_adapter(instance, &surface); + let (device, queue) = Self::create_device(&adapter); + let surface_caps = surface.get_capabilities(&adapter); + let config = Self::create_surface_config(size, surface_caps); + surface.configure(&device, &config); + + Self { + surface, + device, + queue, + config, + size, + window: window_arc, + } + } + + fn create_surface_config(size: PhysicalSize, capabilities: SurfaceCapabilities) -> wgpu::SurfaceConfiguration { + let surface_format = capabilities.formats.iter() + .find(|f| f.is_srgb()) + .copied() + .unwrap_or(capabilities.formats[0]); + + wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: PresentMode::AutoNoVsync, + alpha_mode: capabilities.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + } + } + + fn create_device(adapter: &Adapter) -> (Device, Queue) { + adapter.request_device( + &wgpu::DeviceDescriptor { + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + memory_hints: Default::default(), + label: None, + trace: Default::default(), + }).block_on().unwrap() + } + + fn create_adapter(instance: Instance, surface: &Surface) -> Adapter { + instance.request_adapter( + &wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + } + ).block_on().unwrap() + } + + fn create_gpu_instance() -> Instance { + Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }) + } + + pub fn resize(&mut self, new_size: PhysicalSize) { + self.size = new_size; + + self.config.width = new_size.width; + self.config.height = new_size.height; + + self.surface.configure(&self.device, &self.config); + + println!("Resized to {:?} from state!", new_size); + } + + pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + let output = self.surface.get_current_texture().unwrap(); + let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + { + let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 1.0, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + } + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + } + + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + } + + pub fn window(&self) -> &Window { + &self.window + } +} fn main() { - println!("Hello, world!"); - let earth = Body::new("Earth", 5.972e24, [1.496e11, 0.0], [0.0, 0.0]); - println!("Created body: {:?}", earth); - - let sun = Body::new("Sun", 1.989e30, [0.0, 0.0], [0.0, 0.0]); - println!("Created body: {:?}", sun); -} + pollster::block_on(run()); +} \ No newline at end of file diff --git a/simulator/src/shader.wgsl b/simulator/src/shader.wgsl new file mode 100644 index 0000000..7b23763 --- /dev/null +++ b/simulator/src/shader.wgsl @@ -0,0 +1,22 @@ +struct VertexInput { + @location(0) position: vec2, + @location(1) color: vec3, +}; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec3, +}; + +@vertex +fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = vec4(input.position, 0.0, 1.0); + output.color = input.color; + return output; +} + +@fragment +fn fs_main(input: VertexOutput) -> @location(0) vec4 { + return vec4(input.color, 1.0); +} diff --git a/solar_engine/src/lib.rs b/solar_engine/src/lib.rs index 9eb7368..96c2a8b 100644 --- a/solar_engine/src/lib.rs +++ b/solar_engine/src/lib.rs @@ -1,3 +1,6 @@ mod body; -pub use body::Body; +mod simulator; +pub use body::Body; +pub use simulator::Simulator; +pub use simulator::distance_squared; \ No newline at end of file diff --git a/solar_engine/src/simulator.rs b/solar_engine/src/simulator.rs new file mode 100644 index 0000000..46cf029 --- /dev/null +++ b/solar_engine/src/simulator.rs @@ -0,0 +1,77 @@ +use crate::body::Body; + +const G: f64 = 6.67430e-11; + +pub struct Simulator { + pub bodies: Vec, + pub time: f64, + pub timestep: f64, +} + +pub fn distance_squared(a: [f64; 2], b: [f64; 2]) -> f64 { + let dx = a[0] - b[0]; + let dy = a[1] - b[1]; + dx * dx + dy * dy +} + +impl Simulator { + pub fn new(timestep: f64) -> Self { + Self { + bodies: Vec::new(), + time: 0.0, + timestep, + } + } + + pub fn add_body(&mut self, body: Body) { + self.bodies.push(body); + } + + pub fn step(&mut self) { + let n = self.bodies.len(); + let dt = self.timestep; + + let mut accelerations = vec![[0.0, 0.0]; n]; + + for i in 0..n { + for j in 0..n { + if i == j { + continue; + } + + let (bi, bj) = (&self.bodies[i], &self.bodies[j]); + + let dx = bj.position[0] - bi.position[0]; + let dy = bj.position[1] - bi.position[1]; + let dist_sq = distance_squared(bi.position, bj.position); + let dist = dist_sq.sqrt(); + + if dist < 1e-3 { + continue; + } + + let force = G * bi.mass * bj.mass / dist_sq; + let accel = force / bi.mass; + + let ax = accel * dx / dist; + let ay = accel * dy / dist; + + accelerations[i][0] += ax; + accelerations[i][1] += ay; + } + } + + for i in 0..n { + let a = accelerations[i]; + let body = &mut self.bodies[i]; + + body.velocity[0] += a[0] * dt; + body.velocity[1] += a[1] * dt; + + body.position[0] += body.velocity[0] * dt; + body.position[1] += body.velocity[1] * dt; + } + + self.time += dt; + } +}