Cocoa for Scientists (Part XV): Continuing 3D Visualization
Author: Drew McCormack
Website: http://www.maccoremac.com
Last time, we started a project to build a simple 3D visualization app using Cocoa and VTK. This time, we are going to finish off the source code for that project by adding some controller code. Next time, we will define the interface in Interface Builder.
Adding a Simple Model
The application we are developing, Animoltion, performs an animated visualization of three atoms. To give you a feel for where we are headed, the interface should look something like the screenshot below when finished.

We will fix the number of atoms visualized to 3. (Extending this to an arbitrary number of atoms is left as an exercise.) Each atom is defined by a position in space, a radius, and a velocity. We will use a simple C struct to store this data, rather than a full-blown class.
To add a file for the Atom struct code, follow this procedure:
- Open the Animoltion Xcode project that you used last time, or download it and open it in Xcode.
- Select the Classes group in the Groups & Files list on the left, and choose File > New File…
- Select ‘Empty File in Project’ as the file type, and click Next button.
- Type ‘Atom.h’ as the file name, and click the Finish button.
Now select the Atom.h file in the Groups & Files tree so that you can edit it in the editor on the right. Add the following code:
typedef struct _Atom {
float coords[3];
float velocity[3];
float radius;
} Atom;
// Initialize an Atom
inline Atom MakeAtom(
float c1, float c2, float c3,
float v1, float v2, float v3,
float radius ) {
Atom newAtom;
newAtom.coords[0] = c1;
newAtom.coords[1] = c2;
newAtom.coords[2] = c3;
newAtom.velocity[0] = v1;
newAtom.velocity[1] = v2;
newAtom.velocity[2] = v3;
newAtom.radius = radius;
return newAtom;
}
An inline function has been included to create a new Atom instance. This is merely a convenience.
Adding a Controller Class
With the model layer finished, we now need a controller class to mediate between the interface elements, and the model instances. You could just have your controller derive from NSObject, but we will use an NSDocument subclass. NSDocument is, as the name suggests, a class that represents documents. A document-based application, like Animoltion, can have multiple documents open at a time, and can create new ones. Typically, you would use NSDocument to store information on file, but in Animoltion we don’t need this functionality. Instead, we just use NSDocument as a convenient means of supporting multiple open windows, with different content in each.
When we created our project last time, we chose the Document-based Application option. This Xcode template generates an NSDocument subclass stub for you, and calls it ‘MyDocument’. We need to change that name in various places in the project, and in Interface Builder. The first step is simply to do a textual search and replace. Go to the Project view in the Xcode window (first tab if you are using the All-in-One layout), and click the Project Find tab on the right. (If you can’t see the Project Find tab, it could be that you need to drag the split view down to reveal it.)

To replace ‘MyDocument’ with ‘MoleculeDocument’:
- Enter ‘MyDocument’ in the Find field, and make sure the popup button is set to Project, to just search in the project files.
- Click the Find button.
- Enter ‘MoleculeDocument’ in the Replace field.
- Click the Replace button, and when prompted if you really want to replace the text, click the Replace button.
That will replace any occurrences of ‘MyDocument’ in the text, but doesn’t change file names, so
- Open the Classes group in the Groups & Files list view.
- Option click on MyDocument.h and MyDocument.m to change their names to MoleculeDocument.h and MoleculeDocument.mm. (Note again the .mm extension for Objective-C++)
- Repeat this for the MyDocument.nib file, which is in the Resources group.
We can now add the source code for the MoleculeDocument class. Add the following in the header file:
#import <Cocoa/Cocoa.h>
#import "Atom.h"
#define id Id
#import "vtkSphereSource.h"
#import "vtkDataSetMapper.h"
#undef id
#define NUMBER_OF_ATOMS 3
#define NUMBER_OF_BONDS 3
@class BasicVTKView;
@interface MoleculeDocument : NSDocument
{
IBOutlet BasicVTKView *vtkView;
IBOutlet NSButton *playStateButton;
BOOL isPlaying;
vtkSphereSource *sphereSource;
vtkDataSetMapper *sphereMapper;
NSTimer *animationTimer;
Atom atoms[NUMBER_OF_ATOMS];
float stiffnessFactor;
}
-(IBAction)togglePlayState:(id)sender;
-(IBAction)updateStiffness:(id)sender;
-(void)addSphereActorWithRadius:(float)radius
red:(float)r green:(float)g blue:(float)b alpha:(float)a;
-(void)updateAtomPositions;
-(void)updateActors;
-(void)displayNextFrame;
@end
The interface includes a few outlets to items in the interface; we will connect those up a bit later in Interface Builder. There is also an NSTimer, to animating the visualization; an array of atoms; a parameter for the stiffness of the ‘springs’ between the atoms (ie stiffnessFactor); and some VTK objects (ie vtkSphereSource and vtkDataSetMapper). The latter will be used to supply the data to draw spheres for the atoms.
The methods include some actions for the interface, to play and pause the animation, and to update the spring stiffness parameter, as well as methods related to adding actors (eg spheres) to the VTK view, and updating the positions of the actors as time passes.
The implementation of MoleculeDocument is quite extensive, but much of it is simply elementary physics related to propagating the positions of the atoms — assuming harmonic springs between each — as a function of time.
#import "MoleculeDocument.h"
#import "BasicVTKView.h"
#import "Atom.h"
#define id Id
#import "vtkProperty.h"
#import "vtkActor.h"
#import "vtkPolyData.h"
#import "vtkInteractorStyleSwitch.h"
#import "vtkCocoaRenderWindowInteractor.h"
#import "vtkSmartPointer.h"
#undef id
static const float ANIMATION_TIME_STEP = 0.03;
static const float PROPAGATION_TIME_STEP = 0.20;
static const unsigned SPHERE_RESOLUTION = 16;
@implementation MoleculeDocument
-(void)setupvtkView
{
vtkSmartPointer<vtkInteractorStyleSwitch> intStyle =
vtkSmartPointer<vtkInteractorStyleSwitch>::New();
intStyle->SetCurrentStyleToTrackballCamera();
[vtkView getInteractor]->SetInteractorStyle(intStyle);
sphereSource = vtkSphereSource::New();
sphereSource->SetThetaResolution(SPHERE_RESOLUTION);
sphereSource->SetPhiResolution(SPHERE_RESOLUTION);
sphereMapper = vtkDataSetMapper::New();
sphereMapper->SetInput(sphereSource->GetOutput());
isPlaying = NO;
[vtkView setNeedsDisplay:YES];
}
#pragma mark -
-(void)dealloc {
[animationTimer invalidate];
sphereSource->Delete();
sphereMapper->Delete();
[super dealloc];
}
-(NSString *)windowNibName
{
return @"MoleculeDocument";
}
-(NSData *)dataRepresentationOfType:(NSString *)aType
{
return nil;
}
- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType
{
return YES;
}
-(void)awakeFromNib {
[self setupvtkView];
animationTimer = [NSTimer scheduledTimerWithTimeInterval:ANIMATION_TIME_STEP
target:self
selector:@selector(displayNextFrame)
userInfo:nil
repeats:YES];
stiffnessFactor = 1.0;
// Initialize atom positions
atoms[0] = MakeAtom( 0.0, 0.0, 0.0, 0.3, 0.0, 0.0, 0.5 );
[self addSphereActorWithRadius:atoms[0].radius
red:0.7 green:0.0 blue:0.0 alpha:1.0];
atoms[1] = MakeAtom( 1.0, 0.0, 0.0, -0.3, 0.0, 0.2, 0.7 );
[self addSphereActorWithRadius:atoms[1].radius
red:0.0 green:0.7 blue:0.0 alpha:1.0];
atoms[2] = MakeAtom( 0.0, 1.0, 0.0, 0.0, 0.0, -0.2, 0.3 );
[self addSphereActorWithRadius:atoms[2].radius
red:0.0 green:0.0 blue:0.7 alpha:0.5];
[self updateActors];
[vtkView setNeedsDisplay:YES];
}
-(IBAction)togglePlayState:(id)sender {
isPlaying = !isPlaying;
}
-(IBAction)updateStiffness:(id)sender {
stiffnessFactor = [sender floatValue];
}
-(void)addSphereActorWithRadius:(float)radius
red:(float)r green:(float)g blue:(float)b alpha:(float)a{
vtkActor *sphereActor = vtkActor::New();
sphereActor->SetMapper(sphereMapper);
sphereActor->GetProperty()->SetColor(r, g, b);
sphereActor->GetProperty()->SetOpacity(a);
sphereActor->GetProperty()->SetInterpolation( VTK_GOURAUD );
sphereActor->SetScale(radius, radius, radius);
[vtkView getRenderer]->AddActor(sphereActor);
sphereActor->Delete();
}
-(void)displayNextFrame {
[self updateAtomPositions];
[self updateActors];
[vtkView setNeedsDisplay:YES];
}
-(void)updateAtomPositions {
if ( !isPlaying) 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];
}
}
}
-(void)updateActors {
vtkRenderer *renderer = [vtkView getRenderer];
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]);
}
}
@end
Dissecting MoleculeDocument
Much of the interesting code is in the setupvtkView method.
-(void)setupvtkView
{
vtkSmartPointer<vtkInteractorStyleSwitch> intStyle = vtkSmartPointer<vtkInteractorStyleSwitch>::New();
intStyle->SetCurrentStyleToTrackballCamera();
[vtkView getInteractor]->SetInteractorStyle(intStyle);
sphereSource = vtkSphereSource::New();
sphereSource->SetThetaResolution(SPHERE_RESOLUTION);
sphereSource->SetPhiResolution(SPHERE_RESOLUTION);
sphereMapper = vtkDataSetMapper::New();
sphereMapper->SetInput(sphereSource->GetOutput());
isPlaying = NO;
[vtkView setNeedsDisplay:YES];
}
VTK has a number of different options for how the user interacts with the visualization; you set the one you want using a vtkInteractorStyleSwitch. In the first few lines of this method, a new vtkInteractorStyleSwitch is created, is set to use a trackball-type interaction, and then passed to the vtkInteractor object belonging to the VTK view.
If you are wondering about what that vtkSmartPointer is all about, don’t worry too much: it is a bit like calling autorelease in Cocoa; it will automatically result in the retain count of the vtkInteractorStyleSwitch being reduced by one when the method completes. If the C++ template notation scares you, you can simply remove the vtkSmartPointer and call Delete at the end of the routine on the vtkInteractorStyleSwitch object intStyle.
The next code block in the setupvtkView method creates a vtkSphereSource, which supplies the data coordinates used to draw a sphere out of polygons. The vtkDataSetMapper object maps the coordinates of this sphere to 3D graphics primitives like polygons for rendering in the 3D VTK view. The method finishes by calling the Cocoa setNeedsDisplay: method, which tells the VTK view to redraw itself when convenient.
The model Atom objects are created in the awakeFromNib method.
-(void)awakeFromNib {
[self setupvtkView];
animationTimer = [NSTimer scheduledTimerWithTimeInterval:ANIMATION_TIME_STEP
target:self
selector:@selector(displayNextFrame)
userInfo:nil
repeats:YES];
stiffnessFactor = 1.0;
// Initialize atom positions
atoms[0] = MakeAtom( 0.0, 0.0, 0.0, 0.3, 0.0, 0.0, 0.5 );
[self addSphereActorWithRadius:atoms[0].radius red:0.7 green:0.0 blue:0.0 alpha:1.0];
atoms[1] = MakeAtom( 1.0, 0.0, 0.0, -0.3, 0.0, 0.2, 0.7 );
[self addSphereActorWithRadius:atoms[1].radius red:0.0 green:0.7 blue:0.0 alpha:1.0];
atoms[2] = MakeAtom( 0.0, 1.0, 0.0, 0.0, 0.0, -0.2, 0.3 );
[self addSphereActorWithRadius:atoms[2].radius red:0.0 green:0.0 blue:0.7 alpha:0.5];
[self updateActors];
[vtkView setNeedsDisplay:YES];
}
A spherical actor is created for each one, to represent the atom in the 3D VTK view. These are added using the addSphereActorWithRadius:red:green:blue:alpha method. As the name suggests, this adds a sphere to the view, with the radius and the RGBA values passed. The code to do that looks like this
-(void)addSphereActorWithRadius:(float)radius red:(float)r green:(float)g blue:(float)b alpha:(float)a{
vtkActor *sphereActor = vtkActor::New();
sphereActor->SetMapper(sphereMapper);
sphereActor->GetProperty()->SetColor(r, g, b);
sphereActor->GetProperty()->SetOpacity(a);
sphereActor->GetProperty()->SetInterpolation( VTK_GOURAUD );
sphereActor->SetScale(radius, radius, radius);
[vtkView getRenderer]->AddActor(sphereActor);
sphereActor->Delete();
}
By setting the mapper of the new vtkActor to the vtkDataSetMapper that was created earlier, the actor will be a sphere shape. This method uses the RGBA values passed to set the properties of the actor, and finally adds it to the VTK view’s vtkRenderer, which renders it to the screen.
The one important aspect of the MoleculeDocument class that we have not yet touched upon is animation. An NSTimer is started in awakeFromNib to invoke a particular method at regular time intervals:
animationTimer = [NSTimer scheduledTimerWithTimeInterval:ANIMATION_TIME_STEP
target:self
selector:@selector(displayNextFrame)
userInfo:nil
repeats:YES];
The target: argument is the object that will be messaged when the timer fires, and the selector: argument is the method actually invoked. repeats: tells the NSTimer to keep firing until it is invalidated (in dealloc), rather than just firing once and stopping.
When the timer fires, it invokes displayNextFrame method:
-(void)displayNextFrame {
[self updateAtomPositions];
[self updateActors];
[vtkView setNeedsDisplay:YES];
}
This updates the positions of the atoms, matches the positions of the actors to the Atoms, and finally marks the view as needing a redraw. The updateAtomPositions method is quite long, but has more to do with high-school physics than VTK, so I won’t discuss it here. updateActors sets the position of the spherical actors with the SetPosition method, after first retrieving them from the renderer.
-(void)updateActors {
vtkRenderer *renderer = [vtkView getRenderer];
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]);
}
}
Build and Go to Next Week
This tutorial is already getting quite extensive, so we’ll leave it here for now, and continue on next time. You should be able to build again to make sure everything has gone well. But before you do, you should turn off Zero Link, because I found that it caused problems.
- Double click the Animoltion project icon at the root of the Groups & Files list view.
- In the Info window, select the Build tab.
- Choose Debug in the Configuration popup button.
- Locate ZeroLink in the settings, and make sure it is unchecked. (You can use the filter box to narrow your search for ZeroLink.)
The source code to this point can be downloaded here. In the next, and last (promise!), tutorial, we’ll delve into Interface Builder and get Animoltion animating.



Comments
why VTK?
I can't exactly say that I work with visualization, but I certainly do use complex 3D graphics to visualize and animate a number of physical and chemical phenomena. I'm already comfortable with OpenGL and GLSL. I would have done this same excersize with several GLUT Spheres and use similar methods for translation, and animation. I'm trying to figure out if there is anything that VTK can offer that is more dificult in OpenGL. What would be the advantage of using VTK? Learning curve? Speed of development?
thanks for this entire series.
Justin Mitchell
Why VTK?
Hi Justin,
Of course, I have chosen a very simple example, one that is quite simple to do in OpenGL, since it only works with spheres. But VTK has much more, and it is nearly all about visualizing data. OpenGL just gives you the primitives to draw with; VTK tells you what to draw. It's like the difference between a 2D graphics engine like Quartz 2D, and a plotting framework. The plotting framework sits on top of the drawing engine.
Just to get you a bit of an idea of some of the things you can do with VTK, take the example of a scalar field or density. With maybe 20 lines of VTK code, you can generate an isosurface or volume rendering of the data, complete with user interaction (rotating, translating etc). With another 20 lines or so, you could add a cut plane to it. The point is not that you can't do this with OpenGL yourself, but it will take you thousands of line. Effectively you will be implementing the same sorts of algorithms (eg marching cubes) that are already built into VTK.
Hope that clarifies things.
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org
Re: Why VTK? Ah ha.
AAahhh. That does clarify. Sounds like a much more useful tool than I thought.
Justin
cool tut!
thanks for the tut. can't wait for the next one!
i have a question of a different nature though.
you know the inspector window of keynote or the system preference window...
when you push a button, a new view comes up. how is that done?
i don't suppose it's a "hide everything not related if this button is pushed".
if it's so, drawing the interface in IB is going to be really messy.
Tab View
Hi Ken,
You are best off using an NSTabView. You can put a different view in each tab.
If you want to customize the buttons, like in keynote, just create an NSTabView with the labels hidden. Then connect up your buttons such that when you press one, you select the appropriate tab in the tab view.
Hope that helps,
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org
VTK Tutorial still working for XCode 2.4 on Intel Macs?
It is great to see tutorial's like this so those of us who aren't professional programmers can take advantage of libraries like VTK. Thanks!
I am having some problems getting the second and third sections of this tutorial to run though. The first one ran without problems.
I get a number of linker warnings like:
/usr/bin/ld: warning /Users/AV/Develop/VTK/VTKBin/lib/libvtkCommon.a archive's cputype (7, architecture i386) does not match cputype (18) for specified -arch flag: ppc (can't load from it)
Followed by the error:
/usr/bin/ld: Undefined symbols:
vtkProperty::SetColor(double, double, double)
vtkRenderer::AddActor(vtkProp*)
vtkRenderer::GetActors()
vtkCollection::GetItemAsObject(int)
vtkCollection::GetNumberOfItems()
vtkSphereSource::New()
...
/Users/AV/Develop/Animoltion/build/Animoltion.build/Release/Animoltion.build/Objects-normal/ppc/BasicVTKView.o reference to undefined .objc_class_name_vtkCocoaGLView
/Users/AV/Develop/Animoltion/build/Animoltion.build/Release/Animoltion.build/Objects-normal/ppc/BasicVTKView.o reference to undefined vtkRenderer::New()
/Users/AV/Develop/Animoltion/build/Animoltion.build/Release/Animoltion.build/Objects-normal/ppc/BasicVTKView.o reference to undefined vtkRenderWindow::New()
/Users/AV/Develop/Animoltion/build/Animoltion.build/Release/Animoltion.build/Objects-normal/ppc/BasicVTKView.o reference to undefined vtkRenderWindowInteractor::SetRenderWindow(vtkRenderWindow*)
/Users/AV/Develop/Animoltion/build/Animoltion.build/Release/Animoltion.build/Objects-normal/ppc/BasicVTKView.o reference to undefined vtkRenderWindowInteractor::New()
collect2: ld returned 1 exit status
Any suggestions?
AV
Build Config?
It seems you are trying to build a universal build, but the VTK libraries you have are only for intel mac. If you build using the Debug configuration, it should only build for i386 and work fine. If you build the Release build, it will try to build for ppc as well, and this will fail. You can remove PPC from the release build by double clicking on the Animoltion target, clicking on the Build tab in the inspector, and double clicking the Architecture setting. You should get a sheet with Intel and PPC available. Uncheck PPC and try again.
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org