Skip to main content

gl_utils/render/resource/
draw3d.rs

1use derive_more::From;
2use glium::{self, uniform};
3use strum::{EnumCount, EnumIter, FromRepr};
4use vec_map::VecMap;
5
6use math_utils as math;
7
8use crate::{color, mesh, render, shader, vertex, Mesh, Render};
9
10pub const MESH_GRID_DIMS          : u16 = 12;
11/// A default scale used when rending 3D sprites
12pub const DEFAULT_PIXELS_PER_UNIT : f32 = 64.0;
13
14/// 3d drawing resources.
15///
16/// ```text
17/// [                instance_vertices               ]
18/// ```
19pub struct Draw3d {
20  /// Vertex buffer containing per-instance vertices. Any of the below instanced
21  /// types can be rendered at one of these vertices.
22  instance_vertices : glium::VertexBuffer <vertex::Vert3dOrientationScaleColor>,
23
24  // TODO: combine abbs with basis vectors as variants of a 'primitive'
25  /// Range of per-instance vertices to draw AABB lines
26  instanced_aabb_lines        : std::ops::Range <u32>,
27  /// Range of per-instance vertices to draw AABB triangles
28  instanced_aabb_triangles    : std::ops::Range <u32>,
29  /// Range of per-instance vertices to draw raycast shaded spheres
30  instanced_spheres_raycast   : std::ops::Range <u32>,
31  instanced_billboards        : VecMap <InstancedBillboard>,
32  instanced_meshes            : InstancedMeshes,
33  pub user_buffers            : VecMap <glium::VertexBuffer <vertex::Vert3dColor>>,
34  pub user_draw               : VecMap <UserDraw>,
35  pub light_directional       : math::Unit3 <f32>,
36  pub light_directional_color : color::Rgb <f32>
37}
38
39#[derive(Clone, Debug, Eq, PartialEq)]
40pub struct UserDraw {
41  pub buffer_key        : UserBufferKey,
42  /// Slice of the buffer to draw from
43  pub range             : std::ops::Range <u32>,
44  pub primitive_type    : glium::index::PrimitiveType,
45  pub shader_program_id : shader::ProgramId,
46  pub draw_pass         : DrawPass
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub struct UserBufferKey (pub u32);
51
52#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53pub enum DrawPass {
54  Depth,
55  NoDepth
56}
57
58/// Wireframe meshes matched to ranges of per-instance vertices
59#[derive(Clone, Debug, Eq, PartialEq)]
60pub struct InstancedMesh {
61  /// Slice of `instanced_meshes.line_indices` corresponding to this mesh: each
62  /// instance of the mesh will draw an instance of the vertices mapped to by
63  /// the range of indices
64  pub line_indices_range : std::ops::Range <u32>,
65  /// Range of `instance_vertices` corresponding to this mesh: each of these
66  /// vertices will draw an instance of this mesh at that location
67  pub instances_range    : std::ops::Range <u32>,
68  pub shader_program_id  : shader::ProgramId,
69  pub draw_pass          : DrawPass
70}
71
72/// 3D billboards mapped to ranges of per-instance vertices
73pub struct InstancedBillboard {
74  pub texture         : glium::Texture2d,
75  /// Billboard will be drawn at this range of the `instance_vertices`
76  pub instances_range : std::ops::Range <u32>
77}
78
79/// Used to initialize per-instance vertex buffers
80#[derive(Default)]
81pub struct InstancesInit <'a> {
82  pub aabb_lines               : Option <&'a [vertex::Vert3dOrientationScaleColor]>,
83  pub aabb_triangles           : Option <&'a [vertex::Vert3dOrientationScaleColor]>,
84  pub aabb_lines_and_triangles : Option <&'a [vertex::Vert3dOrientationScaleColor]>,
85  pub spheres_raycast          : Option <&'a [vertex::Vert3dOrientationScaleColor]>,
86  pub meshes                   : VecMap <&'a [vertex::Vert3dOrientationScaleColor]>,
87  pub billboards               : VecMap <&'a [vertex::Vert3dOrientationScaleColor]>
88}
89
90#[derive(Clone, Copy, Debug, Eq, PartialEq, From)]
91pub struct MeshKey (pub u32);
92
93#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
94  FromRepr)]
95#[repr(u16)]
96pub enum MeshId {
97  Grid,
98  Hemisphere,
99  Capsule,
100  Cube,
101  Cylinder,
102  Sphere
103}
104
105//
106//  private
107//
108
109struct InstancedMeshes {
110  /// 3D vertices for instanced drawing.
111  ///
112  /// Multiple instanced meshes are stored here, indexed by slices of
113  /// `line_indices`.
114  pub vertices     : glium::VertexBuffer <vertex::Vert3dInstanced>,
115  /// Indices for drawing instanced 3D meshes in `instanced_vertices`.
116  ///
117  /// Each separate mesh has a corresponding slice of indices in this index
118  /// buffer, with ranges stored in the `instanced_indices_lines_ranges`
119  /// field.
120  pub line_indices : glium::IndexBuffer <u32>,
121  /// Each instanced mesh defines a range of instanced line indices, a shader
122  /// program ID, and a range of instance vertices at which to draw an instance
123  pub meshes       : VecMap <InstancedMesh>,
124  // some uniforms for the capsule shader
125  pub capsule_vertex_id_offset : u32,
126  pub hemisphere_vertex_count  : u32
127}
128
129struct InstancedMeshInit {
130  pub instanced_vertex_data     : Vec <vertex::Vert3dInstanced>,
131  pub instanced_line_index_data : Vec <u32>,
132  pub line_indices_ranges       : VecMap <std::ops::Range <u32>>,
133  pub shader_program_ids        : VecMap <shader::ProgramId>,
134  pub draw_passes               : VecMap <DrawPass>,
135  pub capsule_vertex_id_offset  : u32,
136  pub hemisphere_vertex_count   : u32
137}
138
139////////////////////////////////////////////////////////////////////////////////
140//  impls
141////////////////////////////////////////////////////////////////////////////////
142
143impl Draw3d {
144  pub fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>) -> Self {
145    use color::{Glsl, Normalize};
146    let instanced_aabb_lines      = 0..0;   // empty
147    let instanced_aabb_triangles  = 0..0;   // empty
148    let instanced_spheres_raycast = 0..0;   // empty
149    let instanced_billboards      = VecMap::new();
150    let instanced_meshes          = InstancedMeshes::new (glium_display, vec![]);
151    let instance_vertices         = glium::VertexBuffer::dynamic (
152      glium_display,
153      // origin vertex
154      &[ vertex::Vert3dOrientationScaleColor {
155          position:    [0.0, 0.0, 0.0],
156          orientation: math::Matrix3::identity().into_col_arrays(),
157          scale:       [1.0, 1.0, 1.0],
158          color:       color::DEBUG_CHARTREUSE.glsl_rgba()
159        }
160      ]
161    ).unwrap();
162    let user_buffers            = VecMap::new();
163    let user_draw               = VecMap::new();
164    let light_directional       = -math::Unit3::axis_z();
165    let light_directional_color = color::NOON_SUN.normalize();
166    Draw3d {
167      instance_vertices,
168      instanced_aabb_lines,
169      instanced_aabb_triangles,
170      instanced_spheres_raycast,
171      instanced_billboards,
172      instanced_meshes,
173      user_buffers,
174      user_draw,
175      light_directional,
176      light_directional_color
177    }
178  }
179
180  pub fn draw (
181    render      : &Render <render::resource::Default>,
182    glium_frame : &mut glium::Frame
183  ) {
184    use color::{Glsl, IntoArray};
185    let draw3d = &render.resource.draw3d;
186    // draw each viewport 3d
187    for (_, viewport) in render.viewports.iter() {
188      if let Some (camera3d) = viewport.camera3d() {
189        use glium::Surface;
190        // draw parameters: viewport
191        let draw_parameters_viewport = viewport.draw_parameters();
192        // draw parameters: depth test
193        let draw_parameters_write_depth = glium::DrawParameters {
194          depth: glium::Depth {
195            test:  glium::DepthTest::IfLess,
196            write: true,
197            .. glium::Depth::default()
198          },
199          .. draw_parameters_viewport.clone()
200        };
201        // draw parameters: polygon offset
202        let draw_parameters_polygon_offset = glium::DrawParameters {
203          polygon_offset: glium::draw_parameters::PolygonOffset {
204            factor: 1.0,
205            units:  1.0 / 1024.0,
206            fill:   true,
207            .. Default::default()
208          },
209          backface_culling:
210            glium::draw_parameters::BackfaceCullingMode::CullCounterClockwise,
211          .. draw_parameters_write_depth.clone()
212        };
213        // uniforms
214        let (transform_mat_world_to_view, projection_mat_perspective) =
215          camera3d.view_mats();
216        let projection_mat_perspective_inverse = camera3d.projection().as_matrix()
217          .inverted().into_col_arrays();
218        let light_directional_view = (camera3d.transform_mat_world_to_view()
219          * math::Vector4::from_direction (draw3d.light_directional)
220        ).xyz().into_array();
221        let uniforms = uniform!{
222          uni_transform_mat_view:       transform_mat_world_to_view,
223          uni_projection_mat_perspective: projection_mat_perspective,
224          uni_projection_mat_perspective_inverse: projection_mat_perspective_inverse,
225          uni_light_directional_view:   light_directional_view,
226          uni_light_directional_color:  draw3d.light_directional_color.into_array(),
227          // TODO: variable pixels per unit?
228          uni_pixels_per_unit:          DEFAULT_PIXELS_PER_UNIT,
229          uni_capsule_vertex_id_offset: draw3d.instanced_meshes.capsule_vertex_id_offset,
230          uni_hemisphere_vertex_count:  draw3d.instanced_meshes.hemisphere_vertex_count,
231          uni_color:                    color::BLACK.glsl_rgba()
232        };
233
234        // draw aabbs lines
235        if !draw3d.instanced_aabb_lines.is_empty() {
236          let instances_range =
237            draw3d.instanced_aabb_lines.start as usize..
238            draw3d.instanced_aabb_lines.end   as usize;
239          glium_frame.draw (
240            draw3d.instance_vertices.slice (instances_range).unwrap(),
241            glium::index::IndicesSource::NoIndices {
242              primitives: glium::index::PrimitiveType::Points
243            },
244            &render.resource.shader_programs [
245              shader::ProgramId::WorldSpace3dAabbLines as usize
246            ],
247            &uniforms,
248            &draw_parameters_write_depth
249          ).unwrap();
250        }
251
252        // draw aabbs triangles
253        if !draw3d.instanced_aabb_triangles.is_empty() {
254          let instances_range =
255            draw3d.instanced_aabb_triangles.start as usize..
256            draw3d.instanced_aabb_triangles.end   as usize;
257          glium_frame.draw (
258            draw3d.instance_vertices.slice (instances_range).unwrap(),
259            glium::index::IndicesSource::NoIndices {
260              primitives: glium::index::PrimitiveType::Points
261            },
262            &render.resource.shader_programs [
263              shader::ProgramId::WorldSpace3dAabbTriangles as usize
264            ],
265            &uniforms,
266            &draw_parameters_polygon_offset
267          ).unwrap();
268        }
269
270        // draw spheres
271        if !draw3d.instanced_spheres_raycast.is_empty() {
272          let instances_range =
273            draw3d.instanced_spheres_raycast.start as usize..
274            draw3d.instanced_spheres_raycast.end   as usize;
275          glium_frame.draw (
276            draw3d.instance_vertices.slice (instances_range).unwrap(),
277            glium::index::IndicesSource::NoIndices {
278              primitives: glium::index::PrimitiveType::Points
279            },
280            &render.resource.shader_programs [
281              shader::ProgramId::WorldSpace3dSphere as usize
282            ],
283            &uniforms,
284            &draw_parameters_write_depth
285          ).unwrap();
286        }
287
288        // draw instanced meshes with depth
289        for (_mesh_key, instanced_mesh) in draw3d.instanced_meshes.meshes.iter() {
290          if instanced_mesh.draw_pass != DrawPass::Depth {
291            continue
292          }
293          if !instanced_mesh.instances_range.is_empty() {
294            let (line_indices_range, instances_range) = (
295              instanced_mesh.line_indices_range.start as usize..
296              instanced_mesh.line_indices_range.end   as usize,
297              instanced_mesh.instances_range.start as usize..
298              instanced_mesh.instances_range.end   as usize
299            );
300            glium_frame.draw (
301              (&draw3d.instanced_meshes.vertices,
302                draw3d.instance_vertices.slice (instances_range).unwrap()
303                  .per_instance().unwrap()
304              ),
305              draw3d.instanced_meshes.line_indices.slice (line_indices_range)
306                .unwrap(),
307              &render.resource.shader_programs [instanced_mesh.shader_program_id
308                as usize],
309              &uniforms,
310              &draw_parameters_write_depth
311            ).unwrap();
312          }
313        }
314
315        // user draw calls with depth
316        for user_draw in draw3d.user_draw.values() {
317          if user_draw.draw_pass != DrawPass::Depth {
318            continue
319          }
320          let vertex_buffer = draw3d.user_buffers
321            .get (user_draw.buffer_key.index()).unwrap();
322          let vertex_range  =
323            user_draw.range.start as usize..
324            user_draw.range.end   as usize;
325          let uniforms = uniform!{
326            uni_transform_mat_view:         transform_mat_world_to_view,
327            uni_projection_mat_perspective: projection_mat_perspective,
328            // TODO: variable pixels per unit?
329            uni_pixels_per_unit:            DEFAULT_PIXELS_PER_UNIT,
330            uni_capsule_vertex_id_offset:
331              draw3d.instanced_meshes.capsule_vertex_id_offset,
332            uni_hemisphere_vertex_count:
333              draw3d.instanced_meshes.hemisphere_vertex_count
334          };
335          glium_frame.draw (
336            vertex_buffer.slice (vertex_range).unwrap(),
337            glium::index::NoIndices (user_draw.primitive_type),
338            &render.resource.shader_programs [user_draw.shader_program_id.index()],
339            &uniforms,
340            &draw_parameters_write_depth
341          ).unwrap();
342        }
343
344        // draw instanced meshes w/o depth
345        for (_mesh_key, instanced_mesh) in draw3d.instanced_meshes.meshes.iter() {
346          if instanced_mesh.draw_pass != DrawPass::NoDepth {
347            continue
348          }
349          if !instanced_mesh.instances_range.is_empty() {
350            let (line_indices_range, instances_range) = (
351              instanced_mesh.line_indices_range.start as usize..
352              instanced_mesh.line_indices_range.end   as usize,
353              instanced_mesh.instances_range.start as usize..
354              instanced_mesh.instances_range.end   as usize
355            );
356            glium_frame.draw (
357              (&draw3d.instanced_meshes.vertices,
358                draw3d.instance_vertices.slice (instances_range).unwrap()
359                  .per_instance().unwrap()
360              ),
361              draw3d.instanced_meshes.line_indices.slice (line_indices_range)
362                .unwrap(),
363              &render.resource.shader_programs [instanced_mesh.shader_program_id
364                as usize],
365              &uniforms,
366              &draw_parameters_viewport
367            ).unwrap();
368          }
369        }
370
371        // user draw calls with w/o depth
372        for user_draw in draw3d.user_draw.values() {
373          if user_draw.draw_pass != DrawPass::NoDepth {
374            continue
375          }
376          let vertex_buffer = draw3d.user_buffers
377            .get (user_draw.buffer_key.index()).unwrap();
378          let vertex_range  =
379            user_draw.range.start as usize..
380            user_draw.range.end   as usize;
381          glium_frame.draw (
382            vertex_buffer.slice (vertex_range).unwrap(),
383            glium::index::NoIndices (user_draw.primitive_type),
384            &render.resource.shader_programs [user_draw.shader_program_id.index()],
385            &uniforms,
386            &draw_parameters_viewport
387          ).unwrap();
388        }
389
390        // draw 3d billboards
391        // TODO: draw some or all billboards with depth?
392        for (_, instanced_billboard) in draw3d.instanced_billboards.iter() {
393          let instances_range =
394            instanced_billboard.instances_range.start as usize..
395            instanced_billboard.instances_range.end   as usize;
396          let uniforms = uniform!{
397            uni_transform_mat_view:         transform_mat_world_to_view,
398            uni_projection_mat_perspective: projection_mat_perspective,
399            uni_sampler2d:                  instanced_billboard.texture.sampled()
400              .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest),
401            // TODO: variable pixels per unit?
402            uni_pixels_per_unit:            DEFAULT_PIXELS_PER_UNIT
403          };
404          glium_frame.draw (
405            draw3d.instance_vertices.slice (instances_range).unwrap(),
406            glium::index::IndicesSource::NoIndices {
407              primitives: glium::index::PrimitiveType::Points
408            },
409            &render.resource.shader_programs [
410              shader::ProgramId::WorldSpace3dSprite as usize
411            ],
412            &uniforms,
413            &draw_parameters_viewport
414          ).unwrap();
415        }
416      }
417    } // end loop over viewports
418  } // end draw
419
420  #[inline]
421  pub const fn instance_vertices (&self)
422    -> &glium::VertexBuffer <vertex::Vert3dOrientationScaleColor>
423  {
424    &self.instance_vertices
425  }
426
427  #[inline]
428  pub const fn instanced_aabb_lines (&self) -> &std::ops::Range <u32> {
429    &self.instanced_aabb_lines
430  }
431
432  #[inline]
433  pub fn set_instanced_aabb_lines (&mut self, range : std::ops::Range <u32>) {
434    debug_assert!(range.end <= self.instance_vertices.len() as u32);
435    self.instanced_aabb_lines = range;
436  }
437
438  #[inline]
439  pub const fn instanced_aabb_triangles (&self) -> &std::ops::Range <u32> {
440    &self.instanced_aabb_triangles
441  }
442
443  #[inline]
444  pub fn set_instanced_aabb_triangles (&mut self, range : std::ops::Range <u32>) {
445    debug_assert!(range.end <= self.instance_vertices.len() as u32);
446    self.instanced_aabb_triangles = range;
447  }
448
449  #[inline]
450  pub const fn instanced_meshes (&self) -> &VecMap <InstancedMesh> {
451    &self.instanced_meshes.meshes
452  }
453
454  #[inline]
455  pub fn set_instanced_mesh (&mut self,
456    mesh_key : MeshKey, mesh : InstancedMesh
457  ) {
458    let _ = self.instanced_meshes.meshes.insert (mesh_key.index(), mesh);
459  }
460
461  /// Re-builds instanced buffers to include given instanced meshes. The
462  /// provided instanced mesh keys must not overlap with any of the built-in
463  /// `MeshId`s.
464  pub fn rebuild_instanced_meshes (&mut self,
465    glium_display : &glium::Display <glutin::surface::WindowSurface>,
466    user_meshes   : Vec <(MeshKey, Mesh, DrawPass)>
467  ) {
468    self.instanced_meshes = InstancedMeshes::new (glium_display, user_meshes);
469  }
470
471  /// Creates a new vertex buffer holding per-instance 3D vertices for various
472  /// instanced meshes (grid, hemisphere, sphere, cylinder, capsule), AABBs, and
473  /// tile billboards, and sets the instanced ranges corresponding to each
474  pub fn instance_vertices_set (&mut self,
475    glium_display  : &glium::Display <glutin::surface::WindowSurface>,
476    instances_init : InstancesInit
477  ) {
478    let mut instance_vertices =
479      Vec::with_capacity (instances_init.instance_count());
480
481    self.instanced_aabb_lines     = 0..0;
482    self.instanced_aabb_triangles = 0..0;
483    for mesh in self.instanced_meshes.meshes.values_mut() {
484      mesh.instances_range = 0..0;
485    }
486    // aabbs: we allow the ranges for lines and triangles to overlap so that
487    // aabb_lines_and_triangles can be placed between the lines-only and
488    // triangles-only ranges
489    if let Some (instances) = instances_init.aabb_lines {
490      // lines only
491      let start = instance_vertices.len() as u32;
492      instance_vertices.extend_from_slice (instances);
493      let end   = instance_vertices.len() as u32;
494      self.instanced_aabb_lines = start..end;
495    }
496    if let Some (instances) = instances_init.aabb_lines_and_triangles {
497      // lines and triangles
498      let start = instance_vertices.len() as u32;
499      instance_vertices.extend_from_slice (instances);
500      let end   = instance_vertices.len() as u32;
501      if self.instanced_aabb_lines.end != 0 {
502        debug_assert!(instances_init.aabb_lines.is_none());
503        self.instanced_aabb_lines.start = start;
504      }
505      self.instanced_aabb_lines.end = end;
506      self.instanced_aabb_triangles = start..end;
507    }
508    if let Some (instances) = instances_init.aabb_triangles {
509      // triangles only
510      let start = instance_vertices.len() as u32;
511      instance_vertices.extend_from_slice (instances);
512      let end   = instance_vertices.len() as u32;
513      if self.instanced_aabb_triangles.end == 0 {
514        debug_assert!(instances_init.aabb_lines_and_triangles.is_none());
515        self.instanced_aabb_triangles.start = start;
516      }
517      self.instanced_aabb_triangles.end = end;
518    }
519
520    // shaded spheres
521    if let Some (instances) = instances_init.spheres_raycast {
522      let start = instance_vertices.len() as u32;
523      instance_vertices.extend_from_slice (instances);
524      let end   = instance_vertices.len() as u32;
525      self.instanced_spheres_raycast = start..end;
526    }
527
528    // meshes
529    for (mesh_key, instances) in instances_init.meshes.into_iter() {
530      let start = instance_vertices.len() as u32;
531      instance_vertices.extend_from_slice (instances);
532      let end   = instance_vertices.len() as u32;
533      self.instanced_meshes.meshes[mesh_key].instances_range = start..end;
534    }
535
536    // billboards
537    for (billboard_key, instances) in instances_init.billboards.into_iter() {
538      let start = instance_vertices.len() as u32;
539      instance_vertices.extend_from_slice (instances);
540      let end   = instance_vertices.len() as u32;
541      self.instanced_billboards[billboard_key].instances_range = start..end;
542    }
543
544    // create per instance vertex buffer
545    self.instance_vertices = glium::VertexBuffer::dynamic (
546      glium_display, instance_vertices.as_slice()
547    ).unwrap();
548  }
549
550  #[inline]
551  pub fn instance_vertices_aabb_lines_write (&self,
552    vertex_data : &[vertex::Vert3dOrientationScaleColor]
553  ) {
554    let range =
555      self.instanced_aabb_lines.start as usize..
556      self.instanced_aabb_lines.end   as usize;
557    self.instance_vertices.slice (range).unwrap().write (vertex_data);
558  }
559
560  #[inline]
561  pub fn instance_vertices_aabb_triangles_write (&self,
562    vertex_data : &[vertex::Vert3dOrientationScaleColor]
563  ) {
564    let range =
565      self.instanced_aabb_triangles.start as usize..
566      self.instanced_aabb_triangles.end   as usize;
567    self.instance_vertices.slice (range).unwrap().write (vertex_data);
568  }
569
570  #[inline]
571  pub fn instance_vertices_spheres_raycast_write (&self,
572    vertex_data : &[vertex::Vert3dOrientationScaleColor]
573  ) {
574    let range =
575      self.instanced_spheres_raycast.start as usize..
576      self.instanced_spheres_raycast.end   as usize;
577    self.instance_vertices.slice (range).unwrap().write (vertex_data);
578  }
579
580  #[inline]
581  pub fn instance_vertices_billboard_write (&self,
582    billboard_key : usize,
583    vertex_data   : &[vertex::Vert3dOrientationScaleColor]
584  ) {
585    let range = {
586      let range = self.instanced_billboards[billboard_key].instances_range.clone();
587      range.start as usize..range.end as usize
588    };
589    self.instance_vertices.slice (range).unwrap().write (vertex_data);
590  }
591
592  #[inline]
593  pub fn instance_vertices_mesh_write (&self,
594    mesh_key    : usize,
595    vertex_data : &[vertex::Vert3dOrientationScaleColor]
596  ) {
597    let range = {
598      let range = self.instanced_meshes.meshes[mesh_key].instances_range.clone();
599      range.start as usize..range.end as usize
600    };
601    self.instance_vertices.slice (range).unwrap().write (vertex_data);
602  }
603
604  /// Create a new instanced billboard by blitting a string of tiles to a new
605  /// texture.
606  ///
607  /// Panics if billboard texture with key already exists.
608  pub fn tile_billboard_create (&mut self,
609    glium_display   : &glium::Display <glutin::surface::WindowSurface>,
610    tileset_texture : &glium::Texture2d,
611    billboard_key   : usize,
612    instances_range : std::ops::Range <u32>,
613    string          : &str
614  ) {
615    use glium::Surface;
616    debug_assert_eq!(0, tileset_texture.width() %16);
617    debug_assert_eq!(0, tileset_texture.height()%16);
618    let tile_width  = tileset_texture.width()  / 16;
619    let tile_height = tileset_texture.height() / 16;
620    let texture = glium::Texture2d::empty_with_mipmaps (
621      glium_display, glium::texture::MipmapsOption::NoMipmap,
622      tile_width * string.len() as u32, tile_height
623    ).unwrap();
624    { // blit tiles from default tileset
625      let source     = tileset_texture.as_surface();
626      let mut target = texture.as_surface();
627      target.clear_color (1.0, 0.0, 1.0, 1.0);
628      for (i,ch) in string.chars().enumerate() {
629        let tile = ch as u32;
630        // NB: (0,0) is bottom-left in texture pixel space
631        let source_rect = glium::Rect {
632          left:   tile_width  * (tile % 16),
633          bottom: tile_height * (15 - tile / 16),
634          width:  tile_width,
635          height: tile_height
636        };
637        #[expect(clippy::identity_op)]
638        let target_rect = glium::BlitTarget {
639          left:   0 + i as u32 * tile_width,
640          bottom: 0,
641          width:  tile_width  as i32,
642          height: tile_height as i32
643        };
644        target.blit_from_simple_framebuffer (
645          &source, &source_rect, &target_rect,
646          glium::uniforms::MagnifySamplerFilter::Linear);
647      }
648    }
649    let instanced_billboard = InstancedBillboard { texture, instances_range };
650    assert!(self.instanced_billboards.insert (
651      billboard_key, instanced_billboard
652    ).is_none());
653  }
654}
655
656impl InstancesInit <'_> {
657  pub fn instance_count (&self) -> usize {
658    let mut count = 0;
659    count += self.aabb_lines.map_or(0, <[_]>::len);
660    count += self.aabb_triangles.map_or(0, <[_]>::len);
661    for (_, mesh_data) in self.meshes.iter() {
662      count += mesh_data.len();
663    }
664    for (_, billboard_data) in self.billboards.iter() {
665      count += billboard_data.len();
666    }
667    count
668  }
669}
670
671impl InstancedMeshes {
672  fn new (
673    glium_display : &glium::Display <glutin::surface::WindowSurface>,
674    user_meshes   : Vec <(MeshKey, Mesh, DrawPass)>
675  ) -> Self {
676    let InstancedMeshInit {
677      instanced_vertex_data,
678      instanced_line_index_data,
679      line_indices_ranges,
680      shader_program_ids,
681      draw_passes,
682      capsule_vertex_id_offset,
683      hemisphere_vertex_count
684    } = InstancedMeshInit::new (user_meshes);
685    let vertices     = glium::VertexBuffer::dynamic (
686      glium_display, instanced_vertex_data.as_slice()
687    ).unwrap();
688    let line_indices = glium::IndexBuffer::dynamic (
689      glium_display, glium::index::PrimitiveType::LinesList,
690      instanced_line_index_data.as_slice()
691    ).unwrap();
692    let meshes       = {
693      let mut v = VecMap::new();
694      for (mesh_key, line_indices_range) in line_indices_ranges.into_iter() {
695        let instanced_mesh = InstancedMesh {
696          line_indices_range,
697          instances_range: 0..0,  // empty
698          shader_program_id: shader_program_ids[mesh_key],
699          draw_pass:         draw_passes[mesh_key]
700        };
701        assert!(v.insert (mesh_key, instanced_mesh).is_none());
702      }
703      v
704    };
705    InstancedMeshes {
706      vertices, line_indices, meshes, capsule_vertex_id_offset,
707      hemisphere_vertex_count
708    }
709  }
710}
711
712impl InstancedMeshInit {
713  fn new (user_meshes : Vec <(MeshKey, Mesh, DrawPass)>) -> Self {
714    const GRID_DIMS                 : u16 = MESH_GRID_DIMS;
715    const HEMISPHERE_LATITUDE_DIVS  : u16 = 6;
716    const SPHERE_LATITUDE_DIVS      : u16 = 2 * HEMISPHERE_LATITUDE_DIVS;
717    const LONGITUDE_DIVS            : u16 = 24;
718    const CYLINDER_DIVS             : u16 = LONGITUDE_DIVS;
719    let mut instanced_vertex_data     = Vec::new();
720    let mut instanced_line_index_data = Vec::new();
721    // constant value to shift all indices during mesh creation
722    let mut index_offset      = 0;
723    // the beginning of the range where indices begin for the next mesh
724    let mut index_range_start = 0;
725
726    let mut line_indices_ranges = VecMap::with_capacity (MeshId::COUNT);
727    let mut shader_program_ids  = VecMap::with_capacity (MeshId::COUNT);
728    let mut draw_passes         = VecMap::with_capacity (MeshId::COUNT);
729
730    // grid
731    let mesh::Lines3d {
732      vertices: mut grid_vertex_data, indices: mut grid_index_data
733    } = mesh::Lines3d::grid (index_offset, GRID_DIMS);
734    assert!(line_indices_ranges.insert (
735      MeshId::Grid as usize,
736      index_range_start..index_range_start + grid_index_data.len() as u32
737    ).is_none());
738    assert!(shader_program_ids.insert (
739      MeshId::Grid as usize,
740      shader::ProgramId::ModelSpace3dInstancedOrientationScaleColor
741    ).is_none());
742    assert!(draw_passes.insert (MeshId::Grid as usize, DrawPass::Depth).is_none());
743    instanced_vertex_data.append (&mut grid_vertex_data);
744    instanced_line_index_data.append  (&mut grid_index_data);
745    index_offset      = instanced_vertex_data.len() as u32;
746    index_range_start = instanced_line_index_data.len() as u32;
747
748    // capsule: vertex and index data will be shared with sphere and hemisphere
749    // meshes
750    let capsule_vertex_id_offset = index_offset; // uniform used by capsule shader
751    let mesh::Lines3d {
752      vertices: mut capsule_vertex_data,
753      indices:  mut capsule_index_data
754    } = mesh::Lines3d::capsule (index_offset, HEMISPHERE_LATITUDE_DIVS, LONGITUDE_DIVS);
755    let capsule_line_indices_range =
756      index_range_start..index_range_start + capsule_index_data.len() as u32;
757    assert!(line_indices_ranges.insert (
758      MeshId::Capsule as usize, capsule_line_indices_range.clone()
759    ).is_none());
760    assert!(shader_program_ids.insert (
761      MeshId::Capsule as usize,
762      shader::ProgramId::ModelSpace3dInstancedCapsule
763    ).is_none());
764    assert!(draw_passes.insert (MeshId::Capsule as usize, DrawPass::Depth).is_none());
765    instanced_vertex_data.append (&mut capsule_vertex_data);
766    instanced_line_index_data.append  (&mut capsule_index_data);
767
768    index_offset      = instanced_vertex_data.len() as u32;
769    index_range_start = instanced_line_index_data.len() as u32;
770
771    // hemisphere: this is defined to share the top half of the vertices and
772    // indices of the capsule mesh
773    let (hemisphere_vertex_count, hemisphere_index_count) =
774      mesh::hemisphere::lines3d_vertex_index_counts (
775        HEMISPHERE_LATITUDE_DIVS, LONGITUDE_DIVS);
776    assert!(line_indices_ranges.insert (
777      MeshId::Hemisphere as usize,
778      capsule_line_indices_range.start..
779      capsule_line_indices_range.start + hemisphere_index_count
780    ).is_none());
781    assert!(shader_program_ids.insert (
782      MeshId::Hemisphere as usize,
783      shader::ProgramId::ModelSpace3dInstancedScaleColor
784    ).is_none());
785    assert!(draw_passes.insert (MeshId::Hemisphere as usize, DrawPass::Depth)
786      .is_none());
787
788    // sphere: defined to use part of the capsule mesh indices except for the
789    // extra equator lines and cylinder lines
790    let (_, sphere_index_count) = mesh::sphere::lines3d_vertex_index_counts (
791      SPHERE_LATITUDE_DIVS, LONGITUDE_DIVS);
792    assert!(line_indices_ranges.insert (
793      MeshId::Sphere as usize,
794      capsule_line_indices_range.start..
795      capsule_line_indices_range.start + sphere_index_count
796    ).is_none());
797    assert!(shader_program_ids.insert (
798      MeshId::Sphere as usize,
799      shader::ProgramId::ModelSpace3dInstancedScaleColor
800    ).is_none());
801    assert!(draw_passes.insert (MeshId::Sphere as usize, DrawPass::Depth)
802      .is_none());
803
804    // cylinder
805    let mesh::Lines3d {
806      vertices: mut cylinder_vertex_data, indices: mut cylinder_index_data
807    } = mesh::Lines3d::cylinder (index_offset, CYLINDER_DIVS);
808    assert!(line_indices_ranges.insert (
809      MeshId::Cylinder as usize,
810      index_range_start..index_range_start + cylinder_index_data.len() as u32
811    ).is_none());
812    assert!(shader_program_ids.insert (
813      MeshId::Cylinder as usize,
814      shader::ProgramId::ModelSpace3dInstancedScaleColor
815    ).is_none());
816    assert!(draw_passes.insert (MeshId::Cylinder as usize, DrawPass::Depth)
817      .is_none());
818    instanced_vertex_data.append (&mut cylinder_vertex_data);
819    instanced_line_index_data.append  (&mut cylinder_index_data);
820
821    index_offset      = instanced_vertex_data.len()     as u32;
822    index_range_start = instanced_line_index_data.len() as u32;
823
824    // cube
825    let mesh::Lines3d {
826      vertices: mut cube_vertex_data, indices: mut cube_index_data
827    } = mesh::Lines3d::cube (index_offset);
828    assert!(line_indices_ranges.insert (
829      MeshId::Cube as usize,
830      index_range_start..index_range_start + cube_index_data.len() as u32
831    ).is_none());
832    assert!(shader_program_ids.insert (
833      MeshId::Cube as usize,
834      shader::ProgramId::ModelSpace3dInstancedOrientationScaleColor
835    ).is_none());
836    assert!(draw_passes.insert (MeshId::Cube as usize, DrawPass::Depth).is_none());
837    instanced_vertex_data.append (&mut cube_vertex_data);
838    instanced_line_index_data.append  (&mut cube_index_data);
839
840    // NB: the following is not required between meshes that share all vertices
841    // comment this out between each new mesh when adding more meshes here:
842    //index_offset      = instanced_vertex_data.len()     as u32;
843    //index_range_start = instanced_line_index_data.len() as u32;
844
845    // user meshes
846    let num_user_meshes = user_meshes.len();
847    for (mesh_key, mut mesh, draw_pass) in user_meshes {
848      debug_assert!(mesh_key.index() >= MeshId::COUNT);
849      index_offset      = instanced_vertex_data.len()     as u32;
850      index_range_start = instanced_line_index_data.len() as u32;
851      let mut insert_mesh = |vertices : &mut Vec <_>, indices : &mut Vec <u32>| {
852        indices.iter_mut().for_each (|ix| *ix += index_offset);
853        assert!(line_indices_ranges.insert (
854          mesh_key.index(),
855          index_range_start..index_range_start + indices.len() as u32
856        ).is_none());
857        assert!(shader_program_ids.insert (
858          mesh_key.index(),
859          shader::ProgramId::ModelSpace3dInstancedOrientationScaleColor
860        ).is_none());
861        assert!(draw_passes.insert (mesh_key.index(), draw_pass).is_none());
862        instanced_vertex_data.append (vertices);
863        instanced_line_index_data.append (indices);
864      };
865      match mesh {
866        Mesh::Points3d (mesh::Points3d { ref mut vertices, ref mut indices }) |
867        Mesh::Lines3d (mesh::Lines3d { ref mut vertices, ref mut indices }) |
868        Mesh::Triangles3d (mesh::Triangles3d { ref mut vertices, ref mut indices }) =>
869          insert_mesh (vertices, indices)
870      }
871    }
872
873    debug_assert_eq!(line_indices_ranges.len(), MeshId::COUNT + num_user_meshes);
874    debug_assert_eq!(shader_program_ids.len(),  MeshId::COUNT + num_user_meshes);
875
876    InstancedMeshInit {
877      instanced_vertex_data,
878      instanced_line_index_data,
879      line_indices_ranges,
880      shader_program_ids,
881      draw_passes,
882      capsule_vertex_id_offset,
883      hemisphere_vertex_count
884    }
885  }
886}
887
888impl MeshKey {
889  pub const fn index (self) -> usize {
890    self.0 as usize
891  }
892}
893
894impl UserBufferKey {
895  pub const fn index (self) -> usize {
896    self.0 as usize
897  }
898}