Cellular Volumes
Cellular volumes are a HOOPS Visualize construct similar to shells. While shells do have their advantages, they are fundamentally just a simple collection of points and edges that form faces. They are, therefore, just connected surfaces which associate no higher-order meaning to concepts such as “inside” or “outside”. If a 3D object is defined as a group of shells, we cannot determine negative space such as holes.
Cellular volumes solve these problems. Cellular volumes assign meaning to the concepts of inside and outside, and can determine where negative space exists. Color and texture information can be carried onto capping surfaces and enable rendering along the cutting face inside the defined volume.
Cellular volumes are most often used in Finite Element Analysis (FEA) and Computational Fluid Dynamics (CFD).
Note
To use cellular volumes, include the header file hc_experimental.h.
Defining Cellular Volumes
There are several different types of cellular volume “primitives”, including boxes, pyramids, wedges, or just a general polyhedron. If you know the general shape of your object beforehand, you can use the shape that best represents it. For example, to create a pyramid, you would specify four points as the base, plus an additional point for the tip. This data collectively is called a cellular definition. An example is below:
std::array<float, 15> points = { 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0.5f, 0.5f, 1 };
std::array<int, 6> cell_defs = { HC_CELLULAR_VOLUME_PYRAMID, 0, 1, 2, 3, 4 };
int constexpr point_count = int(points.size()) / 3;
HC_Open_Segment_By_Key(m_pHView->GetSceneKey());
HC_Set_Color("faces=gray, edges=red, cut edges=red");
HC_Set_Visibility("edges, no vertices, cut geometry");
HC_Set_Rendering_Options("no lighting interpolation");
HC_Open_Segment("volume");
HC_KEY cv_key = HC_Insert_Cellular_Volume(point_count, points.data(), int(cell_defs.size()), cell_defs.data());
HC_Insert_Distant_Light(1, 1, -1);
HC_Close_Segment();
HC_Close_Segment();

In the above code snippet, we’ve used the HC_CELLULAR_VOLUME_PYRAMID
constant which tells HOOPS Visualize to expect a pyramid.
It’s easiest to think of cells being defined in groups. A group definition ends if it reaches the end of the array, or a SEPARATOR
value ends a group before starting another. Separators have nothing to do with whether cells or groups are disjoint or connected.
A basic cell type starts a group of cells of that type, consisting of one or more sets of point indices according to the type (5 per pyramid, 8 per box, etc.).
{ Pyramid, 0, 1, 2, 3, 4 }
would define a single pyramid{ Pyramid, 0, 1, 2, 3, 4, 3, 2, 1, 0, 5 }
defines two which share their quadrilateral bases{ Pyramid, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
also defines two which do not share any points, so they are disjoint.
Other defined contants can be used to create various shapes:
Constant | Meaning |
---|---|
HC_CELLULAR_VOLUME_PYRAMID |
pyramid |
HC_CELLULAR_VOLUME_BOX |
box / cube |
HC_CELLULAR_VOLUME_FACE |
|
HC_CELLULAR_VOLUME_WEDGE |
3-sided prism |
HC_CELLULAR_VOLUME_SIMPLEX |
tetrahedron / 3-sided pyramid |
HC_CELLULAR_VOLUME_POLYHEDRON |
general shape |
HC_CELLULAR_VOLUME_MIXED |
meta specifier |
HC_CELLULAR_VOLUME_SEPARATOR |
meta specifier |
Notice the “MIXED” and “SEPARATOR” designators which don’t correspond to a specific shape. As mentioned above, they are used as directives when defining complex cellular volumes. For example, let’s say you would like to specify a shape that is composed of a pyramid and a box. This means each shape would be defined separately in a mixed group. Therefore, we use the MIXED
designator.
This is a pyramid attached to a box, since they share points 0 through 3:
{ Mixed,
Pyramid, 0, 1, 2, 3, 4,
Box, 3, 2, 1, 0, 8, 7, 6, 5 }
The following would be a disjoint pyramid and box (no points shared, no separator needed):
{ Mixed,
Pyramid, 0, 1, 2, 3, 4,
Box, 8, 7, 6, 5, 12, 11, 10, 9 }
A full example:
std::array<float, 27> points = { 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0.5f, 0.5f, 1, // pyramid
0, 0, -1, 1, 0, -1, 1, 1, -1, 0, 1, -1 }; // box's four extra vertices
std::array<int, 16> cell_defs = { HC_CELLULAR_VOLUME_MIXED,
HC_CELLULAR_VOLUME_PYRAMID, 0, 1, 2, 3, 4,
HC_CELLULAR_VOLUME_BOX, 3, 2, 1, 0, 8, 7, 6, 5 };
int constexpr point_count = int(points.size()) / 3;
HC_Open_Segment_By_Key(m_pHView->GetSceneKey());
HC_Set_Color("faces=gray, edges=red, cut edges=red");
HC_Set_Visibility("edges, no vertices, cut geometry");
HC_Set_Rendering_Options("no lighting interpolation");
HC_Open_Segment("volume");
HC_KEY cv_key = HC_Insert_Cellular_Volume(point_count, points.data(), int(cell_defs.size()), cell_defs.data());
HC_Insert_Distant_Light(1, 1, -1);
HC_Close_Segment();
HC_Close_Segment();

The SEPARATOR
designator is used when you have two shapes that you want to consider as one, but are disjoint. The cellular definition would be set up in the following way:
std::array<float, 39> points = { 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0.5f, 0.5f, 1,
0, 0, -1, 1, 0, -1, 1, 1, -1, 0, 1, -1,
0, 0, -2, 1, 0, -2, 1, 1, -2, 0, 1, -2,
};
std::array<int, 17> cell_defs = { HC_CELLULAR_VOLUME_PYRAMID, 0, 1, 2, 3, 4,
HC_CELLULAR_VOLUME_SEPARATOR,
HC_CELLULAR_VOLUME_BOX, 8, 7, 6, 5, 12, 11, 10, 9 };

The use of basic groups or mixed groups is up to the user - basic group are more compact for large sets of the same type of cell, while mixed groups may be easier to translate from other data formats.
Face Groups
A face group is exactly the same as defining shell faces (except no holes allowed), one or more sets of a count with many point indices. This is only defining the faces, however - they don’t do anything until they are grouped together into one or more polyhedron cells. A polyhedron cell is in turn a count followed by that many face indices. Face and polyhedron groups are also terminated by a separator or end of data (though ending in a face group isn’t useful unless you edit to add polyhedron cells after).
Faces are numbered in the order they are defined. If a face is shared, one of the polyhedron cells must be looking at it backwards - this is indicated by using the ones-complement (~) of its index. Polyhedron cells can only be defined using faces that have been defined before that point. There at least a couple smoke tests with samples of them.
Below is an example of defining a cellular volume as a group of faces in a polyhedron:
float points[] = {
-0.57735 , -0.57735 , 0.57735 ,
0.934172, 0.356822, 0 ,
0.934172, -0.356822, 0 ,
-0.934172, 0.356822, 0 ,
-0.934172, -0.356822, 0 ,
0 , 0.934172, 0.356822,
0 , 0.934172, -0.356822,
0.356822, 0 , -0.934172,
-0.356822, 0 , -0.934172,
0 , -0.934172, -0.356822,
0 , -0.934172, 0.356822,
0.356822, 0 , 0.934172,
-0.356822, 0 , 0.934172,
0.57735 , 0.57735 , -0.57735 ,
0.57735 , 0.57735 , 0.57735 ,
-0.57735 , 0.57735 , -0.57735 ,
-0.57735 , 0.57735 , 0.57735 ,
0.57735 , -0.57735 , -0.57735 ,
0.57735 , -0.57735 , 0.57735 ,
-0.57735 , -0.57735 , -0.57735 ,
};
int cell_defs[] = {
HC_CELLULAR_VOLUME_FACE,
5, 2, 18, 11, 14, 1,
5, 13, 7, 17, 2, 1,
5, 4, 19, 8, 15, 3,
5, 16, 12, 0, 4, 3,
5, 15, 6, 5, 16, 3,
5, 14, 5, 6, 13, 1,
5, 17, 9, 10, 18, 2,
5, 0, 10, 9, 19, 4,
5, 8, 19, 9, 17, 7,
5, 15, 8, 7, 13, 6,
5, 14, 11, 12, 16, 5,
5, 0, 12, 11, 18, 10,
HC_CELLULAR_VOLUME_SEPARATOR,
HC_CELLULAR_VOLUME_POLYHEDRON,
12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
};
HC_KEY volume = HC_Insert_Cellular_Volume(countof(points) / 3, points, countof(cell_defs), cell_defs);
Editing Cellular Volumes
Editing a cellular volume means creating or deleting cells in a previously declared volume. To reference a cellular volume, you’ll need to use the unique key returned from HC_Insert_Cellular_Volume`.
First, we’ll discuss deleting.
Deleting
Deletion either works on cells as a whole or on individual vertices, depending on which function is called. In the following code snippet, we use the object’s unique key, cv_key
, to reference the object. The next parameter is an offset which determines where the modification will begin. The operation is zero-based, so the second parameter 1
indicates we want to delete the 2nd volume. The third parameter is the number of cells to delete. In this case we just delete one cell. The last two parameters can be ignored as those are used during the editing process.
HC_Edit_Cellular_Volume_Cells(cv_key, 1, 1, 0, 0);
To affect the object’s vertices (instead of its volume cells), use HC_Edit_Cellular_Volume_Points
, below.
Note
When a point that is related to a cell is removed, the associated cell is also removed.
Editing
Editing a cellular volume is similar to deleting. Instead of providing parameters to delete cells as we did in the previous example, we’ll provide new parameters that modify vertices. In this case we are editing the point in position 8. However, you should be aware that editing a volume will, by default, will add this vertex to it. If you want to replace a vertex, you must also delete the old one:
std::array<float, 3> new_points = { 0, 1, -3 };
// ...
// adds new_points in the 8th position
HC_Edit_Cellular_Volume_Points(cv_key, 8, 0, int(new_points.size()) / 3, new_points.data());
// deletes the point at the 8th position and replaces it will new_points
HC_Edit_Cellular_Volume_Points(cv_key, 8, 1, int(new_points.size()) / 3, new_points.data());
Note
Point IDs are renumbered after removing or inserting points.