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