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, 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 Resource for Default {
241 fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>) -> Self {
242 let shader_programs = shader::build_programs (glium_display).unwrap();
244 let default_textures_16x16 =
246 texture::texture2darray_with_mipmaps_from_bytes (
247 glium_display,
248 &DEFAULT_TEXTURES_16X16_BYTES.values().map (Deref::deref)
249 .collect::<Vec <&[u8]>>(),
250 image::ImageFormat::Png,
251 glium::texture::MipmapsOption::NoMipmap
252 ).unwrap();
253 let textures_16x16 =
254 glium::texture::Texture2dArray::empty (glium_display, 16, 16, 0).unwrap();
255 let textures_64x64 =
256 glium::texture::Texture2dArray::empty (glium_display, 64, 64, 0).unwrap();
257 let textures_anysize = VecMap::new();
258 let textures_pointer = DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS.iter()
259 .map (|(i, (bytes, offset))|{
260 let texture = texture::texture2d_with_mipmaps_from_bytes (
261 glium_display, bytes, image::ImageFormat::Png,
262 glium::texture::MipmapsOption::NoMipmap
263 ).unwrap();
264 (i, (texture, math::Vector2::from (*offset)))
265 }).collect();
266 let tileset_128x128_texture = texture::texture2d_with_mipmaps_from_bytes (
267 glium_display,
268 texture::TILESET_EASCII_ACORN_8X8_PNG_FILE_BYTES,
270 image::ImageFormat::Png,
271 glium::texture::MipmapsOption::NoMipmap
272 ).unwrap();
273 let tileset_256x256_texture = texture::texture2d_with_mipmaps_from_bytes (
274 glium_display,
275 texture::TILESET_EASCII_ACORN_16X16_PNG_FILE_BYTES,
277 image::ImageFormat::Png,
278 glium::texture::MipmapsOption::NoMipmap
279 ).unwrap();
280 let draw2d = Draw2d::new (glium_display);
293 let draw3d = Draw3d::new (glium_display);
294
295 Default {
296 shader_programs,
297 default_textures_16x16,
298 textures_16x16,
299 textures_64x64,
300 textures_anysize,
301 textures_pointer,
302 tileset_128x128_texture,
303 tileset_256x256_texture,
304 draw2d,
305 draw3d
306 }
307 }
308
309 #[inline]
310 fn init (render : &mut Render <Self>) {
311 render.update_viewport_line_loops();
312 }
313
314 #[inline]
315 fn draw_2d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
316 Draw2d::draw (render, glium_frame);
317 }
318
319 #[inline]
320 fn draw_3d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
321 Draw3d::draw (render, glium_frame);
322 } #[inline]
325 fn reset (render : &mut Render <Self>) {
326 render.resource.draw2d = Draw2d::new (&render.glium_display);
327 render.resource.draw3d = Draw3d::new (&render.glium_display);
328 }
329} impl Render <Default> {
332
333 #[inline]
334 pub fn camera3d_position_set (&mut self, position : math::Point3 <f32>) {
335 for (_, viewport) in self.viewports.iter_mut() {
336 if viewport.camera3d().is_some() {
337 viewport.camera3d_set_position (position);
338 }
339 }
340 }
341
342 #[inline]
343 pub fn camera3d_orientation_set (&mut self,
344 orientation : math::Rotation3 <f32>
345 ) {
346 for (_, viewport) in self.viewports.iter_mut() {
347 if viewport.camera3d().is_some() {
348 viewport.camera3d_set_orientation (orientation);
349 }
350 }
351 }
352
353 #[inline]
354 pub fn camera3d_look_at (&mut self, target : math::Point3 <f32>) {
355 for (_, viewport) in self.viewports.iter_mut() {
356 if viewport.camera3d().is_some() {
357 viewport.camera3d_look_at (target);
358 }
359 }
360 }
361
362 pub fn camera3d_move_local_xy (&mut self, dx : f32, dy : f32, dz : f32) {
363 self.viewports[MAIN_VIEWPORT].camera3d_move_local_xy (dx, dy, dz);
364 let position = self.viewports[MAIN_VIEWPORT].camera3d().unwrap().position();
365 for (_, viewport) in self.viewports.iter_mut().skip (1) {
366 if viewport.camera3d().is_some() {
367 viewport.camera3d_set_position (position);
368 }
369 }
370 }
371
372 #[inline]
373 pub fn camera3d_rotate (&mut self,
374 dyaw : math::Rad <f32>, dpitch : math::Rad <f32>, droll : math::Rad <f32>
375 ) {
376 self.viewports[MAIN_VIEWPORT].camera3d_rotate (dyaw, dpitch, droll);
377 }
378
379 pub fn camera3d_orthographic_zoom_scale (&mut self, scale : f32) {
387 assert!(0.0 < scale);
388 for (_, viewport) in self.viewports.iter_mut().skip(1) {
389 if viewport.camera3d().is_some() {
390 debug_assert!(viewport.camera3d().unwrap().projection()
391 .is_orthographic());
392 viewport.camera3d_scale_fovy_or_zoom (scale);
393 }
394 }
395 }
396
397 pub fn camera3d_perspective_fovy_scale (&mut self, scale : f32) {
403 assert!(0.0 < scale);
404 debug_assert!(self.viewports[MAIN_VIEWPORT].camera3d().unwrap().projection()
405 .is_perspective());
406 self.viewports[MAIN_VIEWPORT].camera3d_scale_fovy_or_zoom (scale);
407 }
408
409 pub fn camera2d_zoom_set (&mut self, zoom : f32) {
410 assert!(0.0 < zoom);
411 for (_, viewport) in self.viewports.iter_mut() {
412 if viewport.camera2d().is_some() {
413 viewport.camera2d_set_zoom (zoom);
414 }
415 }
416 self.update_viewport_line_loops();
417 }
418
419 pub fn camera2d_zoom_shift (&mut self, shift : f32) {
421 let mut dirty = false;
422 for (_, viewport) in self.viewports.iter_mut() {
423 if viewport.camera2d().is_some() {
424 let zoom = viewport.camera2d().unwrap().zoom() + shift;
425 if 0.0 < zoom {
426 viewport.camera2d_set_zoom (zoom);
427 dirty = true;
428 }
429 }
430 }
431 if dirty {
432 self.update_viewport_line_loops();
433 }
434 }
435
436 pub fn camera2d_move_local (&mut self, dx : f32, dy : f32) {
437 self.viewports[MAIN_VIEWPORT].camera2d_move_local (dx, dy);
438 let position = self.viewports[MAIN_VIEWPORT].camera2d().unwrap().position();
439 for (_, viewport) in self.viewports.iter_mut().skip (1) {
440 if viewport.camera2d().is_some() {
441 viewport.camera2d_set_position (position);
442 }
443 }
444 }
445
446 pub fn camera2d_move_origin_to_bottom_left (&mut self) {
448 for (_, viewport) in self.viewports.iter_mut() {
449 if viewport.camera2d().is_some() {
450 viewport.camera2d_move_origin_to_bottom_left()
451 }
452 }
453 self.update_viewport_line_loops();
454 }
455
456 pub fn window_resized (&mut self,
460 physical_size : winit::dpi::PhysicalSize <u32>
461 ) {
462 let (width, height) = physical_size.into();
463 if self.viewports.len() < 4 {
464 self.viewports[MAIN_VIEWPORT].set_rect (
465 glium::Rect { left: 0, bottom: 0, width, height }
466 );
467 self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
468 viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
469 } else {
470 let left_width = left_width (width);
472 let right_width = right_width (width);
473 let upper_height = upper_height (height);
474 let lower_height = lower_height (height);
475 self.viewports[LOWER_RIGHT_VIEWPORT].set_rect (
476 glium::Rect {
477 width: right_width,
478 height: lower_height,
479 left: left_width,
480 bottom: 0
481 }
482 );
483 self.viewports[UPPER_LEFT_VIEWPORT].set_rect (
484 glium::Rect {
485 width: left_width,
486 height: upper_height,
487 left: 0,
488 bottom: lower_height
489 }
490 );
491 self.viewports[UPPER_RIGHT_VIEWPORT].set_rect (
492 glium::Rect {
493 width: right_width,
494 height: upper_height,
495 left: left_width,
496 bottom: lower_height
497 }
498 );
499 self.viewports[LOWER_LEFT_VIEWPORT].set_rect (
500 glium::Rect {
501 width: left_width,
502 height: lower_height,
503 left: 0,
504 bottom: 0
505 }
506 );
507 self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
508 viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
509 }
510 self.update_viewport_line_loops();
511 }
512
513 fn update_viewport_line_loops (&mut self) {
518 const VERTS_PER_VIEWPORT : usize = 4;
519 let num_vertices = VERTS_PER_VIEWPORT * self.viewports.len();
520 let vertices = {
521 let mut vertices = Vec::<vertex::Vert2d>::with_capacity (num_vertices);
522 for (_, viewport) in self.viewports.iter() {
523 if let Some (camera2d) = viewport.camera2d() {
524 let pixel_radius = 0.5 / camera2d.zoom();
526 let position = camera2d.position();
527 let ortho = camera2d.ortho();
528 let left = position.0.x + ortho.left;
529 let bottom = position.0.y + ortho.bottom;
530 let top = position.0.y + ortho.top;
531 let right = position.0.x + ortho.right;
532 vertices.append (&mut vec![
533 vertex::Vert2d {
534 position: [left + pixel_radius, bottom + pixel_radius] },
535 vertex::Vert2d {
536 position: [left + pixel_radius, top - pixel_radius] },
537 vertex::Vert2d {
538 position: [right - pixel_radius, top - pixel_radius] },
539 vertex::Vert2d {
540 position: [right - pixel_radius, bottom + pixel_radius] }
541 ]);
542 }
543 }
544 vertices
545 };
546 self.resource.draw2d
547 .line_loop_vertices_set (&self.glium_display, &vertices[..]);
548 }
549}
550
551
552#[inline]
561const fn left_width (window_width : u32) -> u32 {
562 window_width / 2
563}
564#[inline]
565const fn right_width (window_width : u32) -> u32 {
566 window_width / 2 + window_width % 2
567}
568#[inline]
569const fn upper_height (window_height : u32) -> u32 {
570 window_height / 2
571}
572#[inline]
573const fn lower_height (window_height : u32) -> u32 {
574 window_height / 2 + window_height % 2
575}