Attributes
The basic functionality is implemented in PMeshAttributes.
Create attribute lists
Attribute lists are declared as types by
createvattr
for vertices,createfattr
for faces,createhattr
for half-edges, andcreateeattr
for edges.
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)
whereT
is the attribute type, andenabled::Bool
andx0
define the initial state, or - a type
T
, which impliesenabled=true
and a "zero-like" default value.
Use the similar
method for creating an identical type attribute list without entries ("empty vectors").
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 aboveva[name]
) provides a particular attributea
. Attribute names areSymbol
s.a[v]
accesses the element specified by handlev
. Herev
is a vertex handleVHnd
.- Use
vec(a)
for "raw" access to theVector
storing elements (Int
indices, noisused
checks).
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
Get
va = vattr(mesh, :a)
and passva
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 ofa
.Pass names that are given as constant or literal symbols as
Val
types, e.g.,va = vattr(mesh, Val(:a))
.
- 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 usevattr
, 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.similar
— Methodva2 = similar(va::VAttr)
Create same attribute list as va
but empty: without entries.
PMesh.createvattr
— Functioncreatevattr(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
PMeshAttributes.clone
— Methodvc = clone(va::VAttr)
Deep copy of attribute list but don't copy disabled attributes.
PMesh.AbstractVAttr
— TypeAbstract vertex attributes.
Vertex attributes like VAttr
are declared a a subtype.
PMesh.VAttr
— TypeVertex attributes consisting of managed attributes P
plus cached state.
Base.similar
— Methodfa2 = similar(fa::FAttr)
Create same attribute list as va
but empty: without entries.
PMesh.createfattr
— Functioncreatefattr(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
PMeshAttributes.clone
— Methodfc = clone(Fa::FAttr)
Deep copy of attribute list but don't copy disabled attributes.
PMesh.AbstractFAttr
— TypeAbstract face attributes.
Face attributes like VAttr
are declared a a subtype.
PMesh.FAttr
— TypeFace attributes consisting of managed attributes P
plus cached state.
Base.similar
— Methodha2 = similar(ha::Attr)
Create same attribute list as va
but empty: without entries.
PMesh.createhattr
— Functioncreatehattr(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
PMeshAttributes.clone
— Methodhc = clone(ha::HAttr)
Deep copy of attribute list but don't copy disabled attributes.
PMesh.AbstractHAttr
— TypeAbstract half-edge attributes.
Half-edge attributes like VAttr
are declared a a subtype.
PMesh.HAttr
— TypeHalf-edge attributes consisting of managed attributes P
plus cached state.
PMesh.HalfEdge
— TypeInternal link state of an half-edge.
Base.similar
— Methodea2 = similar(ea::EAttr)
Create same attribute list as va
but empty: without entries.
PMesh.createeattr
— Functioncreateeattr(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
PMeshAttributes.clone
— Methodec = clone(ea::EAttr)
Deep copy of attribute list but don't copy disabled attributes.
PMesh.AbstractEAttr
— TypeAbstract edge attributes.
Edge attributes like VAttr
are declared a a subtype.
PMesh.EAttr
— TypeEdge attributes consisting of managed attributes P
plus cached state.
Testing for attributes
Use hasvattr
, hasfattr
, hashattr
, haseattr
to check if an attribute exists.
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
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.collect
— Methodvalues = collect(mesh, ettr(...))
Collect all used values.
Base.collect
— Methodvalues = collect(mesh, hattr(...))
Collect all used values.
Base.collect
— Methodvalues = collect(mesh, vattr(...))
Collect all used values.
Base.empty!
— Methodempty!(mesh)
Remove all vertices, i.e., all mesh elements.
Base.similar
— Methodmesh2 = 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
PMesh.createmesh
— Methodmesh = createmesh(v, g, h, e)
Create mesh with attributes v
(vertices), f
(faces), h
(half-edges), e
(edges).
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))
PMesh.eattr
— Functionlist = 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
.
PMesh.fattr
— Functionlist = 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
.
PMesh.haseattr
— Functionhaseattr(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
?
Calling haseattr
with an edge attribute yields always true
for consistency with passing attributes. There is no name we could check!
See also eattr
PMesh.hasfattr
— Functionhasfattr(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
?
Calling hasfattr
with a face attribute yields always true
for consistency with passing attributes. There is no name we could check!
See also fattr
PMesh.hashattr
— Functionhashattr(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
?
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
PMesh.hasvattr
— Functionhasvattr(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
?
Calling hasvattr
with a vertex attribute yields always true
for consistency with passing attributes. There is no name we could check!
See also vattr
PMesh.hattr
— Functionlist = 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
.
PMesh.link
— Methodlink(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.
PMesh.maphandles
— Methoda = 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.
PMesh.vattr
— Functionlist = 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)
PMeshAttributes.clone
— Methodmc = clone(mesh)
Create deep copy of mesh but don't copy any disabled attributes. In contrast to compact
this method preserves handles and their isused
state.
See also compact
PMesh.MapHandles
— TypeMap handles to elements in attribute
See also maphandles
PMesh.Mesh
— TypePolygonal surface mesh based on a half-edge data structure.
The type of the mesh is defined by its vertex/ face/ half-edge/ edge attributes VA
, FA
, HA
, EA
which are AbstractVAttr
, AbstractFAttr
, AbstractHAttr
, AbstractEAttr
, respectively.
The state of the half-edge data structure is defined as part of the attributes (link
).
Use createmesh
, similar(::Mesh)
and clone
to construct a new mesh.
See also createmesh
, similar
, clone
, vattr
, fattr
, hattr
, eattr
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
.
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.mat
— MethodX = 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.
cols
provides the transposed mat(attr)'
, e.g., for use as solution or rhs on a linear system.
PMesh.scmat
— MethodX = 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.
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