1use std::iter::FromIterator;
5use std::ops::Deref;
6use std::sync::LazyLock;
7
8use glium;
9use image;
10use strum::{EnumCount, EnumIter, FromRepr};
11use vec_map::VecMap;
12use winit;
13
14use math_utils as math;
15
16use crate::{color, render, shader, texture, vertex, Render};
17
18pub mod draw2d;
19pub mod draw3d;
20#[cfg(feature="demo")]
21#[cfg_attr(docsrs, doc(cfg(feature="demo")))]
22pub mod demo;
23
24pub use self::draw2d::Draw2d;
25pub use self::draw3d::Draw3d;
26
27pub const MAIN_VIEWPORT : usize = 0;
32pub const LOWER_RIGHT_VIEWPORT : usize = MAIN_VIEWPORT;
33pub const UPPER_LEFT_VIEWPORT : usize = 1;
34pub const UPPER_RIGHT_VIEWPORT : usize = 2;
35pub const LOWER_LEFT_VIEWPORT : usize = 3;
36pub const OVERLAY_VIEWPORT : usize = 4;
37
38static DEFAULT_TEXTURES_16X16_BYTES : LazyLock <VecMap <&'static [u8]>> =
43 LazyLock::new (|| VecMap::from_iter ([
44 ( DefaultTexture16Id::Crosshair as usize,
45 texture::CROSSHAIR_PNG_FILE_BYTES.as_slice()
46 ), (
47 DefaultTexture16Id::CrosshairInverse as usize,
48 texture::CROSSHAIR_INVERSE_PNG_FILE_BYTES.as_slice()
49 )
50 ]));
51
52static DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS :
53 LazyLock <VecMap <(&'static [u8], [i16; 2])>> = LazyLock::new (|| VecMap::from_iter ([
54 ( DefaultTexturePointerId::Hand as usize,
55 ( texture::POINTER_HAND_PNG_FILE_BYTES_OFFSET.0.as_slice(),
56 texture::POINTER_HAND_PNG_FILE_BYTES_OFFSET.1
57 )
58 )
59 ]));
60
61pub type PointerTextureIndexRepr = u16;
66
67pub trait Resource {
80 fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>)
81 -> Self;
82 fn init (_render : &mut Render <Self>) where Self : Sized
83 { }
84 fn reset (render : &mut Render <Self>) where Self : Sized {
86 render.resource = Self::new (&render.glium_display)
87 }
88 fn draw_2d (_render : &Render <Self>, _glium_frame : &mut glium::Frame) where
89 Self : Sized { }
90 fn draw_3d (_render : &Render <Self>, _glium_frame : &mut glium::Frame) where
91 Self : Sized { }
92}
93
94pub struct Default {
101 pub draw2d : Draw2d,
102 pub draw3d : Draw3d,
103 pub textures_anysize : VecMap <glium::texture::Texture2d>,
105 pub textures_pointer : VecMap <(glium::texture::Texture2d, math::Vector2 <i16>)>,
107 pub tileset_128x128_texture : glium::texture::Texture2d,
109 pub tileset_256x256_texture : glium::texture::Texture2d,
111 shader_programs : VecMap <glium::Program>,
113 default_textures_16x16 : glium::texture::Texture2dArray,
115 textures_16x16 : glium::texture::Texture2dArray,
117 textures_64x64 : glium::texture::Texture2dArray
119}
120
121#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
126 FromRepr)]
127#[repr(u16)]
128pub enum DefaultTexturePointerId {
129 Hand
130}
131
132#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
133 FromRepr)]
134#[repr(u16)]
135pub enum DefaultTexture16Id {
136 Crosshair,
138 CrosshairInverse
140}
141
142#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
143 FromRepr)]
144#[repr(u16)]
145#[derive(Default)]
146pub enum DefaultTilesetId {
147 #[default]
149 EasciiAcorn128,
150 EasciiAcorn256,
151 EasciiAcornInverse128,
153 EasciiAcornInverse256
154}
155
156impl Default {
161 pub fn debug_grid_vertices() -> [vertex::Vert3dOrientationScaleColor; 3] {
164 use std::f32::consts::FRAC_PI_2;
165 const HALF_GRID_DIMS : f32 = 0.5 * draw3d::MESH_GRID_DIMS as f32;
166 [
167 vertex::Vert3dOrientationScaleColor {
169 position: [HALF_GRID_DIMS, 0.0, HALF_GRID_DIMS],
170 orientation: (*math::Rotation3::from_angle_y (math::Rad (FRAC_PI_2)))
171 .into_col_arrays(),
172 scale: [1.0, 1.0, 1.0],
173 color: color::rgba_u8_to_rgba_f32 ([255, 0, 0, 255])
174 },
175 vertex::Vert3dOrientationScaleColor {
177 position: [0.0, HALF_GRID_DIMS, HALF_GRID_DIMS],
178 orientation: (*math::Rotation3::from_angle_x (math::Rad (FRAC_PI_2)))
179 .into_col_arrays(),
180 scale: [1.0, 1.0, 1.0],
181 color: color::rgba_u8_to_rgba_f32 ([0, 255, 0, 255])
182 },
183 vertex::Vert3dOrientationScaleColor {
185 position: [0.0, 0.0, 0.0],
186 orientation: math::Matrix3::identity().into_col_arrays(),
187 scale: [1.0, 1.0, 1.0],
188 color: color::rgba_u8_to_rgba_f32 ([0, 0, 255, 255])
189 }
190 ]
191 }
192 #[inline]
194 pub fn tile_dimensions (&self, tileset_id : DefaultTilesetId) -> [u32; 2] {
195 let (width, height) = match tileset_id {
196 DefaultTilesetId::EasciiAcorn128 =>
197 self.tileset_128x128_texture.dimensions(),
198 DefaultTilesetId::EasciiAcorn256 =>
199 self.tileset_256x256_texture.dimensions(),
200 _ => unimplemented!()
201 };
202 debug_assert_eq!(width % 16, 0);
203 debug_assert_eq!(height % 16, 0);
204 [width / 16, height / 16]
205 }
206 #[inline]
207 pub const fn shader_programs (&self) -> &VecMap <glium::Program> {
208 &self.shader_programs
209 }
210 #[inline]
211 pub fn set_pointer_position (&mut self,
212 display : &glium::Display <glutin::surface::WindowSurface>,
213 position : math::Point2 <f32>
214 ) {
215 let offset = self.draw2d.draw_pointer.map_or (
216 math::Vector2::zero(),
217 |texture_index| self.textures_pointer.get (texture_index as usize)
218 .unwrap().1.numcast().unwrap()
219 );
220 self.draw2d.set_pointer_vertex (display, position + offset);
221 }
222 #[inline]
223 pub fn set_textures_16x16 (&mut self,
224 textures_16x16 : glium::texture::Texture2dArray
225 ) {
226 assert_eq!(textures_16x16.width(), 16);
227 assert_eq!(textures_16x16.height(), 16);
228 self.textures_16x16 = textures_16x16;
229 }
230 #[inline]
231 pub fn set_textures_64x64 (&mut self,
232 textures_64x64 : glium::texture::Texture2dArray
233 ) {
234 assert_eq!(textures_64x64.width(), 64);
235 assert_eq!(textures_64x64.height(), 64);
236 self.textures_64x64 = textures_64x64;
237 }
238}
239
240impl render::Resource for Default {
241 fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>)
242 -> Self
243 {
244 let shader_programs = shader::build_programs (glium_display).unwrap();
246 let default_textures_16x16 =
248 texture::texture2darray_with_mipmaps_from_bytes (
249 glium_display,
250 &DEFAULT_TEXTURES_16X16_BYTES.values().map (Deref::deref)
251 .collect::<Vec <&[u8]>>(),
252 image::ImageFormat::Png,
253 glium::texture::MipmapsOption::NoMipmap
254 ).unwrap();
255 let textures_16x16 =
256 glium::texture::Texture2dArray::empty (glium_display, 16, 16, 0).unwrap();
257 let textures_64x64 =
258 glium::texture::Texture2dArray::empty (glium_display, 64, 64, 0).unwrap();
259 let textures_anysize = VecMap::new();
260 let textures_pointer = DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS.iter()
261 .map (|(i, (bytes, offset))|{
262 let texture = texture::texture2d_with_mipmaps_from_bytes (
263 glium_display, bytes, image::ImageFormat::Png,
264 glium::texture::MipmapsOption::NoMipmap
265 ).unwrap();
266 (i, (texture, math::Vector2::from (*offset)))
267 }).collect();
268 let tileset_128x128_texture = texture::texture2d_with_mipmaps_from_bytes (
269 glium_display,
270 texture::TILESET_EASCII_ACORN_8X8_PNG_FILE_BYTES,
272 image::ImageFormat::Png,
273 glium::texture::MipmapsOption::NoMipmap
274 ).unwrap();
275 let tileset_256x256_texture = texture::texture2d_with_mipmaps_from_bytes (
276 glium_display,
277 texture::TILESET_EASCII_ACORN_16X16_PNG_FILE_BYTES,
279 image::ImageFormat::Png,
280 glium::texture::MipmapsOption::NoMipmap
281 ).unwrap();
282 let draw2d = Draw2d::new (glium_display);
295 let draw3d = Draw3d::new (glium_display);
296
297 Default {
298 shader_programs,
299 default_textures_16x16,
300 textures_16x16,
301 textures_64x64,
302 textures_anysize,
303 textures_pointer,
304 tileset_128x128_texture,
305 tileset_256x256_texture,
306 draw2d,
307 draw3d
308 }
309 }
310
311 #[inline]
312 fn init (render : &mut Render <Self>) {
313 render.update_viewport_line_loops();
314 }
315
316 #[inline]
317 fn draw_2d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
318 Draw2d::draw (render, glium_frame);
319 }
320
321 #[inline]
322 fn draw_3d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
323 Draw3d::draw (render, glium_frame);
324 } #[inline]
327 fn reset (render : &mut Render <Self>) {
328 render.resource.draw2d = Draw2d::new (&render.glium_display);
329 render.resource.draw3d = Draw3d::new (&render.glium_display);
330 }
331} impl Render <Default> {
334
335 #[inline]
336 pub fn camera3d_position_set (&mut self, position : math::Point3 <f32>) {
337 for (_, viewport) in self.viewports.iter_mut() {
338 if viewport.camera3d().is_some() {
339 viewport.camera3d_set_position (position);
340 }
341 }
342 }
343
344 #[inline]
345 pub fn camera3d_orientation_set (&mut self,
346 orientation : math::Rotation3 <f32>
347 ) {
348 for (_, viewport) in self.viewports.iter_mut() {
349 if viewport.camera3d().is_some() {
350 viewport.camera3d_set_orientation (orientation);
351 }
352 }
353 }
354
355 #[inline]
356 pub fn camera3d_look_at (&mut self, target : math::Point3 <f32>) {
357 for (_, viewport) in self.viewports.iter_mut() {
358 if viewport.camera3d().is_some() {
359 viewport.camera3d_look_at (target);
360 }
361 }
362 }
363
364 pub fn camera3d_move_local_xy (&mut self, dx : f32, dy : f32, dz : f32) {
365 self.viewports[MAIN_VIEWPORT].camera3d_move_local_xy (dx, dy, dz);
366 let position = self.viewports[MAIN_VIEWPORT].camera3d().unwrap().position();
367 for (_, viewport) in self.viewports.iter_mut().skip (1) {
368 if viewport.camera3d().is_some() {
369 viewport.camera3d_set_position (position);
370 }
371 }
372 }
373
374 #[inline]
375 pub fn camera3d_rotate (&mut self,
376 dyaw : math::Rad <f32>, dpitch : math::Rad <f32>, droll : math::Rad <f32>
377 ) {
378 self.viewports[MAIN_VIEWPORT].camera3d_rotate (dyaw, dpitch, droll);
379 }
380
381 pub fn camera3d_orthographic_zoom_scale (&mut self, scale : f32) {
389 assert!(0.0 < scale);
390 for (_, viewport) in self.viewports.iter_mut().skip(1) {
391 if viewport.camera3d().is_some() {
392 debug_assert!(viewport.camera3d().unwrap().projection()
393 .is_orthographic());
394 viewport.camera3d_scale_fovy_or_zoom (scale);
395 }
396 }
397 }
398
399 pub fn camera3d_perspective_fovy_scale (&mut self, scale : f32) {
405 assert!(0.0 < scale);
406 debug_assert!(self.viewports[MAIN_VIEWPORT].camera3d().unwrap().projection()
407 .is_perspective());
408 self.viewports[MAIN_VIEWPORT].camera3d_scale_fovy_or_zoom (scale);
409 }
410
411 pub fn camera2d_zoom_set (&mut self, zoom : f32) {
412 assert!(0.0 < zoom);
413 for (_, viewport) in self.viewports.iter_mut() {
414 if viewport.camera2d().is_some() {
415 viewport.camera2d_set_zoom (zoom);
416 }
417 }
418 self.update_viewport_line_loops();
419 }
420
421 pub fn camera2d_zoom_shift (&mut self, shift : f32) {
423 let mut dirty = false;
424 for (_, viewport) in self.viewports.iter_mut() {
425 if viewport.camera2d().is_some() {
426 let zoom = viewport.camera2d().unwrap().zoom() + shift;
427 if 0.0 < zoom {
428 viewport.camera2d_set_zoom (zoom);
429 dirty = true;
430 }
431 }
432 }
433 if dirty {
434 self.update_viewport_line_loops();
435 }
436 }
437
438 pub fn camera2d_move_local (&mut self, dx : f32, dy : f32) {
439 self.viewports[MAIN_VIEWPORT].camera2d_move_local (dx, dy);
440 let position = self.viewports[MAIN_VIEWPORT].camera2d().unwrap().position();
441 for (_, viewport) in self.viewports.iter_mut().skip (1) {
442 if viewport.camera2d().is_some() {
443 viewport.camera2d_set_position (position);
444 }
445 }
446 }
447
448 pub fn camera2d_move_origin_to_bottom_left (&mut self) {
450 for (_, viewport) in self.viewports.iter_mut() {
451 if viewport.camera2d().is_some() {
452 viewport.camera2d_move_origin_to_bottom_left()
453 }
454 }
455 self.update_viewport_line_loops();
456 }
457
458 pub fn window_resized (&mut self,
462 physical_size : winit::dpi::PhysicalSize <u32>
463 ) {
464 let (width, height) = physical_size.into();
465 if self.viewports.len() < 4 {
466 self.viewports[MAIN_VIEWPORT].set_rect (
467 glium::Rect { left: 0, bottom: 0, width, height }
468 );
469 self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
470 viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
471 } else {
472 let left_width = left_width (width);
474 let right_width = right_width (width);
475 let upper_height = upper_height (height);
476 let lower_height = lower_height (height);
477 self.viewports[LOWER_RIGHT_VIEWPORT].set_rect (
478 glium::Rect {
479 width: right_width,
480 height: lower_height,
481 left: left_width,
482 bottom: 0
483 }
484 );
485 self.viewports[UPPER_LEFT_VIEWPORT].set_rect (
486 glium::Rect {
487 width: left_width,
488 height: upper_height,
489 left: 0,
490 bottom: lower_height
491 }
492 );
493 self.viewports[UPPER_RIGHT_VIEWPORT].set_rect (
494 glium::Rect {
495 width: right_width,
496 height: upper_height,
497 left: left_width,
498 bottom: lower_height
499 }
500 );
501 self.viewports[LOWER_LEFT_VIEWPORT].set_rect (
502 glium::Rect {
503 width: left_width,
504 height: lower_height,
505 left: 0,
506 bottom: 0
507 }
508 );
509 self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
510 viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
511 }
512 self.update_viewport_line_loops();
513 }
514
515 fn update_viewport_line_loops (&mut self) {
520 const VERTS_PER_VIEWPORT : usize = 4;
521 let num_vertices = VERTS_PER_VIEWPORT * self.viewports.len();
522 let vertices = {
523 let mut vertices = Vec::<vertex::Vert2d>::with_capacity (num_vertices);
524 for (_, viewport) in self.viewports.iter() {
525 if let Some (camera2d) = viewport.camera2d() {
526 let pixel_radius = 0.5 / camera2d.zoom();
528 let position = camera2d.position();
529 let ortho = camera2d.ortho();
530 let left = position.0.x + ortho.left;
531 let bottom = position.0.y + ortho.bottom;
532 let top = position.0.y + ortho.top;
533 let right = position.0.x + ortho.right;
534 vertices.append (&mut vec![
535 vertex::Vert2d {
536 position: [left + pixel_radius, bottom + pixel_radius] },
537 vertex::Vert2d {
538 position: [left + pixel_radius, top - pixel_radius] },
539 vertex::Vert2d {
540 position: [right - pixel_radius, top - pixel_radius] },
541 vertex::Vert2d {
542 position: [right - pixel_radius, bottom + pixel_radius] }
543 ]);
544 }
545 }
546 vertices
547 };
548 self.resource.draw2d
549 .line_loop_vertices_set (&self.glium_display, &vertices[..]);
550 }
551}
552
553
554#[inline]
563const fn left_width (window_width : u32) -> u32 {
564 window_width / 2
565}
566#[inline]
567const fn right_width (window_width : u32) -> u32 {
568 window_width / 2 + window_width % 2
569}
570#[inline]
571const fn upper_height (window_height : u32) -> u32 {
572 window_height / 2
573}
574#[inline]
575const fn lower_height (window_height : u32) -> u32 {
576 window_height / 2 + window_height % 2
577}