Attributes

Note

The basic functionality is implemented in PMeshAttributes.

Create attribute lists

Attribute lists are declared as types by

Each attribute in the list is specified by a key => value pair, where key is a Symbol, the unique id of the attribute, and value is either

  • the result of specifyattr(T, enabled=initial_state, x0=initial_value) where T is the attribute type, and enabled::Bool and x0 define the initial state, or
  • a type T, which implies enabled=true and a "zero-like" default value.
Note

The specification of an attribute list determines its type!

Tip

Use the similar method for creating an identical type attribute list without entries ("empty vectors").

Note

For performance reasons, there is currently no support for "dynamic" attribute lists (e.g., based on Dict). Instead attribute lists are based on named tuples (see PMeshAttributes).

As a consequence,

  • all attributes must be provided on create*,
  • the type of the mesh is determined by the type of its elements' attribute lists.

Access and performance

Attribute lists and attributes for vertices, faces, half-edges and edges are accessed by methods vattr,fattr,hattr and eattr, respectively.

  • va = vattr(mesh) provides the list of vertex attributes.
  • a = vattr(mesh, name) (or above va[name]) provides a particular attribute a. Attribute names are Symbols.
  • a[v] accesses the element specified by handle v. Here v is a vertex handle VHnd.
  • Use vec(a) for "raw" access to the Vector storing elements (Int indices, no isused checks).
Type stability

Read the following carefully to avoid runtime penalties from dynamic dispatch!

Even if an attribute list va is implemented as a NamedTuple and name is a constant Symbol or a literal (e.g., :a), the type of va[:a] and vattr(mesh, :a) is not fully specified: It is generally a Union{} type. (Unfortunately, there is no way to tell whether name is a literal (i.e., type can be inferred) or a variable Symbol.)

This can lead to performance penalties due to dynamic dispatch. There are two options to avoid this

  1. Get va = vattr(mesh, :a) and pass va to downstream methods, e.g.

    f(mesh, va)

    instead of

    f(mesh, :a)

    This is very effective, also because the access of vattr(mesh) is limited.

    Always consider this option if evaluation of f is expensive, i.e., requires heavy access of a.

    Tip

    You can use vattr(mesh, va) in any other method called downstream: vattr(mesh, va::Vattr) returns va at no extra cost.

  2. Pass names that are given as constant or literal symbols as Val types, e.g., va = vattr(mesh, Val(:a)).

    Note

    We don't provide the "usual" cascade such that f(s::Symbol) calls f(Val(s)) because this gets expensive if s is determined dynamically, i.e., is not a literal or constant.

Good practice
  • Get attributes early from their names and pass attributes rather than symbols.
  • If you need default values, use Val if applicable.
  • Don't specify types for attribute names in signatures f(mesh; position=Val(:x)), and use vattr, possibly as a nop (e.g., x = vattr(mesh, position).
const Vec2 = SVector{2, Float64}
const Vec3 = SVector{3, Float64}

# mesh with vertex position, normal, texture coordinate and face normals
mesh = createmesh((:x => Vec3, :n => Vec3, :u => Vec2), (:n => Vec3), (), ())

# NOTE: vattr(mesh, name::Symbol) called within a method has type
#       Union{UInt8, HHnd, Vec3, Vec3} !!!

function bad(mesh, ax = :x)
    f(mesh, ax)
end

function better(mesh, ax = Val(:x))
    f(mesh, ax)
end

function best(mesh, ax = Val(:x))
    x = vattr(mesh, ax)
    f(mesh, x)
end

function f(mesh, ax)
    x = vattr(mesh, ax)  # Note: nop if ax::VAttr
    # ...
    x[VHnd(1)]
end
Base.similarMethod
va2 = similar(va::VAttr)

Create same attribute list as va but empty: without entries.

source
PMesh.createvattrFunction
createvattr(key => value, ...)
createvattr((key => value, ...))

Create vertex attribute list from key => value pairs, where key is a Symbol and value either a type or the result from specifyattr.

Example:

va = createvattr(:x => SVector{3, Float64}, :n => SVector{3, Float64})

See also similar

source
PMesh.VAttrType

Vertex attributes consisting of managed attributes P plus cached state.

source
Base.similarMethod
fa2 = similar(fa::FAttr)

Create same attribute list as va but empty: without entries.

source
PMesh.createfattrFunction
createfattr(key => value, ...)
createfattr((key => value, ...))

Create face attribute list from key => value pairs, where key is a Symbol and value either a type or the result from specifyattr.

Example:

fa = createfattr(:n => SVector{3, Float64})

See also similar

source
PMesh.FAttrType

Face attributes consisting of managed attributes P plus cached state.

source
Base.similarMethod
ha2 = similar(ha::Attr)

Create same attribute list as va but empty: without entries.

source
PMesh.createhattrFunction
createhattr(key => value, ...)
createhattr((key => value, ...))

Create half-edge attribute list from key => value pairs, where key is a Symbol and value either a type or the result from specifyattr.

See also similar

source
PMesh.HAttrType

Half-edge attributes consisting of managed attributes P plus cached state.

source
Base.similarMethod
ea2 = similar(ea::EAttr)

Create same attribute list as va but empty: without entries.

source
PMesh.createeattrFunction
createeattr(key => value, ...)
createeattr((key => value, ...))

Create edge attribute list from key => value pairs, where key is a Symbol and value either a type or the result from specifyattr.

Example:

ea = createfattr(:locked => Bool)

See also similar

source
PMesh.EAttrType

Edge attributes consisting of managed attributes P plus cached state.

source

Testing for attributes

Use hasvattr, hasfattr, hashattr, haseattr to check if an attribute exists.

Note

For consistency with the "good practices" discussed above, e.g., hasvattr returns always true if given a vertex attribute!

In this case, we don't check if this attribute exists in the given mesh, as there is no field name given!

There is also a 3-argument form the vattr,fattr,hattr and eattr, which never fails but returns a default value if an attribute is not present similar to Base.get:

x = vattr(mesh, :x, nothing)

isnothing(x) && error("no such attribute")

f (mesh, x)  # use attribute
Note

The type of the optional default argument is specified by the user, e.g., provide nothing or an existing attribute or an user-constructed empty attribute.

Attributes and conversion to vectors

Use Base.collect and Base.vec to access all values. The first provides a copy of all used data, the second provides a view of all (used and unused) data.

x = vattr(mesh, Val(:x))

# Copy all used values into a vector
values = collect(mesh, x) ::Vector{eltype(x)}
@assert length(values) <= nv(mesh)

# Get view of all -- used and unused -- values
rawvalues = vec(x)
@assert length(rawvalues) >= nv(mesh)
Base.collectMethod
values = collect(mesh, ettr(...))

Collect all used values.

Note

vec(eattr(...)) provides a view that includes unused values.

source
Base.collectMethod
values = collect(mesh, hattr(...))

Collect all used values.

Note

vec(hattr(...)) provides a view that includes unused values.

source
Base.collectMethod
values = collect(mesh, vattr(...))

Collect all used values.

Note

vec(vattr(...)) provides a view that includes unused values.

source
Base.empty!Method
empty!(mesh)

Remove all vertices, i.e., all mesh elements.

source
Base.similarMethod
mesh2 = similar(mesh)

Create mesh2 with identical same lists and identical type as mesh. The enabled state of attributes and the default values are copied from mesh.

See also createmesh

source
PMesh.createmeshMethod
mesh = createmesh(v, g, h, e)

Create mesh with attributes v (vertices), f (faces), h (half-edges), e (edges).

Note

The attributes determine the type of mesh

Tip

Use similar(::Mesh) for constructing an empty mesh f identical type (i.e., identical attribute lists including enabled state of attributes).

Example

Create mesh with vertex attributes for position :x and normals :n, face normals :n and color :c, no attributes for half-edges, and an edge attribute :feature. A default value :red is specified for face color :c, and this attribute is disabled after construction.

mesh = createmesh((:x => SVector{3, Float64}, :n => SVector{3, Float64}),
                  (:n => SVector{3, Float64},
                   :c => specifyattr(Symbol, enabled=false, x0=:red)),
                  (),
                  (:feature => Bool))

See also similar, Mesh

source
PMesh.eattrFunction
list = eattr(mesh)
attr = eattr(mesh, name)
attr = eattr(mesh, Val(name))     # fast & type-safe if name is a literal
attr = eattr(mesh, attr)          # identity
attr = eattr(mesh, name, default) # checks haseattr

Get edge attribute list from mesh or particular vertex attribute name. The optional default argument is returned if haseattr yields false similar to Base.get.

See also vattr, fattr, hattr

source
PMesh.fattrFunction
list = fattr(mesh)
attr = fattr(mesh, name)
attr = fattr(mesh, Val(name))     # fast & type-safe if name is a literal
attr = fattr(mesh, attr)          # identity

attr = fattr(mesh, name, default) # checks hasfattr

Get face attribute list from mesh or particular vertex attribute name. The optional default argument is returned if hasfattr yields false similar to Base.get.

See also vattr, hattr, eattr

source
PMesh.haseattrFunction
haseattr(mesh, name)

haseattr(mesh, :name)
haseattr(mesh, Val(:name))
haseattr(mesh, nothing)               # always false
haseattr(mesh, ::Attribute{T, EHnd})  # always true

Does mesh provide edge attribute name?

Note

Calling haseattr with an edge attribute yields always true for consistency with passing attributes. There is no name we could check!

See also eattr

source
PMesh.hasfattrFunction
hasfattr(mesh, name)

hasfattr(mesh, :name)
hasfattr(mesh, Val(:name))
hasfattr(mesh, nothing)               # always false
hasfattr(mesh, ::Attribute{T, FHnd})  # always true

Does mesh provide face attribute name?

Note

Calling hasfattr with a face attribute yields always true for consistency with passing attributes. There is no name we could check!

See also fattr

source
PMesh.hashattrFunction
hashattr(mesh, name)

hashattr(mesh, :name)
hashattr(mesh, Val(:name))
hashattr(mesh, nothing)               # always false
hashattr(mesh, ::Attribute{T, HHnd})  # always true

Does mesh provide half-edge attribute name?

Note

Calling hashattr with an half-edge attribute yields always true for consistency with passing attributes. There is no name we could check!

See also hattr

source
PMesh.hasvattrFunction
hasvattr(mesh, name)

hasvattr(mesh, :name)
hasvattr(mesh, Val(:name))

hasvattr(mesh, nothing)               # always false
hasvattr(mesh, ::Attribute{T, FHnd})  # always true

Does mesh provide vertex attribute name?

Note

Calling hasvattr with a vertex attribute yields always true for consistency with passing attributes. There is no name we could check!

See also vattr

source
PMesh.hattrFunction
list = hattr(mesh)
attr = hattr(mesh, name)
attr = hattr(mesh, Val(name))     # fast & type-safe if name is a literal
attr = hattr(mesh, attr)          # identity

attr = hattr(mesh, name, default) # checks hashattr

Get half-edge attribute list from mesh or particular vertex attribute name. The optional default argument is returned if hashattr yields false similar to Base.get.

See also vattr, fattr, eattr

source
PMesh.linkMethod
link(vattr(mesh))
link(fattr(mesh))
link(hattr(mesh))
link(eattr(mesh))

Access attribute that stores the state of the half-edge data structure, which "links" mesh elements.

Note

For internal use only!

See also vattr, fattr, hattr, eattr

source
PMesh.maphandlesMethod
a = vattr(mesh, x)
f = maphandles(a)

elt = f(v::VHnd)

Create a functor that maps handles to attribute a (e.g., VHnd to vertex attributes) to values.

source
PMesh.vattrFunction
list = vattr(mesh)
attr = vattr(mesh, name)
attr = vattr(mesh, Val(name))     # fast & type-safe if name is a literal
attr = vattr(mesh, attr)          # identity

attr = vattr(mesh, name, default) # checks hasvattr

Get vertex attribute list from mesh or particular vertex attribute name. The optional default argument is returned if hasvattr yields false similar to Base.get.

Example:

x = vattr(mesh, :x)

See also fattr, hattr, eattr

source

Matrix views of attributes

Many attributes store vectors, e.g., vertex positions x. Then vec(x) is of type Vector{SVector{N, T}}. Sometimes it is convenient to have a matrix view either with N-vectors in columns or as the transposed with N-vectors rows such that coordinates are viewed in columns. This can be achieved by mat and cols.

Tip

One use case is setting the attribute as solution of a linear system or setting up the rhs of a linear system.

x = vattr(mesh, Val(:x))

X = mat(x)
@assert size(X) == (3, nv(mesh))  # assume 3-vectors

U = cols(x)
@assert size(U) == (nv(mesh), 3)  # assume 3-vectors
@assert U' == X

u = U[:, 1]
v = U[:, 2]

U .= A \\ b
PMesh.matMethod
X = mat(attr)
xj = X[:, j]    # equals vec(attr)[j]

Short for X = BaseUtilities.scmatrix(vec(attr)). Provides view of mesh attribute x as Matrix with vectors in columns.

Tip

cols provides the transposed mat(attr)', e.g., for use as solution or rhs on a linear system.

Note

A change of

See also scmat, cols

source
PMesh.scmatMethod
X = scmat(attr)
Xmat = X[]
XVec = X[:]

Short for X = BaseUtilities.scmatrix(vec(attr)). Provides views of mesh attribute x as Matrix with vectors in columns or Vector of vectors.

See also mat, cols

source

Virtual attributes

Every attribute can be mapped or lifted by a function. This is a feature of PMeshAttributes.

x = vattr(mesh, Val(:x))
h = first(vertices(mesh))

f(x) = 2x

fx = map(f, x)
fx[h]          # is equivalent to f.(x[h])

fx = f ∘ x     # is equivalent to map(f, x)

gfx = g ∘ fx   # maps by g ∘ f
gfx[h]         # is equivalent to g.(f.(x[h])) == g.(fx(h))

gfx = fx ∘ g   # maps by f ∘ g
gfx[h]         # is equivalent to f.(g.(x[h])) == f.(gx(h))

Alternatively, Geometry utilities provide linear and affine Transformations positions and normals.

You can also copy attributes, possibly between meshes (if they share the same sets of handles):

# destination
x1 = vattr(mesh1, Val(:x))  # 3d
u1 = vattr(mesh2, Val(:u))  # 2d

# source
x2 = vattr(mesh2, Val(:u))  # 3d

@assert collect(vertices(mesh1)) == collect(vertices(mesh2))

copy!(x1, x2, vertices(mesh1))

# copy/convert 3d->2d
map!(Map.proj(1, 2), x1, x2, vertices(mesh1))

See also PMesh.Maps.proj