macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Developing Visualization Applications with Cocoa and VTK, Part 2
Pages: 1, 2, 3

Most of the attributes are self explanatory, except perhaps the interpolation method. The SetInterpolation() method sets the algorithm used for shading the polygons used to build up our 3D sphere in OpenGL. The VTK_GOURAUD constant corresponds to an algorithm that makes our spheres look more round, rather than polyhedral.



Note that Delete() is called on the sphere actor after it has been added to the renderer. This may seem strange, but remember that the Delete() method corresponds to the Obj-C release method. The renderer retains the actor in the AddActor() method, so sphereActor is not de-allocated.

The displayNextFrame method is called by our animation timer and is responsible for moving the atoms, and updating the view.

-(void)displayNextFrame {
    [self updateAtomPositions];
    [self updateActors];
    [self setNeedsDisplay:YES];
}

We will come to the updateAtomPositions method in a minute; it changes the atoms' positions and velocities. The updateActors method locates the spheres displayed on the screen according to the atoms' positions.

-(void)updateActors {
    vtkRenderer *renderer = [self renderer];
    vtkActor *actor;
    vtkActorCollection *coll = renderer->GetActors();
    int actorIndex;

    for ( actorIndex = 0; actorIndex < coll->GetNumberOfItems(); 
        ++actorIndex) {
        actor = (vtkActor *)coll->GetItemAsObject(actorIndex);
        actor->SetPosition(
            _atoms[actorIndex].coords[0], 
            _atoms[actorIndex].coords[1], 
            _atoms[actorIndex].coords[2]);
    }

}

This code makes use of the vtkActorCollection class, which is a bit like an NSArray especially for vtkActor objects. The GetActors() method returns all the actors, and we then iterate over the actors, setting their positions using the SetPosition() method with the current coordinates of the atoms. The GetItemAsObject() method returns a pointer to the vtkObject object at the index given. vtkObject is the VTK analog of NSObject in Cocoa. Because we know that the returned object is actually a vtkActor, we cast the pointer returned to be a pointer to a vtkActor. (Such casts are common in C++, which has very strict static typing.)

Now let's move on to the really interesting stuff, the physics. Before you suffer any flashbacks to the horror of high-school physics classes, let me set your mind at ease: I won't cover the physics of the atoms' motion in any detail here. If you are so inclined, I am sure you can figure out what is happening from the code, and if not, I'm sure you don't want to hear it. In any case, it is peripheral to the task at hand: you could use any propagation scheme you fancy to move the atoms.

The updateAtomPositions method encapsulates a 17-year-old's worst examination nightmare, updating the atom positions according to the laws of Newton and the assumption of Simple Harmonic Motion.

-(void)updateAtomPositions {

    if ( !_playing) return;

    float bondEquilibriumLengths[NUMBER_OF_BONDS] = 
        { 1.0, 1.5, 2.0};

    float bondStiffnesses[NUMBER_OF_BONDS];
    bondStiffnesses[0] = 0.5 * _stiffnessFactor;
    bondStiffnesses[1] = 1.0 * _stiffnessFactor;
    bondStiffnesses[2] = 0.1 * _stiffnessFactor;
    
    int bondConnectivityFirstAtom[NUMBER_OF_BONDS]  = 
        { 0, 0, 1 };
    int bondConnectivitySecondAtom[NUMBER_OF_BONDS] = 
        { 1, 2, 2 };
    
    // Calculate the coordinate differences and 
    // bond lengths for each bond.
    float bondCoordDifferences[NUMBER_OF_BONDS][3];
    float bondLengths[NUMBER_OF_BONDS] = { 0.0, 0.0, 0.0 };
    unsigned coordIndex, bondIndex;
    for ( bondIndex = 0; bondIndex < NUMBER_OF_BONDS; 
        ++bondIndex ) {
        unsigned firstAtomIndex  = 
            bondConnectivityFirstAtom[bondIndex];
        unsigned secondAtomIndex = 
            bondConnectivitySecondAtom[bondIndex];
        for ( coordIndex = 0; coordIndex < 3; ++coordIndex ) {
            bondCoordDifferences[bondIndex][coordIndex] = 
                _atoms[firstAtomIndex].coords[coordIndex] - 
                _atoms[secondAtomIndex].coords[coordIndex];
            bondLengths[bondIndex] += 
                pow( bondCoordDifferences[bondIndex][coordIndex], 
                    2 );
        }
        bondLengths[bondIndex] = sqrt( bondLengths[bondIndex] );
    }
    
    // Update coordinates and velocities.     
    float temp;
    for ( bondIndex = 0; bondIndex < NUMBER_OF_BONDS;
        ++bondIndex ) {
        unsigned firstAtomIndex  = 
            bondConnectivityFirstAtom[bondIndex];
        unsigned secondAtomIndex = 
            bondConnectivitySecondAtom[bondIndex];
        temp = -PROPAGATION_TIME_STEP * 
               bondStiffnesses[bondIndex] * 
               ( bondLengths[bondIndex] - 
                  bondEquilibriumLengths[bondIndex] ) / 
               bondLengths[bondIndex];
        for ( coordIndex = 0; coordIndex < 3; ++coordIndex ) {
            _atoms[firstAtomIndex].velocity[coordIndex] += 
                temp * 
                bondCoordDifferences[bondIndex][coordIndex];
            _atoms[secondAtomIndex].velocity[coordIndex] -= 
                temp *
                bondCoordDifferences[bondIndex][coordIndex];
        }
    }
    
    unsigned atomIndex;
    for ( atomIndex = 0; atomIndex < NUMBER_OF_ATOMS; 
        ++atomIndex ) {
        for ( coordIndex = 0; coordIndex < 3; 
            ++coordIndex ) {
            _atoms[atomIndex].coords[coordIndex] += 
                PROPAGATION_TIME_STEP * 
                _atoms[atomIndex].velocity[coordIndex];
        }
    }
        
}


@end

Note that the first line in this method returns if the _playing flag is set to NO. This is how the animation is paused: if _playing is NO, the atoms simply don't get moved. The rest of the method performs the mathematical gymnastics necessary to integrate the molecule's equations of motion (whatever that means).

Take a quick breather and look back over the code we have written, in particular the VTK-related code. First of all, note how little there actually is. We created a few objects, connected them together into a pipeline, set a few properties, and bingo: a 3D molecule. You may not be that impressed that you can create spheres in this way, because OpenGL also has methods to make this easy, but you may be more impressed if I tell you that you can create just about any 3D visualization with VTK using the same simple approach. The code required to generate a complex isosurface is not much more complicated or extensive than that required to generate a sphere. With 50 lines of code you can do just about anything with VTK, and not have a polygon in sight. Well, maybe in sight, but not in mind.

Now build and run the project, and if all has gone well, you are ready to experience Animoltion.

Animolting

Animoltion is not exactly rocket science. Just press Play to start the animation and Stop to pause. Pressing Play again will cause the animation to continue from where it was paused.

We can also now finally solve the mystery of the stiffness slider. If you move the slider to the right, you should see the molecule become stiffer, with the atoms vibrating more rapidly but not moving as far. Moving the slider to the left makes the molecule more floppy, with the atoms vibrating less rapidly, and traveling farther.

Apart from the controls we have built into Animoltion, VTK brings several of its own to the table. Clicking on the 3D view rotates the molecule, for example. Control-Shift-click zooms in and out, depending on where you click. Shift-click translates the view sideways. Control-click causes the view to rotate around the line of sight.

Sometimes you will notice that the atoms move outside the view, or come too close to the camera and are chopped off. In such instances you can press "r", to reset the camera location. You might also try pressing "w", which shows the atoms in wireframe; pressing "s" makes them solid again.

Where to From Here?

In Part 1 I mentioned a few resources at the end which I think are well worth looking up if you are interested in giving VTK a whirl. You can save yourself a lot of time by purchasing the Visualization Toolkit User's Guide from the Kitware site. Also keep an eye out for The Visualization Toolkit: An Object-Oriented Approach to 3-D Graphics, a new edition of which is due out soon. This book is more theoretical than the user's guide, taking a more in-depth look at the algorithms used in VTK and visualization in general. The Kitware site is also a good source of information. The VTK docs, which are hosted there, are indispensable. Lastly, don't forget the horse's mouth: the VTK source code. If you have followed this article to the letter, you already have a copy on your hard disk.

Acknowledgments

Before signing off I want to send a big thanks to Michael Norton and Yves Starreveld. Apart from igniting my interest in VTK via his Mac DevCenter article, Michael also read the early drafts of this article, and Yves--the guru of VTK on Mac OS X, and the man responsible for the Cocoa port--also offered his proofreading services free-of-charge.

Drew McCormack works at the Free University in Amsterdam, and develops the Cocoa shareware Trade Strategist.


Read more Developing for Mac OS X columns.

Return to the Mac DevCenter.