This tutorial shows you how to create a new visual model that shows the van der Waals representation of a group of atoms.
Setting up the SAMSON Element
Use the SAMSON Element Generator to create a new SAMSON Element called VanDerWaals, containing a visual model called SEVanDerWaalsVisualModel.
Setting up the visual model
A visual model is applied to a group of nodes passed to the constructor of the visual model. Since we want to apply a van der Waals visual model to a group of atoms, we add a member to the visual model to remember the list of atoms that we were passed.
Add the following member inside the declaration of the SEVanDerWaalsVisualModel class in the SEVanDerWaalsVisualModel.hpp file:
SBPointerIndexer<SBAtom> atomIndexer; |
A pointer indexer is similar to a node indexer, thus assigns indices to pointed objects, but automatically removes references from the indexer when a pointed object is deleted (in the C++ sense), so that it can never point to deleted memory.
Then, in the SEVanDerWaalsVisualModel.cpp file, modify the second constructor as follows:
1 2 3 4 5 6 7 8 9 10 | SEVanDerWaalsVisualModel::SEVanDerWaalsVisualModel(const SBNodeIndexer& nodeIndexer) { SBNodeIndexer temporaryIndexer; SB_FOR(SBNode* node, nodeIndexer) node->getNodes(temporaryIndexer, SBNode::IsType(SBNode::Atom)); SB_FOR(SBNode* node, temporaryIndexer) atomIndexer.addReferenceTarget(node); } |
Lines 3-5 collect atoms among the nodes passed to the constructor of the visual model (possibly among their descendants), while lines 7 and 8 add the atoms to the atom indexer.
Displaying spheres
To display something in the viewport, we overload the display function of the visual model. For convenience, SAMSON provides many functions to ease rendering objects (instead of having to use OpenGL functions).
In this tutorial, we are going to use the displaySpheres function, to draw one sphere per atom, with a radius equal to the van der Waals radius of the atom. We thus include the SBAtom.hpp and SAMSON.hpp headers at the beginning of the file:
#include "SBAtom.hpp" #include "SAMSON.hpp" |
We can now code inside the display function of the visual model.
First, we initialize arrays needed to transfer data to SAMSON for rendering spheres:
unsigned int numberOfAtoms = atomIndexer.size(); float* positionData = new float[3 * numberOfAtoms]; float* radiusData = new float[numberOfAtoms]; float* colorData = new float[4 * numberOfAtoms]; unsigned int* flagData = new unsigned int[numberOfAtoms]; |
Observe the different array sizes above: we need 3 float per atom for the position, 1 float for the radius, 4 floats for the color (red, green, blue and opacity), and 1 unsigned int for flags used to alter the rendering colors (e.g. the selection flag and the highlighting flag).
We also need to know whether a color scheme is applied to the visual model, so we retrieve the pointer to the material applied to the visual model:
SBNodeMaterial* material = getMaterial(); |
We can now fill the arrays by traversing the list of atoms:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | for (unsigned int i = 0; i < numberOfAtoms; i++) { SBAtom* currentAtom = atomIndexer[i]; SBPosition3 position = currentAtom->getPosition(); positionData[3 * i + 0] = (float)position.v[0].getValue(); positionData[3 * i + 1] = (float)position.v[1].getValue(); positionData[3 * i + 2] = (float)position.v[2].getValue(); radiusData[i] = (float)currentAtom->getVanDerWaalsRadius().getValue(); if (material) { material->getColorScheme()->getColor(colorData + 4 * i, currentAtom); } else if (currentAtom->getMaterial()) currentAtom->getMaterial()->getColorScheme()->getColor(colorData + 4 * i, currentAtom); else { colorData[4 * i + 0] = 1.0f; colorData[4 * i + 1] = 1.0f; colorData[4 * i + 2] = 1.0f; colorData[4 * i + 3] = 1.0f; } flagData[i] = currentAtom->getInheritedFlags() | getInheritedFlags(); } |
Lines 6-8 fill the position array, line 10 is for the van der Waals radius. Both physical quantities have length units (SBQuantity::length), so we extract their numerical value with the getValue function, and cast the resulting double as a float, which is expected by the renderer.
Lines 12 to 24 fill the color array based on the material applied to the visual model, if any. Precisely, if a material is applied to the visual model, we charge it of filling the color array, based on the current atom we’re examining. Else, we check whether the atom itself has a color scheme in order to use it. Else, we set the color to white with lines 20 to 23 (red, green, blue and opacity are set to their maximum value: 1.0f).
Finally, line 27 fills the flag array based on an “or” combination of the flags of the visual model and the flags of the current atom, to help the user have visual feedback when nodes are highlighted or selected.
We can now ask SAMSON to display these spheres:
SAMSON::displaySpheres( numberOfAtoms, positionData, radiusData, colorData, flagData); |
And we complete the display function by cleaning up memory:
delete[] positionData; delete[] radiusData; delete[] colorData; delete[] flagData; |
Casting shadows
SAMSON uses a shadow map algorithm in order to render shadows. This algorithm performs a hidden rendering pass from the point of view of the light, and uses this render to determine whether a point is shadowed during the main rendering pass. In order to cast shadows, we thus need to render spheres again, but in the displayForShadow function. For faster results, we could avoid sending colors to SAMSON, since the shadow pass only cares to render geometry and determine the depth of objects. For simplicity, however, we can just call the display function from the displayForShadow function:
void SEVanDerWaalsVisualModel::displayForShadow() { display(); } |
The visual model then casts shadows:
Enabling selection
In SAMSON, picking objects in the viewport is also implemented with rendering functions. However, instead assigning colors to each pixel of the viewport, we assign the index of the node being rendered at this location. Visual models can thus become selectable if they implement the displayForSelection function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | void SEVanDerWaalsVisualModel::displayForSelection() { unsigned int numberOfAtoms = atomIndexer.size(); float* positionData = new float[3 * numberOfAtoms]; float* radiusData = new float[numberOfAtoms]; unsigned int* nodeIndexData = new unsigned int[numberOfAtoms]; for (unsigned int i = 0; i < numberOfAtoms; i++) { SBAtom* currentAtom = atomIndexer[i]; SBPosition3 position = currentAtom->getPosition(); positionData[3 * i + 0] = (float)position.v[0].getValue(); positionData[3 * i + 1] = (float)position.v[1].getValue(); positionData[3 * i + 2] = (float)position.v[2].getValue(); radiusData[i] = (float)currentAtom->getVanDerWaalsRadius().getValue(); nodeIndexData[i] = currentAtom->getNodeIndex(); } SAMSON::displaySpheresSelection( numberOfAtoms, positionData, radiusData, nodeIndexData); delete[] positionData; delete[] radiusData; delete[] nodeIndexData; } |
This function is shorter than the display function, since we do not have to deal with colors. The main difference is line 20, where we fill the index array with the unique node index that SAMSON has assigned to the atom. As a result, when the user attempts to pick a sphere in the visual model, the atom is selected: