This module provides functions for working with indexed meshes. You can:
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
empty : Mesh vertex
A mesh with no vertices or faces.
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.
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').
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
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.
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.
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
.
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.