Meshes

GeometryBasics defines two mesh types to work with - Mesh and MetaMesh

Mesh

GeometryBasics.MeshType
Mesh{PositionDim, PositionType, FaceType, VertexAttributeNames, VertexAttributeTypes, FaceVectorType} <: AbstractMesh{PositionDim, PositionType} <: AbstractGeometry{PositionDim, PositionType}

The type of a concrete mesh. The associated struct contains 3 fields:

struct Mesh{...}
    vertex_attributes::NamedTuple{VertexAttributeNames, VertexAttributeTypes}
    faces::FaceVectorType
    views::Vector{UnitRange{Int}}
end

A vertex typically carries multiple distinct pieces of data, e.g. a position, a normal, a texture coordinate, etc. We call those pieces of data vertex attributes. The vertex_attributes field contains the name and a collection <: AbstractVector or <: FaceView for each attribute. The n-th element of that collection is the value of the corresponding attribute for the n-th vertex.

#                   vertex       1        2        3
vertex_attributes[:position] = [pos1,    pos2,    pos3,    ...]
vertex_attributes[:normal]   = [normal1, normal2, normal3, ...]
...

A NamedTuple is used here to allow different meshes to carry different vertex attributes while also keeping things type stable. The constructor enforces a few restrictions:

  • The first attribute must be named position and must have a Point{PositionDim, PositionType} eltype.
  • Each vertex attribute must refer to the same number of vertices. (All vertex attributes defined by

AbstractVector must match in length. For FaceViews, the number of faces needs to match.)

See also: vertex_attributes, coordinates, normals, texturecoordinates, decompose, FaceView, expand_faceviews

The faces field is a collection <: AbstractVector{FaceType} containing faces that describe how vertices are connected. Typically these are (GL)TriangleFaces or QuadFaces, but they can be any collection of vertex indices <: AbstractFace.

See also: faces, decompose

The views field can be used to separate the mesh into mutliple submeshes. Each submesh is described by a "view" into the faces vector, i.e. submesh n uses mesh.faces[mesh.views[n]]. A Mesh can be constructed without views, which results in an empty views vector.

See also: merge, split_mesh

source

You can get data from a mesh using a few interface functions:

  • vertex_attributes(mesh) = mesh.vertex_attributes
  • coordinates(mesh) = mesh.vertex_attributes[:position]
  • normals(mesh) = mesh.vertex_attributes[:normal]
  • texturecoordinates(mesh) = mesh.vertex_attributes[:uv]
  • faces(mesh) = mesh.faces

You can also grab the contents of mesh.vertex_attributes as if they were fields of the Mesh, e.g. mesh.position works.

FaceView

GeometryBasics.FaceViewType
FaceView(data, faces)

A FaceView is an alternative to passing a vertex attribute directly to a mesh. It bundles data with a new set of faces which may index that data differently from the faces defined in a mesh. This can be useful to avoid duplication in data.

For example, data can be defined per face by giving each face just one (repeated) index:

per_face_normals = FaceView(
    normals,                 # one per face
    FT.(eachindex(normals))  # with FT = facetype(mesh)
)

If you need a mesh with strictly per-vertex data, e.g. for rendering, you can use expand_faceviews(mesh) to convert every vertex attribute to be per-vertex. This will duplicate data and reorder faces as needed.

You can get the data of a FaceView with values(faceview) and the faces with faces(faceview).

source

The purpose of FaceView is to allow you to add data that doesn't use the same vertex indices as mesh.faces As a minimal example consider a mesh that is just one triangle, i.e. 3 position and one triangle face TriangleFace(1,2,3). Let's say we want to add a flat color to the triangle. In this case we only have one color, but our face refers to 3 different vertices (3 different positions). To avoid duplicating the color data, we can instead define a new triangle face TriangleFace(1) and add the color attribute as a FaceView([color], [TriangleFace(1)]). If we ever need the mesh to be defined with just one common set of faces, i.e. no FaceView and appropriately duplicated vertex data, we can use expand_faceviews(mesh) to generate it.

On a larger scale this can be useful for memory and performance reason, e.g. when you do calculations with vertex attributes. It can also simplify some definitions, like for example Rect3. In that case we have 8 positions and 6 normals with FaceViews, or 24 without (assuming per-face normals).

MetaMesh

MetaMesh

How to create a mesh

GeometryBasics

In GeometryBasics you mainly create meshes from primitives using a few constructors:

  • triangle_mesh(primitive) generates the most basic mesh (i.e. positions and faces)
  • normal_mesh(primitive) generates a mesh with normals (generated if the primitive doesn't implement normal())
  • uv_mesh(primitive) generates a mesh with texture coordinates (generated if the primitive doesn't implement texturecoordinates())
  • uv_normal_mesh(primitive) generates a mesh with normals and texture coordinates

Each of these constructors also includes keyword arguments for setting types, i.e. pointtype, facetype, normaltype and uvtype as appropriate. Of course you can also construct a mesh directly from data, either with there various Mesh() or GeometryBasics.mesh() constructors. The latter also include a pointtype and facetype conversion.

Finally there is also a merge(::Vector{Mesh}) function which combines multiple meshes into a single one. Note that this doesn't remove any data (e.g. hidden or duplicate vertices), and may remove FaceViews if they are incompatible between meshes.

Meshing.jl

MeshIO.jl

The MeshIO.jl package provides load/save support for several file formats which store meshes.

using GLMakie, GLMakie.FileIO, GeometryBasics

m = load(GLMakie.assetpath("cat.obj"))
GLMakie.mesh(m; color=load(GLMakie.assetpath("diffusemap.png")), axis=(; show_axis=false))
Example block output