plexus 0.0.4

3D mesh generation and manipulation.
Documentation

Plexus

Plexus is a Rust library for generating and manipulating 3D meshes.

Build Status Documentation Crate

Generation and Iterator Expressions

Meshes can be generated from primitives like cubes and spheres using iterator expressions. Primitives emit topological structures like Triangles or Quads, which contain arbitrary geometric data in their vertices. These can be transformed and decomposed into other topologies and geometric data via triangulation, tesselation, and conversion into rendering pipeline data.

use nalgebra::Point3;
use plexus::buffer::MeshBuffer;
use plexus::generate::sphere;
use plexus::prelude::*;

// Example module in the local crate that provides basic rendering.
use render::{self, Color, Vertex};

// Construct a buffer of index and vertex data from a sphere primitive.
let buffer = sphere::UVSphere::<f32>::with_unit_radius(16, 16)
    .polygons_with_position()
    .map_vertices(|position| -> Point3<_> { position.into() })
    .map_vertices(|position| position * 10.0)
    .map_vertices(|position| Vertex::new(position, Color::white()))
    .collect::<MeshBuffer<u32, Vertex>>();
render::draw(buffer.as_index_slice(), buffer.as_vertex_slice());

Half-Edge Graph Meshes

Generators are flexible and easy to use, but only represent vertex geometry and are difficult to query and manipulate. A Mesh, represented as a half-edge graph, supports arbitrary geometry for vertices, edges, and faces. The graph can also be queried and manipulated in ways that generators and iterator expressions cannot.

use nalgebra::Point3;
use plexus::generate::sphere;
use plexus::graph::Mesh;
use plexus::prelude::*;

// Construct a mesh from a sphere primitive. The vertex geometry is convertible
// to `Point3` via the `FromGeometry` trait in this example.
let mut mesh = sphere::UVSphere::<f32>::with_unit_radius(8, 8)
    .polygons_with_position()
    .map_vertices(|position| position.into_hash())
    .collect::<Mesh<Point3<f32>>>();
// Extrude a face in the mesh.
let key = mesh.faces().nth(0).unwrap().key();
let face = mesh.face_mut(key).unwrap().extrude(1.0).unwrap();

Geometric Traits

Meshes support arbitrary geometry via optional traits. Implementing these traits allows more operations to be supported, but only two basic traits are required: Geometry and Attribute.

use nalgebra::{Point3, Vector3};
use plexus::geometry::{Attribute, Geometry};
use plexus::geometry::convert::AsPosition;

#[derive(Clone, Copy)]
pub struct VertexGeometry {
    pub position: Point3<f32>,
    pub normal: Vector3<f32>,
}

impl Attribute for VertexGeometry {}

impl Geometry for VertexGeometry {
    type Vertex = Self;
    type Edge = ();
    type Face = ();
}

impl AsPosition for VertexGeometry {
    type Target = Point3<f32>;

    fn as_position(&self) -> &Self::Target {
        &self.position
    }

    fn as_position_mut(&mut self) -> &mut Self::Target {
        &mut self.position
    }
}

Geometric traits are optionally implemented for types in the nalgebra and cgmath crates so that common types can be used right away for vertex geomtry.

Hashing Floating-Point Values

When collecting an iterator expression into a graph or buffer, an indexer is used to transform the geometry into raw buffers. HashIndexer is fast and reliable, and is used by collect (which can be overridden via collect_with_indexer). However, geometry often contains floating point values, which do not implement Hash. An LruIndexer can also be used, but can be slower and requires a sufficient capacity to work correctly.

The ordered-float crate is used by the ordered module to ease this problem. Common geometric types implement traits that provide conversions to and from a conjugate type that implements Hash (via the into_hash and from_hash functions). Some geometric types can be constructed from these conjugate types, as seen in the Mesh example.

The ordered module also exposes some hashing functions for floating point primitives, which can be used to directly implement Hash. With the derivative crate, floating point fields can be hashed using one of these functions while deriving Hash. The Vertex type used in the above example could be defined as follows:

use plexus::ordered;

#[derive(Derivative)]
#[derivative(Hash)]
pub struct Vertex {
    #[derivative(Hash(hash_with="ordered::hash_float_array"))]
    pub position: [f32; 3],
    ...
}