Meshes
GeometryBasics defines two mesh types to work with - Mesh
and MetaMesh
Mesh
GeometryBasics.Mesh
— TypeMesh{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 aPoint{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)TriangleFace
s or QuadFace
s, but they can be any collection of vertex indices <: AbstractFace
.
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
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.FaceView
— TypeFaceView(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)
.
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 implementnormal()
)uv_mesh(primitive)
generates a mesh with texture coordinates (generated if the primitive doesn't implementtexturecoordinates()
)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 FaceView
s 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))