Read / Write attributes
If you've read the section on Graphite, we talked briefly about what we call attributes. Attributes are data that can be written to the primitives of a mesh. They are detached from the mesh itself, but can still be saved when the file is saved in geogram format.
The geogram format is specific to the Graphite viewer. The advantage of this format is that you can visualize mesh attributes in Graphite in the form of color maps. To find out more, please refer to the Graphite section.
We'll look at how to create different kinds of attributes and save them for viewing, then load them from a file.
Attribute types
Each primitive can be associated with one or more attributes. Each primitive type has a corresponding attribute type.
Primitive type | Attribute type |
---|---|
Surface::Vertex | PointAttribute<T> |
Surface::Facet | FacetAttribute<T> |
Surface::Halfedge | CornerAttribute<T> |
Volume::Facet | CellFacetAttribute<T> |
Volume::Cell | CellAttribute<T> |
Polyline::Edge | EdgeAttribute<T> |
The attributes classes are templated, so you can associate data of any type with any primitive. The most commonly used types are double
, int
, bool
, vec2
, vec3
.
Warning
- Only the geogram file format support attributes.
- If you want to view attributes (in Graphite), only the primitive types listed above can be used.
Create and fill attributes
Point attribute
In this example, we'll create a point attribute for which for each vertex of the mesh we'll set the distance between origin and this vertex.
// Create a point attribute
PointAttribute<double> pa(m.points);
for (auto v : m.iter_vertices()) {
// Compute manhattan distance (l1-norm) between origin and vertex
auto d = vec3(0,0,0) - v.pos();
pa[v] = std::abs(d.x) + std::abs(d.y) + std::abs(d.z);
}
Now, you just have to save attribute into the mesh file.
// Save mesh with previously created attribute
write_by_extension(output_dir + "catorus_manhattan_point_attr.geogram", m, {{"pa", pa}});
Now, let's visualize this point attribute into Graphite. What you see is that the further away the vertices are from the origin, the whiter they are, and the closer they are, the darker they are.
Facet attribute
In this example, we'll create a facet attribute for which we'll set a random value between 0 and 99 for each facet of the mesh.
// Create a facet attribute
FacetAttribute<int> fa(m);
// For all facets in mesh, associate a random int value between [0-99]
for (auto f : m.iter_facets())
fa[f] = rand() % 100;
Now, you just have to save attribute into the mesh file.
// Save mesh with previously created attribute
write_by_extension(output_dir + "catorus_facet_attr.geogram", m, {{"fa", fa}});
If you visualise this attribute into Graphite, you will see a very funky cat !
Of course, the purpose of attributes is not to make pretty and colorful cats. We can use attributes to debug and visualize what we're doing on a mesh. You'll see better examples later.
Corner attribute
CornerAttribute<vec2> ca(m);
for (auto &h : m.iter_halfedges())
ca[h] = vec2(h.from().pos().x, h.from().pos().y);
write_by_extension(output_dir + "catorus_corner_attr.geogram", m, {{"ca", ca}});
If you visualise this attribute into Graphite, you will see this sort of cat:
Edge attribute
// Create a edge attribute
EdgeAttribute<int> edge_id_attr(p);
// For all edge in polyline set edge attribute with edge id
for (auto &e : p.iter_edges())
edge_id_attr[e] = e;
write_by_extension(output_dir + "pyramid_attr.geogram", p, {{"edge_id", edge_id_attr}});
Save attributes
As you've seen, attributes can be saved directly in geogram files. Geogram files can then be read by graphite, which is able to display the attributes. You'll notice that attributes are stored in the file as key/value pairs: each attribute must have a name.
For example we will save all previously created attributes into a geogram file:
// Save mesh with all previously created attributes
write_by_extension(output_dir + "catorus_attr.geogram", m, {{"pa", pa}, {"fa", fa}, {"ca", ca}});
Read attributes
Now it's time to learn how to read the attributes we've written in our geogram files:
// Load mesh and read attributes
Triangles m2;
SurfaceAttributes attributes = read_by_extension(output_dir + "catorus_attr.geogram", m2);
// Load "pa" attribute
PointAttribute<double> pa2("pa", attributes, m2);
// Load "fa" attribute
FacetAttribute<int> fa2("fa", attributes, m2);
// Load "ca" attribute
CornerAttribute<vec2> ca2("ca", attributes, m2);
std::cout
<< "PointAttribute size: " << pa2.ptr.get()->data.size()
<< ", FacetAttribute size: " << fa2.ptr.get()->data.size()
<< ", CornerAttribute size: " << ca2.ptr.get()->data.size()
<< std::endl;
Dynamic binding
If you want to get an attribute by name later, you can use attribute dynamic binding:
- Declare an attribute variable
- And bind it to a mesh attribute when necessary:
- If the attribute exists in the mesh, your attribute variable will be filled with the attribute data
- Otherwise, the attribute variable is filled by the default value
// Comment below and uncomment next line to see different behavior
const std::string bind_attr = "pa";
// const std::string bind_attr = "unkown_attribute";
// Create a new point attribute
PointAttribute<double> pa3;
// Bind to the mesh, if bind return true, the attribute already exists: pa3 is fill with 'pa' data
// if bind return false, the attribute does not exist: pa3 is added to the mesh and is fill with default value
if (pa3.bind(bind_attr, attributes, m2)) {
std::cout
<< "Point attribute 'pa' exists and is bound successfully."
<< std::endl;
} else {
std::cout
<< "Point attribute 'pa' does not exist and was added successfully."
<< std::endl;
}
// Display attribute values
for (int i = 0; i < 10; i++) {
std::cout << pa3[m.vertex(i)] << " ";
}
std::cout << "..." << std::endl;