nsys-gl-utils 0.11.10

OpenGL and graphics utilities
Documentation
//! Mesh generation utilities.

use std::path::Path;
use bytemuck;
use derive_more::{From, TryInto};
use gltf;
use num_traits::NumCast;
use strum;
use crate::{math, math::geometry, util, vertex};

pub mod capsule;
pub mod cube;
pub mod cylinder;
pub mod grid;
pub mod hemisphere;
pub mod sphere;

#[derive(Clone, Debug, PartialEq, From, TryInto)]
pub enum Mesh {
  Points3d (Points3d),
  Lines3d  (Lines3d),
  Triangles3d (Triangles3d)
}

/// Raw data defining a point-cloud mesh
#[derive(Clone, Debug, PartialEq, From)]
pub struct Points3d {
  pub vertices : Vec <vertex::Vert3dInstanced>,
  pub indices  : Vec <u32>
}

/// Raw data defining a wireframe mesh
#[derive(Clone, Debug, PartialEq, From)]
pub struct Lines3d {
  pub vertices : Vec <vertex::Vert3dInstanced>,
  pub indices  : Vec <u32>
}

/// Raw data defining a triangle mesh
#[derive(Clone, Debug, PartialEq, From)]
pub struct Triangles3d {
  pub vertices : Vec <vertex::Vert3dInstanced>,
  pub indices  : Vec <u32>
}

/// A builder type to allow building a Lines3d mesh by incrementally by adding points
pub struct Points3dBuilder {
  points : Points3d
}

/// A builder type to allow building a Lines3d mesh by incrementally by adding edges
pub struct Lines3dBuilder {
  lines : Lines3d
}

/// A builder type to allow building a Triangles3d mesh by incrementally by adding faces
pub struct Triangles3dBuilder {
  triangles : Triangles3d
}

impl Mesh {
  /// Load a GLTF file containing a single mesh
  pub fn load_gltf <P : AsRef <Path>> (path : P) -> Self {
    let (document, buffers, _images) = gltf::import (path).unwrap();
    let mut meshes = document.meshes();
    let mesh = meshes.next().unwrap();
    debug_assert!(meshes.peekable().next().is_none());
    let mut primitives = mesh.primitives();
    let primitive = primitives.next().unwrap();
    debug_assert!(primitives.peekable().next().is_none());
    let reader = primitive.reader (|buffer| Some (&buffers[buffer.index()]));
    let vertices = reader.read_positions().unwrap()
      .map (|inst_position| vertex::Vert3dInstanced { inst_position }).collect();
    let indices  = reader.read_indices().unwrap().into_u32().collect();
    match primitive.mode() {
      gltf::mesh::Mode::Points => Points3d { vertices, indices }.into(),
      gltf::mesh::Mode::Lines => Lines3d { vertices, indices }.into(),
      _ => unimplemented!()
    }
  }

  /// Write mesh to an embedded `.gltf` file
  pub fn write_gltf <P : AsRef <Path>> (&self, path : P) {
    fn bounding_coords (points: &[vertex::Vert3dInstanced])
      -> ([f32; 3], [f32; 3])
    {
      let mut min = [f32::MAX, f32::MAX, f32::MAX];
      let mut max = [f32::MIN, f32::MIN, f32::MIN];
      for point in points {
        let p = point.inst_position;
        for i in 0..3 {
          min[i] = f32::min (min[i], p[i]);
          max[i] = f32::max (max[i], p[i]);
        }
      }
      (min, max)
    }
    let (vertices, indices, mode) = match self {
      Mesh::Points3d (Points3d { vertices, indices }) =>
        (vertices, indices, gltf::mesh::Mode::Points),
      Mesh::Lines3d (Lines3d { vertices, indices }) =>
        (vertices, indices, gltf::mesh::Mode::Lines),
      Mesh::Triangles3d (Triangles3d { vertices, indices }) =>
        (vertices, indices, gltf::mesh::Mode::Triangles)
    };
    let (min, max)        = bounding_coords (&vertices[..]);
    let vertices_bytes    = bytemuck::cast_slice (vertices);
    let indices_bytes     = bytemuck::cast_slice (indices);
    let (buffer, views)   =
      util::gltf::json_buffer_views (0, &[vertices_bytes, indices_bytes]);
    let position_accessor = util::gltf::json_accessor (0, vertices.len() as u32,
      gltf::json::accessor::Type::Vec3,
      gltf::json::accessor::ComponentType::F32, Some ([min, max]));
    let index_accessor    = util::gltf::json_accessor (1, indices.len() as u32,
      gltf::json::accessor::Type::Scalar,
      gltf::json::accessor::ComponentType::U32, None);
    let lines_primitive   = util::gltf::json_primitive (
      gltf::json::mesh::Semantic::Positions, mode, 1);
    let mesh              = util::gltf::json_mesh (vec![lines_primitive]);
    let node              = util::gltf::json_node (0);
    let root              = util::gltf::json_root (
      vec![node], vec![mesh], vec![buffer], views,
      vec![position_accessor, index_accessor]);
    let writer            = std::fs::File::create (path).unwrap();
    gltf::json::serialize::to_writer_pretty (writer, &root).unwrap();
  }
}

impl Points3d {
  pub const fn empty() -> Self {
    Points3d { vertices: vec![], indices: vec![] }
  }
}

impl Lines3d {
  pub const fn empty() -> Self {
    Lines3d { vertices: vec![], indices: vec![] }
  }
}

impl From <geometry::Aabb3 <f32>> for Lines3d {
  fn from (aabb : geometry::Aabb3 <f32>) -> Self {
    use strum::IntoEnumIterator;
    let vertices = math::Octant::iter().map (|octant| vertex::Vert3dInstanced {
      inst_position: aabb.corner (octant).0.into_array()
    }).collect();
    let indices  = vec![
      0, 1, 0, 2, 0, 4,
      7, 6, 7, 5, 7, 3,
      1, 3, 1, 5,
      2, 3, 2, 6,
      4, 5, 4, 6
    ];
    Lines3d { vertices, indices }
  }
}

impl Triangles3d {
  pub const fn empty() -> Self {
    Triangles3d { vertices: vec![], indices: vec![] }
  }
}

impl Points3dBuilder {
  pub const fn empty() -> Self {
    Points3dBuilder {
      points: Points3d::empty()
    }
  }

  pub fn push_point <T : NumCast> (&mut self, point : math::Point3 <T>) -> &mut Self {
    let vertex = |p : math::Point3 <T>|
      vertex::Vert3dInstanced { inst_position: p.0.numcast().unwrap().into_array() };
    self.points.indices.push (self.points.vertices.len() as u32);
    self.points.vertices.push (vertex (point));
    self
  }

  pub fn build (self) -> Points3d {
    self.points
  }
}

impl Lines3dBuilder {
  pub const fn empty() -> Self {
    Lines3dBuilder {
      lines: Lines3d::empty()
    }
  }

  pub fn push_edge <T : NumCast> (&mut self, edge : [math::Point3 <T>; 2]) -> &mut Self {
    // TODO: we are de-duplicating vertices, but checking each vertex is a linear
    // search; improve the efficiency, can we de-duplicate at the end with a single
    // pass?
    let [a, b] : [math::Point3 <f32>; 2] = edge.map (|p| p.numcast().unwrap());
    let vertex = |p : math::Point3 <f32>|
      vertex::Vert3dInstanced { inst_position: p.0.into_array() };
    let mut add_vertex = |v|
      if let Some(i) = self.lines.vertices.iter().position (|x| *x == v) {
        self.lines.indices.push (i as u32)
      } else {
        self.lines.indices.push (self.lines.vertices.len() as u32);
        self.lines.vertices.push (v);
      };
    add_vertex (vertex (a));
    add_vertex (vertex (b));
    self
  }

  pub fn build (self) -> Lines3d {
    self.lines
  }
}

impl Triangles3dBuilder {
  pub const fn empty() -> Self {
    Triangles3dBuilder {
      triangles: Triangles3d::empty()
    }
  }

  pub fn push_face <T : NumCast> (&mut self, face : [math::Point3 <T>; 3]) -> &mut Self {
    // TODO: we are de-duplicating vertices, but checking each vertex is a linear
    // search; improve the efficiency, can we de-duplicate at the end with a single
    // pass?
    let [a, b, c] : [math::Point3 <f32>; 3] = face.map (|p| p.numcast().unwrap());
    let vertex = |p : math::Point3 <f32>|
      vertex::Vert3dInstanced { inst_position: p.0.into_array() };
    let mut add_vertex = |v|
      if let Some(i) = self.triangles.vertices.iter().position (|x| *x == v) {
        self.triangles.indices.push (i as u32)
      } else {
        self.triangles.indices.push (self.triangles.vertices.len() as u32);
        self.triangles.vertices.push (v);
      };
    add_vertex (vertex (a));
    add_vertex (vertex (b));
    add_vertex (vertex (c));
    self
  }

  pub fn build (self) -> Triangles3d {
    self.triangles
  }
}