1use std::sync::{Arc, LazyLock, RwLock};
2use strum::IntoEnumIterator;
3use winit;
4
5use math_utils as math;
6use math_utils::num;
7
8use crate::{render, tile, Render};
9use super::*;
10
11static MODIFIERS : LazyLock <Arc <RwLock <winit::keyboard::ModifiersState>>> =
14 LazyLock::new (|| Arc::new (RwLock::new (winit::keyboard::ModifiersState::empty())));
15
16pub struct App {
18 pub render : Render <Default>,
19 pub mouse_position : (f64, f64),
20 pub mouse_button_event : Option <winit::event::ElementState>,
21 pub running : bool
22}
23
24impl App {
25 pub fn new (
26 glium_display : glium::Display <glutin::surface::WindowSurface>,
27 window : winit::window::Window
28 ) -> Self {
29 let mut render = Render::<Default>::new (glium_display, window);
30 render.demo_init();
31 let mouse_position = (0.0, 0.0);
32 let mouse_button_event = None;
33 let running = true;
34 Self { render, mouse_position, mouse_button_event, running }
35 }
36}
37
38impl winit::application::ApplicationHandler for App {
39 fn resumed (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) { }
41
42 fn window_event (&mut self,
43 _event_loop : &winit::event_loop::ActiveEventLoop,
44 _window_id : winit::window::WindowId,
45 event : winit::event::WindowEvent
46 ) {
47 self.render.demo_handle_winit_window_event (
56 event,
57 &mut self.running,
58 &mut self.mouse_position,
59 &mut self.mouse_button_event)
60 }
61}
62
63impl Render <Default> {
64 pub fn modifiers() -> winit::keyboard::ModifiersState {
66 *MODIFIERS.read().unwrap()
67 }
68 fn demo_init (&mut self) {
88 use draw3d::*;
89
90 println!(">>> Initializing gl-utils demo...");
91 println!(" Press 'Q' or 'Esc' to quit");
92 println!(" Horizontal movement: 'W', 'A', 'S', 'D'");
93 println!(" Vertical movement: 'Space', 'LCtrl' or 'R', 'F'");
94 println!(" Rotation: 'Up', 'Down', 'Left', 'Right' or 'H', 'J', 'K', 'L'");
95 println!(" Zoom in/out 3D (perspective): 'I', 'O'");
96 println!(" Zoom in/out 3D (orthographic): 'Alt+I', 'Alt+O'");
97 println!(" Zoom in/out 2D: 'Shift+Alt+I', 'Shift+Alt+O'");
98 println!(" Toggle split into 4 viewports with orthographic views: 'X'");
99 println!(" Screenshot: 'F10'");
100
101 self.resource.draw2d.draw_pointer =
103 Some (DefaultTexturePointerId::Hand as PointerTextureIndexRepr);
104 self.window.set_cursor_visible (false);
106
107 let cube_vertex = [vertex::Vert3dOrientationScaleColor {
112 position: [-1.5, 2.5, 1.0],
113 orientation: math::Matrix3::identity().into_col_arrays(),
114 scale: [std::f32::consts::SQRT_3 / 6.0; 3],
115 color: color::rgba_u8_to_rgba_f32 (color::CYAN)
116 }];
117 let aabb_lines_vertex = [vertex::Vert3dOrientationScaleColor {
119 position: [-0.5, 2.5, 1.0],
120 orientation: math::Matrix3::identity().into_col_arrays(),
121 scale: [0.5, 0.5, 1.0],
122 color: color::rgba_u8_to_rgba_f32 (color::DEBUG_PINK)
123 }];
124 let aabb_triangles_vertex = [vertex::Vert3dOrientationScaleColor {
126 color: color::rgba_u8_to_rgba_f32 (color::DEBUG_GREY),
127 .. aabb_lines_vertex[0]
128 }];
129 let grid_vertex_data = Default::debug_grid_vertices();
131 let hemisphere_vertex = [vertex::Vert3dOrientationScaleColor {
133 position: [2.5, 2.5, 0.0],
134 orientation: math::Matrix3::identity().into_col_arrays(),
135 scale: [0.5, 0.5, 0.5],
136 color: color::rgba_u8_to_rgba_f32 (color::DEBUG_VIOLET)
137 }];
138 let sphere_vertex = [vertex::Vert3dOrientationScaleColor {
141 position: [1.5, 2.5, 0.5],
142 orientation: math::Matrix3::identity().into_col_arrays(),
143 scale: [0.5, 0.5, 0.5],
144 color: color::rgba_u8_to_rgba_f32 (color::DEBUG_RED)
145 }];
146 let capsule_vertex = [vertex::Vert3dOrientationScaleColor {
148 position: [0.5, 2.5, 1.0],
149 orientation: math::Matrix3::identity().into_col_arrays(),
150 scale: [0.5, 0.5, 1.0],
151 color: color::rgba_u8_to_rgba_f32 (color::DEBUG_AZURE)
152 }];
153 let cylinder_vertex = [vertex::Vert3dOrientationScaleColor {
155 position: [3.5, 2.5, 1.0],
156 orientation: math::Matrix3::identity().into_col_arrays(),
157 scale: [0.5, 0.5, 1.0],
158 color: color::rgba_u8_to_rgba_f32 (color::DEBUG_LIGHT_BLUE)
159 }];
160 let aabb_lines = Some (&aabb_lines_vertex[..]);
161 let aabb_triangles = Some (&aabb_triangles_vertex[..]);
162 let aabb_lines_and_triangles = None;
163 let meshes = {
164 let mut v = VecMap::with_capacity (MeshId::COUNT);
165 assert!(v.insert (MeshId::Grid as usize, &grid_vertex_data[..]).is_none());
166 assert!(v.insert (MeshId::Hemisphere as usize, &hemisphere_vertex[..]).is_none());
167 assert!(v.insert (MeshId::Sphere as usize, &sphere_vertex[..]).is_none());
168 assert!(v.insert (MeshId::Capsule as usize, &capsule_vertex[..]).is_none());
169 assert!(v.insert (MeshId::Cube as usize, &cube_vertex[..]).is_none());
170 assert!(v.insert (MeshId::Cylinder as usize, &cylinder_vertex[..]).is_none());
171 v
172 };
173 let billboards = VecMap::new();
176 self.resource.draw3d.instance_vertices_set (
177 &self.glium_display,
178 InstancesInit {
179 aabb_lines, aabb_triangles, aabb_lines_and_triangles, meshes, billboards
180 }
181 );
182 for mesh_id in MeshId::iter() {
184 let key = mesh_id as usize;
185 self.resource.draw3d.tile_billboard_create (
186 &self.glium_display, &self.resource.tileset_128x128_texture,
187 key, self.resource.draw3d.instanced_meshes()[key].instances_range.clone(),
188 format!("{mesh_id:?}").as_str()
189 );
190 }
191 let aabb_billboard_key = MeshId::COUNT;
192 self.resource.draw3d.tile_billboard_create (
193 &self.glium_display, &self.resource.tileset_128x128_texture,
194 aabb_billboard_key, self.resource.draw3d.instanced_aabb_lines().clone(),
196 "Aabb"
197 );
198
199 let (width, height) = self.window.inner_size().into();
200 let [_tile_width, tile_height] =
201 self.resource.tile_dimensions (DefaultTilesetId::EasciiAcorn128);
202 let mut tile_2d_vertices = tile::vertices ("Viewport0", (0, 0));
206 let viewport0_tile_range = 0..tile_2d_vertices.len() as u32;
207 tile_2d_vertices.extend (tile::vertices ("Viewport1: +Y", (0, 0)));
208 let viewport1_tile_range = viewport0_tile_range.end..tile_2d_vertices.len()
209 as u32;
210 tile_2d_vertices.extend (tile::vertices ("Viewport2: +X", (0, 0)));
211 let viewport2_tile_range = viewport1_tile_range.end..tile_2d_vertices.len()
212 as u32;
213 tile_2d_vertices.extend (tile::vertices ("Viewport3: -Z", (0, 0)));
214 let viewport3_tile_range = viewport2_tile_range.end..tile_2d_vertices.len()
215 as u32;
216 let fps_row = ((height / tile_height) - 1) as i32;
218 tile_2d_vertices.extend (tile::vertices ("FPS: 0 ", (fps_row, 0)));
219 let viewport4_tile_range = viewport3_tile_range.end..tile_2d_vertices.len()
220 as u32;
221 self.resource.draw2d.tile_2d_vertices = glium::VertexBuffer::dynamic (
222 &self.glium_display, &tile_2d_vertices[..]
223 ).unwrap();
224 let tile_color_2d_vertices = {
225 let tiles = tile::vertices ("abc123", (2, 0));
226 let colors = vec![
227 ( [1.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0] ),
228 ( [0.0, 1.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0] ),
229 ( [0.0, 0.0, 1.0, 1.0], [0.0, 0.0, 0.0, 0.0] ),
230 ( [0.0, 0.0, 0.0, 0.0], [1.0, 1.0, 0.0, 1.0] ),
231 ( [0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 1.0, 1.0] ),
232 ( [0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 1.0, 1.0] )
233 ];
234 tiles.into_iter().zip (colors).map (
235 |(vertex::Vert2dTile { tile, row, column }, (fg, bg))|
236 vertex::Vert2dTileColor { tile, row, column, fg, bg }
237 ).collect::<Vec <_>>()
238 };
239 let viewport0_tile_color_range = 0..tile_color_2d_vertices.len() as u32;
240 self.resource.draw2d.tile_color_2d_vertices = glium::VertexBuffer::dynamic (
241 &self.glium_display, &tile_color_2d_vertices[..]
242 ).unwrap();
243 let draw_tiles = draw2d::Tiles {
244 vertex_range: 0..0, origin: (2, 2).into(),
245 tileset_id: DefaultTilesetId::EasciiAcorn128
246 };
247 let draw_tiles_color = draw2d::Tiles {
248 vertex_range: 0..0, origin: draw2d::TilesOrigin::World,
249 tileset_id: DefaultTilesetId::EasciiAcorn256
250 };
251 self.resource.draw2d.viewport_resources_set (MAIN_VIEWPORT,
252 draw2d::ViewportResources {
253 draw_indices: vec![draw2d::DrawIndices {
254 rectangle: None,
255 draw_tiles: Some (draw2d::Tiles {
256 vertex_range: viewport0_tile_range,
257 .. draw_tiles.clone()
258 }),
259 draw_color_tiles: Some (draw2d::Tiles {
260 vertex_range: viewport0_tile_color_range,
261 .. draw_tiles_color
262 })
263 }],
264 .. draw2d::ViewportResources::default()
265 }
266 );
267 self.resource.draw2d.viewport_resources_set (UPPER_LEFT_VIEWPORT,
268 draw2d::ViewportResources {
269 draw_indices: vec![draw2d::DrawIndices {
270 draw_tiles: Some (draw2d::Tiles {
271 vertex_range: viewport1_tile_range,
272 .. draw_tiles.clone()
273 }),
274 .. draw2d::DrawIndices::default()
275 }],
276 .. draw2d::ViewportResources::default()
277 }
278 );
279 self.resource.draw2d.viewport_resources_set (UPPER_RIGHT_VIEWPORT,
280 draw2d::ViewportResources {
281 draw_indices: vec![draw2d::DrawIndices {
282 draw_tiles: Some (draw2d::Tiles {
283 vertex_range: viewport2_tile_range,
284 .. draw_tiles.clone()
285 }),
286 .. draw2d::DrawIndices::default()
287 }],
288 .. draw2d::ViewportResources::default()
289 }
290 );
291 self.resource.draw2d.viewport_resources_set (LOWER_LEFT_VIEWPORT,
292 draw2d::ViewportResources {
293 draw_indices: vec![draw2d::DrawIndices {
294 draw_tiles: Some (draw2d::Tiles {
295 vertex_range: viewport3_tile_range,
296 .. draw_tiles.clone()
297 }),
298 .. draw2d::DrawIndices::default()
299 }],
300 .. draw2d::ViewportResources::default()
301 }
302 );
303 self.resource.draw2d.viewport_resources_set (OVERLAY_VIEWPORT,
304 draw2d::ViewportResources {
305 draw_indices: vec![draw2d::DrawIndices {
306 draw_tiles: Some (draw2d::Tiles {
307 vertex_range: viewport4_tile_range,
308 .. draw_tiles
309 }),
310 .. draw2d::DrawIndices::default()
311 }],
312 draw_lineloop: false,
313 draw_crosshair: false
314 }
315 );
316 let overlay = render::viewport::Builder::new (
318 glium::Rect { width, height, left: 0, bottom: 0 }
319 ).with_camera_3d (false).build();
320 assert!(self.viewports.insert (OVERLAY_VIEWPORT, overlay).is_none());
321 }
322
323 pub fn demo_handle_winit_window_event (&mut self,
324 event : winit::event::WindowEvent,
325 running : &mut bool,
326 mouse_position : &mut (f64, f64),
327 mouse_button_event : &mut Option <winit::event::ElementState>
328 ) {
329 use std::f32::consts::PI;
330 use winit::{event, keyboard};
331 use num::Zero;
332 log::debug!("demo handle winit window event: {event:?}");
333 match event {
334 event::WindowEvent::Resized (physical_size) => {
336 log::debug!("window event: {event:?}");
337 self.window_resized (physical_size);
338 }
339 event::WindowEvent::CloseRequested => { *running = false; }
341 #[expect(clippy::match_same_arms)]
344 event::WindowEvent::Destroyed => { }
345 event::WindowEvent::ModifiersChanged (modifiers) =>
347 *MODIFIERS.write().unwrap() = modifiers.state(),
348 event::WindowEvent::KeyboardInput {
350 event: event::KeyEvent {
351 state: event::ElementState::Pressed,
352 physical_key, logical_key, location, ..
353 },
354 ..
355 } => match (logical_key, location) {
356 ( keyboard::Key::Named (keyboard::NamedKey::Control),
357 keyboard::KeyLocation::Left
358 ) =>
359 if Self::modifiers().shift_key() {
360 self.camera3d_move_local_xy (0.0, 0.0, -0.5)
361 } else {
362 self.camera3d_move_local_xy (0.0, 0.0, -1.0)
363 }
364 _ => match physical_key {
365 keyboard::PhysicalKey::Code (key_code) => match key_code {
366 keyboard::KeyCode::KeyQ | keyboard::KeyCode::Escape =>
368 *running = false,
369 keyboard::KeyCode::Digit1 =>
371 self.frame_fun = render::frame_fun_default::<Default>,
372 keyboard::KeyCode::KeyW =>
376 if Self::modifiers().shift_key() {
377 self.camera3d_move_local_xy (0.0, 0.5, 0.0)
378 } else {
379 self.camera3d_move_local_xy (0.0, 1.0, 0.0)
380 }
381 keyboard::KeyCode::KeyS =>
382 if Self::modifiers().shift_key() {
383 self.camera3d_move_local_xy (0.0, -0.5, 0.0)
384 } else {
385 self.camera3d_move_local_xy (0.0, -1.0, 0.0)
386 }
387 keyboard::KeyCode::KeyD =>
388 if Self::modifiers().shift_key() {
389 self.camera3d_move_local_xy (0.5, 0.0, 0.0)
390 } else {
391 self.camera3d_move_local_xy (1.0, 0.0, 0.0)
392 }
393 keyboard::KeyCode::KeyA =>
394 if Self::modifiers().shift_key() {
395 self.camera3d_move_local_xy (-0.5, 0.0, 0.0)
396 } else {
397 self.camera3d_move_local_xy (-1.0, 0.0, 0.0)
398 }
399 keyboard::KeyCode::Space | keyboard::KeyCode::KeyR =>
400 if Self::modifiers().shift_key() {
401 self.camera3d_move_local_xy (0.0, 0.0, 0.5)
402 } else {
403 self.camera3d_move_local_xy (0.0, 0.0, 1.0)
404 }
405 keyboard::KeyCode::KeyF =>
406 if Self::modifiers().shift_key() {
407 self.camera3d_move_local_xy (0.0, 0.0, -0.5)
408 } else {
409 self.camera3d_move_local_xy (0.0, 0.0, -1.0)
410 }
411 keyboard::KeyCode::KeyJ | keyboard::KeyCode::ArrowDown => {
412 let modifiers = Self::modifiers();
413 if modifiers.alt_key() && modifiers.shift_key() {
414 self.camera2d_move_local (0.0, -1.0);
415 } else {
416 self.camera3d_rotate (
417 math::Rad::zero(),
418 math::Rad (-PI / 12.0),
419 math::Rad::zero());
420 }
421 }
422 keyboard::KeyCode::KeyK | keyboard::KeyCode::ArrowUp => {
423 let modifiers = Self::modifiers();
424 if modifiers.alt_key() && modifiers.shift_key() {
425 self.camera2d_move_local (0.0, 1.0);
426 } else {
427 self.camera3d_rotate (
428 math::Rad::zero(),
429 math::Rad (PI / 12.0),
430 math::Rad::zero());
431 }
432 }
433 keyboard::KeyCode::KeyH | keyboard::KeyCode::ArrowLeft => {
434 let modifiers = Self::modifiers();
435 if modifiers.alt_key() && modifiers.shift_key() {
436 self.camera2d_move_local (-1.0, 0.0);
437 } else {
438 self.camera3d_rotate (
439 math::Rad (PI / 12.0),
440 math::Rad::zero(),
441 math::Rad::zero());
442 }
443 }
444 keyboard::KeyCode::KeyL | keyboard::KeyCode::ArrowRight => {
445 let modifiers = Self::modifiers();
446 if modifiers.alt_key() && modifiers.shift_key() {
447 self.camera2d_move_local (1.0, 0.0);
448 } else {
449 self.camera3d_rotate (
450 math::Rad (-PI / 12.0),
451 math::Rad::zero(),
452 math::Rad::zero());
453 }
454 }
455 keyboard::KeyCode::KeyI => {
456 let modifiers = Self::modifiers();
457 if modifiers.alt_key() && modifiers.shift_key() {
458 self.camera2d_zoom_shift (1.0);
459 } else if modifiers.alt_key() {
460 self.camera3d_orthographic_zoom_scale (1.1);
461 } else {
462 self.camera3d_perspective_fovy_scale (1.0 / 1.1);
463 }
464 }
465 keyboard::KeyCode::KeyO => {
466 let modifiers = Self::modifiers();
467 if modifiers.alt_key() && modifiers.shift_key() {
468 self.camera2d_zoom_shift (-1.0);
469 } else if modifiers.alt_key() {
470 self.camera3d_orthographic_zoom_scale (1.0 / 1.1);
471 } else {
472 self.camera3d_perspective_fovy_scale (1.1);
473 }
474 }
475 keyboard::KeyCode::KeyX => self.demo_toggle_quad_viewports(),
477 keyboard::KeyCode::F10 | keyboard::KeyCode::PrintScreen =>
479 self.screenshot(),
480 _ => {}
481 }
482 keyboard::PhysicalKey::Unidentified (_) => {}
483 }
484 } event::WindowEvent::MouseInput { button: event::MouseButton::Left, state, .. } =>
486 *mouse_button_event = Some (state),
487 event::WindowEvent::CursorMoved { position, .. } => {
488 if self.resource.draw2d.draw_pointer.is_some() {
489 let (_, height) = self.glium_display.get_framebuffer_dimensions();
490 let x = position.x as f32;
491 let y = height as f32 - position.y as f32;
492 self.resource
493 .set_pointer_position (&self.glium_display, [x, y].into());
494 }
495 *mouse_position = (position.x, position.y);
496 }
497 _ => {}
498 }
499 }
500
501 fn demo_toggle_quad_viewports (&mut self) {
516 if self.viewports.len() <= 2 {
517 let (
520 left_width, right_width, upper_height, lower_height, position,
521 position_2d, zoom_2d
522 ) = {
523 let lower_right = &mut self.viewports[MAIN_VIEWPORT];
524 let window_width = lower_right.rect().width;
525 let window_height = lower_right.rect().height;
526 let left_width = left_width (window_width);
527 let right_width = right_width (window_width);
528 let upper_height = upper_height (window_height);
529 let lower_height = lower_height (window_height);
530 lower_right.set_rect (glium::Rect {
531 width: right_width,
532 height: lower_height,
533 left: left_width,
534 bottom: 0
535 });
536 ( left_width, right_width, upper_height, lower_height,
537 lower_right.camera3d().unwrap().position(),
538 lower_right.camera2d().unwrap().position(),
539 lower_right.camera2d().unwrap().zoom()
540 )
541 };
542
543 let upper_left = render::viewport::Builder::new (glium::Rect {
544 width: left_width,
545 height: upper_height,
546 left: 0,
547 bottom: lower_height
548 }).with_zoom_2d (zoom_2d).with_position_2d (position_2d)
549 .orthographic_3d (1.0)
550 .with_pose_3d (
551 math::Pose3 { position, angles: math::Angles3::default() })
553 .build();
554
555 let upper_right = render::viewport::Builder::new (glium::Rect {
556 width: right_width,
557 height: upper_height,
558 left: left_width,
559 bottom: lower_height,
560 }).with_zoom_2d (zoom_2d).with_position_2d (position_2d)
561 .orthographic_3d (1.0)
562 .with_pose_3d (
563 math::Pose3 {
564 position,
565 angles: math::Angles3::wrap (
567 math::Deg (-90.0).into(),
568 math::Rad (0.0),
569 math::Rad (0.0))
570 })
571 .build();
572
573 let lower_left = render::viewport::Builder::new (glium::Rect {
574 width: left_width,
575 height: lower_height,
576 left: 0,
577 bottom: 0,
578 }).with_zoom_2d (zoom_2d).with_position_2d (position_2d)
579 .orthographic_3d (1.0)
580 .with_pose_3d (
581 math::Pose3 {
582 position,
583 angles: math::Angles3::wrap (
585 math::Rad (0.0),
586 math::Deg (-90.0).into(),
587 math::Rad (0.0))
588 })
589 .build();
590
591 assert!(self.viewports.insert (UPPER_LEFT_VIEWPORT, upper_left)
592 .is_none());
593 assert!(self.viewports.insert (UPPER_RIGHT_VIEWPORT, upper_right)
594 .is_none());
595 assert!(self.viewports.insert (LOWER_LEFT_VIEWPORT, lower_left)
596 .is_none());
597 } else {
598 assert!(self.viewports.len() == 4 || self.viewports.len() == 5);
600 assert!(self.viewports.remove (UPPER_LEFT_VIEWPORT) .is_some());
601 assert!(self.viewports.remove (UPPER_RIGHT_VIEWPORT).is_some());
602 assert!(self.viewports.remove (LOWER_LEFT_VIEWPORT) .is_some());
603 let main_viewport = &mut self.viewports[MAIN_VIEWPORT];
604 let (width, height) = self.window.inner_size().into();
605 main_viewport.set_rect (glium::Rect {
606 width,
607 height,
608 left: 0,
609 bottom: 0
610 });
611 }
612 self.update_viewport_line_loops();
613 } }