ianmackenzie / elm-triangular-mesh / TriangularMesh

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


type TriangularMesh vertex

A TriangularMesh is a simple form of face-vertex mesh defined by an array of vertices and a list of face indices. Each set of face indices consists of a tuple of three integer vertex indices.

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 =
    TriangularMesh Point2d

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

type alias Mesh3d =
    TriangularMesh ( Point3d, Vector3d )

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

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

type alias RenderMesh =
    TriangularMesh VertexData

Constants

empty : TriangularMesh vertex

A mesh with no vertices or faces.

Constructors

indexed : Array vertex -> List ( Basics.Int, Basics.Int, Basics.Int ) -> TriangularMesh vertex

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

Square mesh

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

faceIndices =
    [ ( 0, 1, 2 ), ( 0, 2, 3 ) ]

square =
    TriangularMesh.indexed vertices faceIndices

Invalid face indices (triples where any one of the three indices is out of bounds) will be dropped.

triangles : List ( vertex, vertex, vertex ) -> TriangularMesh vertex

Create a mesh from a list of triangular faces, where each face is given as a tuple of three vertices. Note that this will not perform any kind of vertex deduplication, so if any vertices are shared between different triangles then they will occur more than once in the resulting mesh's vertex array.

mesh =
    TriangularMesh.triangles [ ( a, b, c ), ( a, c, d ) ]

TriangularMesh.vertices mesh
--> Array.fromList [ a, b, c, a, c, d ]

Array.faceIndices mesh
--> [ ( 0, 1, 2 ), ( 3, 4, 5 ) ]

fan : vertex -> List vertex -> TriangularMesh vertex

Create a fan-shaped mesh from the first given vertex to all vertices in the given list. If the given list is empty or has only one element, then an empty mesh is returned. Otherwise, the first face will be from the first given vertex to the first and second list vertices, the second face will be from the first given vertex to the second and third list vertices, etc.

mesh =
    TriangularMesh.fan a [ b, c, d, e ]

TriangularMesh.vertices mesh
--> Array.fromList [ a, b, c, d, e ]

TriangularMesh.faceVertices mesh
--> [ ( a, b, c ), ( a, c, d ), ( a, d, e ) ]

radial : vertex -> List vertex -> TriangularMesh vertex

Like fan, but also connect the last vertex in the list back to the first. This can be useful to create cones or cone-like shapes with a tip vertex connected to a closed loop of other vertices.

mesh =
    TriangularMesh.radial a [ b, c, d, e ]

TriangularMesh.vertices mesh
--> Array.fromList [ a, b, c, d, e ]

TriangularMesh.faceVertices mesh
--> [ ( a, b, c )
--> , ( a, c, d )
--> , ( a, d, e )
--> , ( a, e, b )
--> ]

strip : List vertex -> List vertex -> TriangularMesh vertex

Create a strip-shaped mesh between two lists of vertices. The two lists should be the same length; if one list is longer, the extra vertices will be dropped. To get triangles with counterclockwise winding order, the second list should be to the left of the first; for example, for two left-to-right vertex lists, the second should be above the first.

mesh =
    TriangularMesh.strip [ a, b, c ] [ d, e, f ]

TriangularMesh.faceVertices mesh
--> [ ( a, b, e )
--> , ( a, e, d )
--> , ( b, c, f )
--> , ( b, f, e )
--> ]

grid : Basics.Int -> Basics.Int -> (Basics.Float -> Basics.Float -> vertex) -> TriangularMesh 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. For example, given some function f that creates vertices,

TriangularMesh.grid 3 2 f

will produce a mesh like this:

Rectangular mesh

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) -> TriangularMesh 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 TriangularMesh

TriangularMesh.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) -> TriangularMesh 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) -> TriangularMesh 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. For example, given some function f that creates vertices,

TriangularMesh.indexedGrid 3 2 f

will produce a mesh like this:

Rectangular mesh

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

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

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

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

Combining meshes

combine : List (TriangularMesh a) -> TriangularMesh 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.

square =
    TriangularMesh.with
        { vertices = Array.fromList [ a, b, c, d ]
        , faceIndices = [ ( 0, 1, 2 ), ( 0, 2, 3 ) ]
        }

triangle =
    TriangularMesh.with
        { vertices = Array.fromList [ e, f, g ]
        , faceIndices = [ ( 0, 1, 2 ) ]
        }

TriangularMesh.combine [ square, triangle ]
--> TriangularMesh.with
-->     { vertices =
-->         Array.fromList [ a, b, c, d, e, f, g ]
-->     , faceIndices =
-->         [ ( 0, 1, 2 )
-->         , ( 0, 2, 3 )
-->         , ( 4, 5, 6 )
-->         ]
-->     }

TriangularMesh.combine [ triangle, square ]
--> TriangularMesh.with
-->     { vertices =
-->         Array.fromList [ e, f, g, a, b, c, d ]
-->     , faceIndices =
-->         [ ( 0, 1, 2 )
-->         , ( 3, 4, 5 )
-->         , ( 3, 5, 6 )
-->         ]
-->     }

Properties

vertices : TriangularMesh vertex -> Array vertex

Get the vertices of a mesh.

TriangularMesh.vertices square
--> Array.fromList [ a, b, c, d ]

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

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

TriangularMesh.vertex 1 square
--> Just b

TriangularMesh.vertex 4 square
--> Nothing

faceIndices : TriangularMesh vertex -> List ( Basics.Int, Basics.Int, Basics.Int )

Get the faces of a mesh as triples of vertex indices.

TriangularMesh.faceIndices square
--> [ ( 0, 1, 2 ), ( 0, 2, 3 ) ]

faceVertices : TriangularMesh vertex -> List ( vertex, vertex, vertex )

Get the faces of a mesh as triples of vertices.

TriangularMesh.faceVertices square
--> [ ( a, b, c ), ( a, c, d ) ]

edgeIndices : TriangularMesh 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.

TriangularMesh.edgeIndices square
--> [ ( 0, 1 )
--> , ( 0, 2 )
--> , ( 0, 3 )
--> , ( 1, 2 )
--> , ( 2, 3 )
--> ]

edgeVertices : TriangularMesh 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.

TriangularMesh.edgeVertices square
--> [ ( a, b )
--> , ( a, c )
--> , ( a, d )
--> , ( b, c )
--> , ( c, d )
--> ]

Transformations

mapVertices : (a -> b) -> TriangularMesh a -> TriangularMesh 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 : TriangularMesh ( Float, Float )
mesh2d =
    ...

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

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