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