1use crate::canvas::*;
2use std::sync::Arc;
3use wgpu::{BindGroup, BindGroupLayout, Color, CommandEncoder, TextureView, util::DeviceExt};
4use winit::{
5 dpi::{PhysicalPosition, PhysicalSize},
6 event::WindowEvent,
7 event_loop::ActiveEventLoop,
8 window::{Window, WindowId},
9};
10
11pub mod complex_polynomial;
12pub mod plottable;
13pub mod shapes;
14pub mod test_pentagon;
15
16#[repr(C)]
17#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
18pub struct CameraUniform {
19 pub matrix: [[f32; 2]; 2],
20 pub matrix_inv: [[f32; 2]; 2],
21 pub shift: [f32; 2],
22}
23
24fn mat2x2inv([[a, b], [c, d]]: [[f32; 2]; 2]) -> [[f32; 2]; 2] {
25 let det = a * d - b * c;
26 [[d / det, -c / det], [-b / det, a / det]]
27}
28
29pub trait Camera {
30 fn get_uniform(&self, display_size: PhysicalSize<u32>) -> CameraUniform;
31
32 fn window_event(
33 &mut self,
34 display_size: PhysicalSize<u32>,
35 mouse_pos: PhysicalPosition<f64>,
36 event_loop: &ActiveEventLoop,
37 id: WindowId,
38 event: &WindowEvent,
39 );
40
41 fn pixel_to_wgpu(
42 &self,
43 display_size: PhysicalSize<u32>,
44 pixels: PhysicalPosition<f64>,
45 ) -> (f64, f64) {
46 (
47 2.0 * pixels.x / display_size.width as f64 - 1.0,
48 -2.0 * pixels.y / display_size.height as f64 + 1.0,
49 )
50 }
51
52 fn wgpu_to_pixel(
53 &self,
54 display_size: PhysicalSize<u32>,
55 wgpu: (f64, f64),
56 ) -> PhysicalPosition<f64> {
57 PhysicalPosition::new(
58 display_size.width as f64 * (wgpu.0 + 1.0) / 2.0,
59 display_size.height as f64 * (-wgpu.1 + 1.0) / 2.0,
60 )
61 }
62
63 fn wgpu_to_coord(&self, display_size: PhysicalSize<u32>, wgpu: (f64, f64)) -> (f64, f64) {
64 let uniform = self.get_uniform(display_size);
65 let [[a, b], [c, d]] = mat2x2inv(uniform.matrix);
66 let v = (
67 wgpu.0 - uniform.shift[0] as f64,
68 wgpu.1 - uniform.shift[1] as f64,
69 );
70 (
71 a as f64 * v.0 + b as f64 * v.1,
72 c as f64 * v.0 + d as f64 * v.1,
73 )
74 }
75
76 fn coord_to_wgpu(&self, display_size: PhysicalSize<u32>, coord: (f64, f64)) -> (f64, f64) {
77 let uniform = self.get_uniform(display_size);
78 let [[a, b], [c, d]] = uniform.matrix;
79 let [x, y] = uniform.shift;
80 let v = coord;
81 (
82 a as f64 * v.0 + b as f64 * v.1 + x as f64,
83 c as f64 * v.0 + d as f64 * v.1 + y as f64,
84 )
85 }
86
87 fn pixel_to_coord(
88 &self,
89 display_size: PhysicalSize<u32>,
90 pixels: PhysicalPosition<f64>,
91 ) -> (f64, f64) {
92 self.wgpu_to_coord(display_size, self.pixel_to_wgpu(display_size, pixels))
93 }
94
95 fn coord_to_pixel(
96 &self,
97 display_size: PhysicalSize<u32>,
98 coord: (f64, f64),
99 ) -> PhysicalPosition<f64> {
100 self.wgpu_to_pixel(display_size, self.coord_to_wgpu(display_size, coord))
101 }
102}
103
104pub struct MouseWheelZoomCamera {
105 mid_x: f64,
107 mid_y: f64,
109 sqrt_area: f64,
111}
112
113#[allow(clippy::new_without_default)]
114impl MouseWheelZoomCamera {
115 pub fn new() -> Self {
116 Self {
117 mid_x: 0.0,
118 mid_y: 0.0,
119 sqrt_area: 6.0,
120 }
121 }
122}
123
124impl Camera for MouseWheelZoomCamera {
125 fn get_uniform(&self, display_size: PhysicalSize<u32>) -> CameraUniform {
126 let display_size = (display_size.width as f32, display_size.height as f32);
127 let avg_side = (display_size.0 * display_size.1).sqrt();
128 let x_mult = 2.0 * avg_side / (display_size.0 * self.sqrt_area as f32);
129 let y_mult = 2.0 * avg_side / (display_size.1 * self.sqrt_area as f32);
130 let matrix = [[x_mult, 0.0], [0.0, y_mult]];
131 let matrix_inv = mat2x2inv(matrix);
132 CameraUniform {
133 matrix,
134 matrix_inv,
135 shift: [-self.mid_x as f32 * x_mult, -self.mid_y as f32 * y_mult],
136 }
137 }
138
139 fn window_event(
140 &mut self,
141 display_size: PhysicalSize<u32>,
142 mouse_pos: PhysicalPosition<f64>,
143 _event_loop: &ActiveEventLoop,
144 _id: WindowId,
145 event: &WindowEvent,
146 ) {
147 #[allow(clippy::single_match)]
148 match event {
149 WindowEvent::MouseWheel { delta, .. } => {
150 let dy = match delta {
151 winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y,
152 winit::event::MouseScrollDelta::LineDelta(_x, y) => *y as f64,
153 };
154 self.zoom_event(display_size, mouse_pos, 0.8f64.powf(dy));
155 }
156 _ => {}
157 }
158 }
159}
160
161impl MouseWheelZoomCamera {
162 fn zoom_event(
163 &mut self,
164 display_size: PhysicalSize<u32>,
165 center: PhysicalPosition<f64>,
166 mult: f64,
167 ) {
168 let center_before = self.pixel_to_coord(display_size, center);
170 self.sqrt_area *= mult;
171 let center_after = self.pixel_to_coord(display_size, center);
172 self.mid_x += center_before.0 - center_after.0;
173 self.mid_y += center_before.1 - center_after.1;
174 }
175}
176
177pub struct Canvas2D {
178 mouse_pos: PhysicalPosition<f64>,
179 items: Vec<Box<dyn Canvas2DItem>>,
180 camera: Box<dyn Camera>,
181}
182
183pub trait Canvas2DItemWgpu {
184 fn render(
185 &mut self,
186 encoder: &mut CommandEncoder,
187 view: &TextureView,
188 camera_bind_group: &BindGroup,
189 ) -> Result<(), wgpu::SurfaceError>;
190}
191
192#[allow(clippy::new_without_default)]
193pub trait Canvas2DItem {
194 fn new_wgpu(
195 &self,
196 wgpu_state: &WgpuState,
197 camera_bind_group_layout: &BindGroupLayout,
198 ) -> Box<dyn Canvas2DItemWgpu>;
199}
200
201pub struct Canvas2DWindowState {
202 wgpu_state: WgpuState,
203
204 camera_uniform: CameraUniform,
205 camera_buffer: wgpu::Buffer,
206 camera_bind_group_layout: wgpu::BindGroupLayout,
207 camera_bind_group: wgpu::BindGroup,
208
209 items: Vec<Box<dyn Canvas2DItemWgpu>>,
210}
211
212impl Canvas2DWindowState {
213 fn new(window: Arc<Window>) -> Self {
214 let wgpu_state = WgpuState::new(window);
215
216 let camera_uniform = CameraUniform {
217 matrix: [[1.0, 0.0], [0.0, 1.0]],
218 matrix_inv: [[1.0, 0.0], [0.0, 1.0]],
219 shift: [0.0, 0.0],
220 };
221
222 let camera_buffer =
223 wgpu_state
224 .device
225 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
226 label: Some("Camera Buffer"),
227 contents: bytemuck::cast_slice(&[camera_uniform]),
228 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
229 });
230
231 let camera_bind_group_layout =
232 wgpu_state
233 .device
234 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
235 entries: &[wgpu::BindGroupLayoutEntry {
236 binding: 0,
237 visibility: wgpu::ShaderStages::VERTEX,
238 ty: wgpu::BindingType::Buffer {
239 ty: wgpu::BufferBindingType::Uniform,
240 has_dynamic_offset: false,
241 min_binding_size: None,
242 },
243 count: None,
244 }],
245 label: Some("camera_bind_group_layout"),
246 });
247
248 let camera_bind_group = wgpu_state
249 .device
250 .create_bind_group(&wgpu::BindGroupDescriptor {
251 layout: &camera_bind_group_layout,
252 entries: &[wgpu::BindGroupEntry {
253 binding: 0,
254 resource: camera_buffer.as_entire_binding(),
255 }],
256 label: Some("camera_bind_group"),
257 });
258
259 Self {
260 items: vec![],
261 wgpu_state,
262 camera_uniform,
263 camera_bind_group,
264 camera_buffer,
265 camera_bind_group_layout,
266 }
267 }
268
269 fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
270 self.wgpu_state.queue.write_buffer(
271 &self.camera_buffer,
272 0,
273 bytemuck::cast_slice(&[self.camera_uniform]),
274 );
275
276 let output = self.wgpu_state.surface.get_current_texture()?;
277 let view = output
278 .texture
279 .create_view(&wgpu::TextureViewDescriptor::default());
280 let mut encoder =
281 self.wgpu_state
282 .device
283 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
284 label: Some("Render Encoder"),
285 });
286
287 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
288 label: Some("clear-pass"),
289 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
290 view: &view,
291 resolve_target: None,
292 ops: wgpu::Operations {
293 load: wgpu::LoadOp::Clear(Color::WHITE),
294 store: wgpu::StoreOp::Store,
295 },
296 })],
297 depth_stencil_attachment: None,
298 timestamp_writes: None,
299 occlusion_query_set: None,
300 });
301
302 for item in &mut self.items {
303 item.render(&mut encoder, &view, &self.camera_bind_group)?;
304 }
305
306 self.wgpu_state
308 .queue
309 .submit(std::iter::once(encoder.finish()));
310 output.present();
311
312 Ok(())
313 }
314}
315
316impl Canvas for Canvas2D {
317 type WindowState = Canvas2DWindowState;
318
319 fn new_window_state(&self, window: Arc<Window>) -> Self::WindowState {
320 let mut state = Canvas2DWindowState::new(window);
321 for item in &self.items {
322 state
323 .items
324 .push(item.new_wgpu(&state.wgpu_state, &state.camera_bind_group_layout));
325 }
326 state
327 }
328
329 fn window_event(
330 &mut self,
331 window_state: &mut Self::WindowState,
332 event_loop: &ActiveEventLoop,
333 id: WindowId,
334 event: WindowEvent,
335 ) {
336 self.camera.window_event(
337 window_state.wgpu_state.size,
338 self.mouse_pos,
339 event_loop,
340 id,
341 &event,
342 );
343
344 #[allow(clippy::single_match)]
345 match event {
346 WindowEvent::CloseRequested => {
347 event_loop.exit();
348 }
349 WindowEvent::Resized(physical_size) => {
350 window_state.wgpu_state.resize(physical_size);
351 }
352 WindowEvent::RedrawRequested => {
353 window_state.camera_uniform = self.camera.get_uniform(window_state.wgpu_state.size);
354 match window_state.render() {
355 Ok(()) => {}
356 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
358 window_state.wgpu_state.reconfigure();
359 }
360 Err(wgpu::SurfaceError::OutOfMemory | wgpu::SurfaceError::Other) => {
362 log::error!("OutOfMemory");
363 event_loop.exit();
364 }
365
366 Err(wgpu::SurfaceError::Timeout) => {
368 log::warn!("Surface timeout")
369 }
370 }
371 window_state.wgpu_state.window.request_redraw();
372 }
373 WindowEvent::CursorMoved { position, .. } => {
374 self.mouse_pos = position;
375 }
376 WindowEvent::MouseInput { state, button, .. } => match (button, state) {
377 (winit::event::MouseButton::Left, winit::event::ElementState::Pressed) => {
378 println!(
379 "{:?} -> {:?} -> {:?}",
380 self.mouse_pos,
381 self.camera
382 .pixel_to_coord(window_state.wgpu_state.size, self.mouse_pos),
383 self.camera.coord_to_pixel(
384 window_state.wgpu_state.size,
385 self.camera
386 .pixel_to_coord(window_state.wgpu_state.size, self.mouse_pos)
387 )
388 );
389 }
390 _ => {}
391 },
392 _ => (),
393 }
394 }
395}
396
397impl Canvas2D {
398 pub fn new(camera: Box<dyn Camera>) -> Self {
399 Self {
400 mouse_pos: PhysicalPosition::new(0.0, 0.0),
401 items: vec![],
402 camera,
403 }
404 }
405
406 pub fn add_item(&mut self, item: impl Canvas2DItem + 'static) {
407 self.items.push(Box::new(item));
408 }
409}