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