odf / elm-mesh / Mesh

This module provides functions for working with indexed meshes. You can:


type Mesh vertex

A Mesh is an instance of a polygon mesh which is implemented as a so-called half-edge data structure. Each mesh consists of an array of vertices and some connectivity information that describes how faces, edges and vertices are related to each other. A face must have at least three vertices, but can also have four, five or more.

The half-edge data structure guarantees that the mesh describes a surface with a consistent "orientation", which intuitively means that if one were to walk along on the surface of the mesh one could never find oneself back at the starting point but standing upside-down on the other side of the mesh.

The vertices themselves can be any type you want. For a 2D mesh, you might have each vertex be simply a point:

type alias Mesh2d =
    Mesh Point2d

For a 3D mesh, each vertex might be a (point, normal) tuple:

type alias Mesh3d =
    Mesh ( Point3d, Vector3d )

In more complex cases, each vertex might be a record:

type alias VertexData =
    { position : Point3d
    , normal : Vector3d
    , color : Color
    }

type alias RenderMesh =
    Mesh VertexData

Constants

empty : Mesh vertex

A mesh with no vertices or faces.

Constructors

fromOrientedFaces : Array vertex -> List (List Basics.Int) -> Result String (Mesh vertex)

Create a mesh from an array of vertices and a list of face indices. For example, to construct a square pyramid where a is the front right corner, b is the front left corner, c is the back left corner, d is the back right corner and e is the apex:

vertices =
    Array.fromList [ a, b, c, d, e ]

faceIndices =
    [ [ 0, 1, 2, 3 ]
    , [ 0, 4, 1 ]
    , [ 1, 4, 2 ]
    , [ 2, 4, 3 ]
    , [ 3, 4, 0 ]
    ]

pyramid =
    Mesh.fromOrientedFaces vertices faceIndices
        |> Result.withDefault Mesh.empty

The return type here is a Result String (Mesh vertex) rather than just a Mesh vertex so that a specific error message can be produced if there is a problem with the input.

grid : Basics.Int -> Basics.Int -> (Basics.Float -> Basics.Float -> vertex) -> Mesh vertex

Construct a mesh in the form of a rectangular grid. This is useful for constructing things like terrain meshes given a height function, or a parametric surface given a function that computes a 3D point (and perhaps a normal vector) from U and V parameter values.

The arguments are the number of steps to take in the U and V directions, and a function that takes U and V values (which each range between 0 and 1) and returns some sort of vertex value. A mesh will then be constructed will all vertices correctly connected to each other.

Special grids

These functions work similarly to grid but let you construct shapes like cylindrical tubes, spheres or toruses, where some edges of the grid 'wrap around' and join up with other edges. For example, a cylindrical tube (a cylinder without ends) can be thought of as a piece of paper curled around so that one edge touches the other.

These functions ensure that in cases like a cylindrical tube, there's actually only one set of vertices along the shared edge that is then referenced by the faces on either side. Roughly speaking, this is the difference between a polyline where the last vertex happens to be the same as the first (and so looks like a closed polygon, but isn't actually connected) and a proper polygon where the last vertex is actually connected back to the first.

Note that these functions can be used to create meshes that represent actual cylinders, spheres, and toruses, but they can also be used to make any mesh that is topologically equivalent to one of those. For example, the ball function can be used to create meshes for both spheres and ellipsoids.

tube : Basics.Int -> Basics.Int -> (Basics.Float -> Basics.Float -> vertex) -> Mesh vertex

Construct a mesh that is topologically equivalent to a cylinder, where the U parameter value is along the axis of the sphere and the V parameter value is around the circumference. The mesh will wrap in the V direction, so the provided function will never be called with V=1; instead, the last vertices in the V direction will connect back to the first ones.

If you wanted to construct a 5 meter long, 2 meter radius cylindrical mesh along the X axis, you might do something like

import Mesh

Mesh.tube 1 72 <|
    \u v ->
        let
            theta =
                2 * pi * v
        in
        { x = 5 * u
        , y = 2 * sin theta
        , z = 2 * cos theta
        }

ring : Basics.Int -> Basics.Int -> (Basics.Float -> Basics.Float -> vertex) -> Mesh vertex

Construct a mesh that is topologically equivalent to a torus. This is similar to tube except that the mesh wraps in both the U and V directions.

ball : Basics.Int -> Basics.Int -> (Basics.Float -> Basics.Float -> vertex) -> Mesh vertex

Construct a mesh that is topologically equivalent to a sphere; the resulting mesh will have the basic sphere structure as shown here with U corresponding to Θ and V corresponding to Φ (with V=0 meaning the bottom point on the sphere or 'south pole' and V=1 meaning the top point on the sphere or 'north pole').

Indexed grids

These functions work like their non-indexed versions, but the function gets passed the indices of individual vertices instead of their parameter values.

indexedGrid : Basics.Int -> Basics.Int -> (Basics.Int -> Basics.Int -> vertex) -> Mesh vertex

indexedTube : Basics.Int -> Basics.Int -> (Basics.Int -> Basics.Int -> vertex) -> Mesh vertex

indexedRing : Basics.Int -> Basics.Int -> (Basics.Int -> Basics.Int -> vertex) -> Mesh vertex

indexedBall : Basics.Int -> Basics.Int -> (Basics.Int -> Basics.Int -> vertex) -> Mesh vertex

Interop

fromTriangularMesh : TriangularTriangularMesh vertex -> Result String (Mesh vertex)

Try to convert a TriangularMesh instance into a Mesh. This could potentially fail because a Mesh is more restrictive.

toTriangularMesh : Mesh vertex -> TriangularTriangularMesh vertex

Triangulate each face of the mesh and return the result as a TriangularMesh instance.

Combining and splitting meshes

combine : List (Mesh a) -> Mesh a

Combine a list of meshes into a single mesh. This concatenates the vertex arrays of each mesh and adjusts face indices to refer to the combined vertex array.

pyramid =
    Mesh.fromOrientedFaces
        (Array.fromList [ a, b, c, d, e ])
        [ [ 0, 1, 2, 3 ]
        , [ 0, 4, 1 ]
        , [ 1, 4, 2 ]
        , [ 2, 4, 3 ]
        , [ 3, 4, 0 ]
        ]

tetrahedron =
    Mesh.fromOrientedFaces
        (Array.fromList [ A, B, C, D ])
        [ [ 0, 1, 2 ]
        , [ 0, 3, 1 ]
        , [ 1, 3, 2 ]
        , [ 2, 3, 0 ]
        ]

combined =
    Mesh.combine [ pyramid, tetrahedron ]

Mesh.vertices combined
--> Array.fromList [ a, b, c, d, e, A, B, C, D ]

Mesh.faceIndices combined
--> [ [ 0, 1, 2, 3 ]
--> , [ 0, 4, 1 ]
--> , [ 1, 4, 2 ]
--> , [ 2, 4, 3 ]
--> , [ 0, 3, 4 ]
--> , [ 5, 6, 7 ]
--> , [ 5, 8, 6 ]
--> , [ 6, 8, 7 ]
--> , [ 5, 7, 8 ]
--> ]

filterFaces : (List vertex -> Basics.Bool) -> Mesh vertex -> Mesh vertex

Keep faces that satisfy the test and vertices in those faces.

Properties

vertices : Mesh vertex -> Array vertex

Get the vertices of a mesh.

Mesh.vertices pyramid
--> Array.fromList [ a, b, c, d, e ]

vertex : Basics.Int -> Mesh vertex -> Maybe vertex

Get a particular vertex of a mesh by index. If the index is out of range, returns Nothing.

Mesh.vertex 1 pyramid
--> Just b

Mesh.vertex 5 pyramid
--> Nothing

faceIndices : Mesh vertex -> List (List Basics.Int)

Get the faces of a mesh as lists of vertex indices. Each face is normalized so that its smallest vertex index comes first.

Mesh.faceIndices pyramid
--> [ [ 0, 1, 2, 3 ]
--> , [ 0, 4, 1 ]
--> , [ 1, 4, 2 ]
--> , [ 2, 4, 3 ]
--> , [ 0, 3, 4 ]
--> ]

faceVertices : Mesh vertex -> List (List vertex)

Get the faces of a mesh as lists of vertices. The ordering of vertices for each face is the same as in faceIndices.

Mesh.faceVertices pyramid
--> [ [ a, b, c, d ]
--> , [ a, e, b ]
--> , [ b, e, c ]
--> , [ c, e, d ]
--> , [ a, d, e ]
--> ]

boundaryIndices : Mesh vertex -> List (List Basics.Int)

Get the boundary components of a mesh as lists of vertex indices. Each component is normalized so that its smallest vertex index comes first.

pyramidTop =
    Mesh.fromOrientedFaces
        (Array.fromList [ a, b, c, d, e ])
        [ [ 0, 4, 1 ]
        , [ 1, 4, 2 ]
        , [ 2, 4, 3 ]
        , [ 3, 4, 0 ] ]
        |> Result.withDefault Mesh.empty

Mesh.faceIndices pyramidTop
--> [ [ 0, 4, 1 ]
--> , [ 1, 4, 2 ]
--> , [ 2, 4, 3 ]
--> , [ 0, 3, 4 ]
--> ]

Mesh.boundaryIndices pyramidTop
--> [ [ 0, 1, 2, 3 ] ]

boundaryVertices : Mesh vertex -> List (List vertex)

Get the boundary components of a mesh as lists of vertices. The ordering of vertices for each component is the same as in faceIndices.

Mesh.boundaryVertices pyramidTop
--> [ [ a, b, c, d ] ]

edgeIndices : Mesh vertex -> List ( Basics.Int, Basics.Int )

Get all of the edges of a mesh as pairs of vertex indices. Each edge will only be returned once, with the lower-index vertex listed first, and will be returned in sorted order.

Mesh.edgeIndices pyramid
--> [ ( 0, 1 )
--> , ( 0, 3 )
--> , ( 0, 4 )
--> , ( 1, 2 )
--> , ( 1, 4 )
--> , ( 2, 3 )
--> , ( 2, 4 )
--> , ( 3, 4 )
--> ]

edgeVertices : Mesh vertex -> List ( vertex, vertex )

Get all of the edges of a mesh as pairs of vertices. Each edge will only be returned once, with the lower-index vertex listed first, and will be returned in sorted order (by index).

Mesh.edgeVertices pyramid
--> [ ( a, b )
--> , ( a, d )
--> , ( a, e )
--> , ( b, c )
--> , ( b, e )
--> , ( c, d )
--> , ( c, e )
--> , ( d, e )
--> ]

neighborIndices : Mesh vertex -> Array (List Basics.Int)

Get the neighbors of all vertices as lists of vertex indices. The i-th entry in the output array corresponds to the i-th vertex of the mesh. The neighbors are ordered as one would encounter them while walking around the vertex in the same direction (clockwise or counterclockwise) as the vertices for each face are listed, starting from the one with the lowest vertex index.

Mesh.neighborIndices pyramid
--> [ [ 1, 3, 4 ]
--> , [ 0, 4, 2 ]
--> , [ 1, 4, 3 ]
--> , [ 0, 2, 4 ]
--> , [ 0, 3, 2, 1 ]
--> ]

neighborVertices : Mesh vertex -> Array (List vertex)

Get the neighbors of all vertices as lists of vertices. Further details are as in neighborIndices.

Transformations

mapVertices : (a -> b) -> Mesh a -> Mesh b

Transform a mesh by applying the given function to each of its vertices. For example, if you had a 2D mesh where each vertex was an ( x, y ) tuple and you wanted to convert it to a 3D mesh on the XY plane, you might use

mesh2d : Mesh ( Float, Float )
mesh2d =
    ...

to3d : ( Float, Float ) -> ( Float, Float, Float )
to3d ( x, y ) =
    ( x, y, 0 )

mesh3d : Mesh ( Float, Float, Float )
mesh3d =
    Mesh.mapVertices to3d mesh2d

withNormals : (a -> Point3d units coordinates) -> (a -> Vector3d Quantity.Unitless coordinates -> b) -> Mesh a -> Mesh b

Calculate normals for a mesh. The first argument is a function that takes a vertex of the input mesh and returns its position as a Point3d. The second argument is a function that takes a vertex of the input mesh and its computed normal as a Vector3d, and returns the corresponding vertex for the output mesh.

subdivide : (List vertex -> vertex) -> Mesh vertex -> Mesh vertex

Subdivide a mesh by first adding a new vertex in the center of each edge, subdividing each edge into two in the process, then adding a new vertex in the center of each face and splitting the face into quadrangles by connecting the new face center to the new edge centers.

The first argument is a function that takes a list of input vertices, namely the vertices of an edge or face to be subdivided, and returns the new "center" vertex.

subdivideSmoothly : (vertex -> Basics.Bool) -> (vertex -> Point3d units coordinates) -> (List vertex -> Point3d units coordinates -> vertex) -> Mesh vertex -> Mesh vertex

Subdivide a mesh into quadrangles using the Catmull-Clark method. The connectivity of the result will be the same as in subdivide, but the vertices will be repositioned to make it appear smoother.

The first argument is a function that returns True if the input vertex should not be moved. The second argument is a function that returns the position of an input vertex. The third argument is a function that takes a list of input vertices and a computed output position and returns an output vertex.

extrude : (vertex -> vertex) -> Mesh vertex -> Mesh vertex

Extrude a mesh. This adds a duplicate of the original mesh with the face orientations reversed and the provided function applied to the vertices. The duplicate is then connected to the original along any boundary components. For example, if the original mesh is a hexagon in the xy plane, and the vertex mapping moves each vertex up along the z-direction, the result is a hexagonal prism.