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