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 color::{Normalize, WithAlphaOpaque};
165 use std::f32::consts::FRAC_PI_2;
166 const HALF_GRID_DIMS : f32 = 0.5 * draw3d::MESH_GRID_DIMS as f32;
167 [
168 vertex::Vert3dOrientationScaleColor {
170 position: [HALF_GRID_DIMS, 0.0, HALF_GRID_DIMS],
171 orientation: (*math::Rotation3::from_angle_y (math::Rad (FRAC_PI_2)))
172 .into_col_arrays(),
173 scale: [1.0, 1.0, 1.0],
174 color: color::RED.normalize().rgba().into()
175 },
176 vertex::Vert3dOrientationScaleColor {
178 position: [0.0, HALF_GRID_DIMS, HALF_GRID_DIMS],
179 orientation: (*math::Rotation3::from_angle_x (math::Rad (FRAC_PI_2)))
180 .into_col_arrays(),
181 scale: [1.0, 1.0, 1.0],
182 color: color::GREEN.normalize().rgba().into()
183 },
184 vertex::Vert3dOrientationScaleColor {
186 position: [0.0, 0.0, 0.0],
187 orientation: math::Matrix3::identity().into_col_arrays(),
188 scale: [1.0, 1.0, 1.0],
189 color: color::BLUE.normalize().rgba().into()
190 }
191 ]
192 }
193 #[inline]
195 pub fn tile_dimensions (&self, tileset_id : DefaultTilesetId) -> [u32; 2] {
196 let (width, height) = match tileset_id {
197 DefaultTilesetId::EasciiAcorn128 =>
198 self.tileset_128x128_texture.dimensions(),
199 DefaultTilesetId::EasciiAcorn256 =>
200 self.tileset_256x256_texture.dimensions(),
201 _ => unimplemented!()
202 };
203 debug_assert_eq!(width % 16, 0);
204 debug_assert_eq!(height % 16, 0);
205 [width / 16, height / 16]
206 }
207 #[inline]
208 pub const fn shader_programs (&self) -> &VecMap <glium::Program> {
209 &self.shader_programs
210 }
211 #[inline]
212 pub fn set_pointer_position (&mut self,
213 display : &glium::Display <glutin::surface::WindowSurface>,
214 position : math::Point2 <f32>
215 ) {
216 let offset = self.draw2d.draw_pointer.map_or (
217 math::Vector2::zero(),
218 |texture_index| self.textures_pointer.get (texture_index as usize)
219 .unwrap().1.numcast().unwrap()
220 );
221 self.draw2d.set_pointer_vertex (display, position + offset);
222 }
223 #[inline]
224 pub fn set_textures_16x16 (&mut self,
225 textures_16x16 : glium::texture::Texture2dArray
226 ) {
227 assert_eq!(textures_16x16.width(), 16);
228 assert_eq!(textures_16x16.height(), 16);
229 self.textures_16x16 = textures_16x16;
230 }
231 #[inline]
232 pub fn set_textures_64x64 (&mut self,
233 textures_64x64 : glium::texture::Texture2dArray
234 ) {
235 assert_eq!(textures_64x64.width(), 64);
236 assert_eq!(textures_64x64.height(), 64);
237 self.textures_64x64 = textures_64x64;
238 }
239}
240
241impl Resource for Default {
242 fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>) -> Self {
243 let shader_programs = shader::build_programs (glium_display).unwrap();
245 let default_textures_16x16 =
247 texture::texture2darray_with_mipmaps_from_bytes (
248 glium_display,
249 &DEFAULT_TEXTURES_16X16_BYTES.values().map (Deref::deref)
250 .collect::<Vec <&[u8]>>(),
251 image::ImageFormat::Png,
252 glium::texture::MipmapsOption::NoMipmap
253 ).unwrap();
254 let textures_16x16 =
255 glium::texture::Texture2dArray::empty (glium_display, 16, 16, 0).unwrap();
256 let textures_64x64 =
257 glium::texture::Texture2dArray::empty (glium_display, 64, 64, 0).unwrap();
258 let textures_anysize = VecMap::new();
259 let textures_pointer = DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS.iter()
260 .map (|(i, (bytes, offset))|{
261 let texture = texture::texture2d_with_mipmaps_from_bytes (
262 glium_display, bytes, image::ImageFormat::Png,
263 glium::texture::MipmapsOption::NoMipmap
264 ).unwrap();
265 (i, (texture, math::Vector2::from (*offset)))
266 }).collect();
267 let tileset_128x128_texture = texture::texture2d_with_mipmaps_from_bytes (
268 glium_display,
269 texture::TILESET_EASCII_ACORN_8X8_PNG_FILE_BYTES,
271 image::ImageFormat::Png,
272 glium::texture::MipmapsOption::NoMipmap
273 ).unwrap();
274 let tileset_256x256_texture = texture::texture2d_with_mipmaps_from_bytes (
275 glium_display,
276 texture::TILESET_EASCII_ACORN_16X16_PNG_FILE_BYTES,
278 image::ImageFormat::Png,
279 glium::texture::MipmapsOption::NoMipmap
280 ).unwrap();
281 let draw2d = Draw2d::new (glium_display);
294 let draw3d = Draw3d::new (glium_display);
295
296 Default {
297 shader_programs,
298 default_textures_16x16,
299 textures_16x16,
300 textures_64x64,
301 textures_anysize,
302 textures_pointer,
303 tileset_128x128_texture,
304 tileset_256x256_texture,
305 draw2d,
306 draw3d
307 }
308 }
309
310 #[inline]
311 fn init (render : &mut Render <Self>) {
312 render.update_viewport_line_loops();
313 }
314
315 #[inline]
316 fn draw_2d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
317 Draw2d::draw (render, glium_frame);
318 }
319
320 #[inline]
321 fn draw_3d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
322 Draw3d::draw (render, glium_frame);
323 } #[inline]
326 fn reset (render : &mut Render <Self>) {
327 render.resource.draw2d = Draw2d::new (&render.glium_display);
328 render.resource.draw3d = Draw3d::new (&render.glium_display);
329 }
330} impl Render <Default> {
333
334 #[inline]
335 pub fn camera3d_position_set (&mut self, position : math::Point3 <f32>) {
336 for (_, viewport) in self.viewports.iter_mut() {
337 if viewport.camera3d().is_some() {
338 viewport.camera3d_set_position (position);
339 }
340 }
341 }
342
343 #[inline]
344 pub fn camera3d_orientation_set (&mut self,
345 orientation : math::Rotation3 <f32>
346 ) {
347 for (_, viewport) in self.viewports.iter_mut() {
348 if viewport.camera3d().is_some() {
349 viewport.camera3d_set_orientation (orientation);
350 }
351 }
352 }
353
354 #[inline]
355 pub fn camera3d_look_at (&mut self, target : math::Point3 <f32>) {
356 for (_, viewport) in self.viewports.iter_mut() {
357 if viewport.camera3d().is_some() {
358 viewport.camera3d_look_at (target);
359 }
360 }
361 }
362
363 pub fn camera3d_move_local_xy (&mut self, dx : f32, dy : f32, dz : f32) {
364 self.viewports[MAIN_VIEWPORT].camera3d_move_local_xy (dx, dy, dz);
365 let position = self.viewports[MAIN_VIEWPORT].camera3d().unwrap().position();
366 for (_, viewport) in self.viewports.iter_mut().skip (1) {
367 if viewport.camera3d().is_some() {
368 viewport.camera3d_set_position (position);
369 }
370 }
371 }
372
373 #[inline]
374 pub fn camera3d_rotate (&mut self,
375 dyaw : math::Rad <f32>, dpitch : math::Rad <f32>, droll : math::Rad <f32>
376 ) {
377 self.viewports[MAIN_VIEWPORT].camera3d_rotate (dyaw, dpitch, droll);
378 }
379
380 pub fn camera3d_orthographic_zoom_scale (&mut self, scale : f32) {
388 assert!(0.0 < scale);
389 for (_, viewport) in self.viewports.iter_mut().skip(1) {
390 if viewport.camera3d().is_some() {
391 debug_assert!(viewport.camera3d().unwrap().projection()
392 .is_orthographic());
393 viewport.camera3d_scale_fovy_or_zoom (scale);
394 }
395 }
396 }
397
398 pub fn camera3d_perspective_fovy_scale (&mut self, scale : f32) {
404 assert!(0.0 < scale);
405 debug_assert!(self.viewports[MAIN_VIEWPORT].camera3d().unwrap().projection()
406 .is_perspective());
407 self.viewports[MAIN_VIEWPORT].camera3d_scale_fovy_or_zoom (scale);
408 }
409
410 pub fn camera2d_zoom_set (&mut self, zoom : f32) {
411 assert!(0.0 < zoom);
412 for (_, viewport) in self.viewports.iter_mut() {
413 if viewport.camera2d().is_some() {
414 viewport.camera2d_set_zoom (zoom);
415 }
416 }
417 self.update_viewport_line_loops();
418 }
419
420 pub fn camera2d_zoom_shift (&mut self, shift : f32) {
422 let mut dirty = false;
423 for (_, viewport) in self.viewports.iter_mut() {
424 if viewport.camera2d().is_some() {
425 let zoom = viewport.camera2d().unwrap().zoom() + shift;
426 if 0.0 < zoom {
427 viewport.camera2d_set_zoom (zoom);
428 dirty = true;
429 }
430 }
431 }
432 if dirty {
433 self.update_viewport_line_loops();
434 }
435 }
436
437 pub fn camera2d_move_local (&mut self, dx : f32, dy : f32) {
438 self.viewports[MAIN_VIEWPORT].camera2d_move_local (dx, dy);
439 let position = self.viewports[MAIN_VIEWPORT].camera2d().unwrap().position();
440 for (_, viewport) in self.viewports.iter_mut().skip (1) {
441 if viewport.camera2d().is_some() {
442 viewport.camera2d_set_position (position);
443 }
444 }
445 }
446
447 pub fn camera2d_move_origin_to_bottom_left (&mut self) {
449 for (_, viewport) in self.viewports.iter_mut() {
450 if viewport.camera2d().is_some() {
451 viewport.camera2d_move_origin_to_bottom_left()
452 }
453 }
454 self.update_viewport_line_loops();
455 }
456
457 pub fn window_resized (&mut self,
461 physical_size : winit::dpi::PhysicalSize <u32>
462 ) {
463 let (width, height) = physical_size.into();
464 if self.viewports.len() < 4 {
465 self.viewports[MAIN_VIEWPORT].set_rect (
466 glium::Rect { left: 0, bottom: 0, width, height }
467 );
468 self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
469 viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
470 } else {
471 let left_width = left_width (width);
473 let right_width = right_width (width);
474 let upper_height = upper_height (height);
475 let lower_height = lower_height (height);
476 self.viewports[LOWER_RIGHT_VIEWPORT].set_rect (
477 glium::Rect {
478 width: right_width,
479 height: lower_height,
480 left: left_width,
481 bottom: 0
482 }
483 );
484 self.viewports[UPPER_LEFT_VIEWPORT].set_rect (
485 glium::Rect {
486 width: left_width,
487 height: upper_height,
488 left: 0,
489 bottom: lower_height
490 }
491 );
492 self.viewports[UPPER_RIGHT_VIEWPORT].set_rect (
493 glium::Rect {
494 width: right_width,
495 height: upper_height,
496 left: left_width,
497 bottom: lower_height
498 }
499 );
500 self.viewports[LOWER_LEFT_VIEWPORT].set_rect (
501 glium::Rect {
502 width: left_width,
503 height: lower_height,
504 left: 0,
505 bottom: 0
506 }
507 );
508 self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
509 viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
510 }
511 self.update_viewport_line_loops();
512 }
513
514 fn update_viewport_line_loops (&mut self) {
519 const VERTS_PER_VIEWPORT : usize = 4;
520 let num_vertices = VERTS_PER_VIEWPORT * self.viewports.len();
521 let vertices = {
522 let mut vertices = Vec::<vertex::Vert2d>::with_capacity (num_vertices);
523 for (_, viewport) in self.viewports.iter() {
524 if let Some (camera2d) = viewport.camera2d() {
525 let pixel_radius = 0.5 / camera2d.zoom();
527 let position = camera2d.position();
528 let ortho = camera2d.ortho();
529 let left = position.0.x + ortho.left;
530 let bottom = position.0.y + ortho.bottom;
531 let top = position.0.y + ortho.top;
532 let right = position.0.x + ortho.right;
533 vertices.append (&mut vec![
534 vertex::Vert2d {
535 position: [left + pixel_radius, bottom + pixel_radius] },
536 vertex::Vert2d {
537 position: [left + pixel_radius, top - pixel_radius] },
538 vertex::Vert2d {
539 position: [right - pixel_radius, top - pixel_radius] },
540 vertex::Vert2d {
541 position: [right - pixel_radius, bottom + pixel_radius] }
542 ]);
543 }
544 }
545 vertices
546 };
547 self.resource.draw2d
548 .line_loop_vertices_set (&self.glium_display, &vertices[..]);
549 }
550}
551
552
553#[inline]
562const fn left_width (window_width : u32) -> u32 {
563 window_width / 2
564}
565#[inline]
566const fn right_width (window_width : u32) -> u32 {
567 window_width / 2 + window_width % 2
568}
569#[inline]
570const fn upper_height (window_height : u32) -> u32 {
571 window_height / 2
572}
573#[inline]
574const fn lower_height (window_height : u32) -> u32 {
575 window_height / 2 + window_height % 2
576}