MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Developing for Mac OS X Developing Visualization Applications with Cocoa and VTK

by Drew McCormack
02/11/2003

Back in June I read Michael Norton's MacDevCenter article 3-D Data Visualization on Mac OS X. I am a scientist by trade, but I hadn't made much use of visualization in my research. Michael's article opened my eyes to a whole new way of doing things and showed how easy it could be to turn your garden variety iBook into a powerful scientific ally.

For those of you not familiar with VTK, it is to visualization what Cocoa is to application development: VTK provides a high-level object-oriented framework which allows you to easily visualize 3D data sets without having to write any low-level OpenGL code. OpenGL is used to perform the eventual rendering, but the software developer writes code at a higher level of abstraction. Like Cocoa, VTK can literally save you having to write thousands of lines of source code.

In addition to being a scientist, I am also a Cocoa enthusiast, spending every spare minute of the day writing shareware for Mac OS X. So it wasn't long after reading Michael's article that I began wondering if it were possible to get the best of both worlds in one app: Cocoa's great Application building frameworks, and VTK's equally impressive visualization libraries. It turns out it is very possible to make visualization apps to-die-for with Cocoa and VTK. This article is about how to do just that.


Screen shot.
Dynasity, an application written by the author for visualizing time-dependent fields, is written entirely with Cocoa and VTK. It is currently in beta testing.

I'll lead you through integrating VTK and Cocoa by way of example: In Part 1 we will learn the general steps required to setup a Cocoa-VTK project, and in Part 2 we will develop a simple Cocoa-VTK 3D animation application. Along the way we will learn how to compile VTK, configure Project Builder, overcome a few incompatibilities between VTK and Cocoa, and even get a quick intro to Objective-C++.

Building VTK

The best place to start in order to get a successful VTK build on Mac OS X is Michael Norton's original MacDevCenter article. Most of what we need to do here is covered there. But there are a few minor differences, so we will quickly move through the steps required to install VTK suitably for use in a Cocoa application.

Firstly, you will need the tool "CMake" to build VTK. If you haven't installed this already, go to www.cmake.org and download the source code file CMake1.4.5-src-unix.tar.gz. I have found this version to be stable. Unless you enjoy the thrill of the unknown, I suggest you steer clear of using CVS to get the source. I encountered problems building VTK when I used a CVS derived CMake, and you don't want to waste time on CMake when you could be wasting time on the really fun stuff.

Unpack the CMake source, and change directories:

tar xvzf CMake1.4.5-src-unix.tar.gz
cd CMake-1.4.5

Now configure, build, and install CMake

./configure
make
sudo make install

Note that you need to use "sudo" for the last step, so you will need to be an administrator, and enter your password at the prompt. The last thing you need to check is that your PATH environment variable includes the /usr/local/bin directory where cmake gets installed. For tcsh you need to put this in your ~/.tcshrc file:

setenv PATH $PATH:/usr/local/bin

and then source the file

source ~/.tcshrc

With CMake installed, you're ready to install VTK. Because Cocoa has not been supported that long in VTK, you will need a relatively new copy of the source code, and you will need CVS for this. In a terminal window, change to the directory where you would like to install VTK. I will assume this is the directory "Develop" in your home directory, in which case you should issue this command

cd ~/Develop

Now checkout the VTK source code with the following two commands:

cvs -d :pserver:anonymous@public.kitware.com:/cvsroot/VTK login
cvs -d :pserver:anonymous@public.kitware.com:/cvsroot/VTK checkout -D 09/20/2002 VTK

When asked for a password after the first command, enter vtk. If you want the very latest sources, leave out the -D 09/20/2002 in the second command. I had trouble compiling more recent versions of VTK and found that for this date the code will at least compile and run, even if it doesn't include cutting edge developments. Now go and watch the wrestling for half an hour: checking out the source can take a while.

In the ~/Develop directory, create a directory for building VTK, which I will call VTKBuild and change to that directory

mkdir VTKBuild
cd VTKBuild

Issue the following command:

cmake ../VTK

When this completes, you should find a file called CMakeCache.txt in your VTKBuild directory. This is a configuration file for the cmake system. We need to make a few changes to it, so use your favorite editor to search the CMakeCache.txt file for the following entries, and change them accordingly:

VTK_USE_CARBON:BOOL=OFF
VTK_USE_COCOA:BOOL=ON 

Unlike in Michael Norton's original article, we're going to build static libraries, so we are leaving the BUILD_SHARED_LIBS option OFF. Static libraries make it easier to distribute your app once you have written it because all binary code is included in the executable. Your users do not need to install VTK in order to use your app. (It is possible to embed shared libraries in an app too, but it is much more involved, so we'll steer clear of it here.)

Run cmake again a couple of times from the VTKBuild directory.

cmake ../VTK; cmake ../VTK

Doing this is advised whenever you make changes to the CMakeCache.txt file, as it can sometimes require several iterations before the changes have completely propagated through the whole system.

Now type ...

make

... and pray. Just kidding. If all goes well, you should end up with a handful of static libraries in the VTKBuild/bin directory, and you are ready to start on the Cocoa stage of the journey.

Creating a Cocoa/VTK project

As I mentioned previously, in Part 2 we are going to build a small 3D animation program, so we will now start setting up a Project Builder project with that in mind. To begin with, open Project Builder, and choose New Project... from the File menu. Select Cocoa Application, enter the project name "Animoltion", select where you want to have the project folder, and click Finish. (To find out why it is called Animoltion, you will have to wait for our next riveting installment.)

VTK on Mac OS X is based on OpenGL, so we need to add the OpenGL framework--installed on every Mac OS X computer--to our new project. Choose "Add Frameworks..." from the "Project" menu, and browse to the framework bundle

/System/Library/Frameworks/OpenGL.framework

Click "Add", and accept the default options in the dialog that appears. It's not always easy to predict where the framework will appear in your "Files" tab of Project Builder, but it belongs in "Frameworks/Linked Frameworks", so drag it in there.

Now we need to add the VTK libraries to the project. Project Builder doesn't create a "Libraries" group, so we will make one ourselves. Select the root "Animoltion" group in the "Files" tab, and choose "New Group" from the "Project" menu. Enter "Libraries" and press return.

To add the libraries, we need to do something which might seem strange. Project Builder doesn't include an "Add Libraries..." command, so we will use the "Add Frameworks..." command from the "Project" menu. Select this menu item, and browse to the ~/Develop/VTKBuild/bin directory where the VTK libraries were compiled. Select the following libraries:

libvtkCommon.a
libvtkFiltering.a
libvtkGraphics.a
libvtkImaging.a
libvtkRendering.a

and click "Add" twice, again accepting the default options in the dialog that appears. If the libraries do not appear in your "Libraries" group, just drag them in there.

Building Cocoa Applications: A Step by Step Guide

Related Reading

Building Cocoa Applications: A Step by Step Guide
By Simson Garfinkel, Michael Mahoney

We are only half way when it comes to importing VTK into our project; we still haven't got the header files. (This is a big advantage of Apple's frameworks: they include both the library binaries and header files in a single bundle.) Create a new group in the "Other Sources" group, and call it "VTK Headers". The problem we now face is that VTK's header files are distributed over multiple directories. This is where Jaguar's new improved finder can be a big help. (On pre-Jaguar versions of Mac OS X, you will probably need to use Sherlock instead.)

Make the Finder active, and choose "Find..." from the File menu. Choose to search in "Specific Places" from the popup and add the ~/Develop/VTK and ~/Develop/VTKBuild directories. Search for files whose name contains .h and vtk. You will need to add a second search criteria by pressing the "+" button in order to add vtk. Click the "Search" button. When all the files have been found, select them all, and drag them to the "VTK Headers" group in your Animoltion project. Click "Add" in the sheet that appears.

By default, Project Builder sets up your project to use prebinding. Since we didn't compile VTK with any prebinding flags set, we need to turn this off in our project. Click the "Targets" tab, and then select the "Animoltion" target. Click "Linker Settings" under "Settings", and deselect the "Prebind" checkbox.

Introduction to the Cocoa in VTK

VTK is a cross-platform framework and a good thing too. But this means that things are not necessarily done in the way a Cocoa programmer might expect. So before writing code specific to our molecular viewer, we will need a few general classes to adapt VTK more to the Cocoa way of doing things.

In order to achieve portability, VTK is based on a system of abstract factories. Usually a VTK programmer would call the "New" method of the VTK class vtkRenderWindow, in order to create a new window to render in. A concrete subclass of vtkRenderWindow, in our case vtkCocoaRenderWindow, is created by New and returned; but the programmer doesn't need to know which subclass is used: she just interacts with the window via the methods of the abstract vtkRenderWindow class. In this way the code can be made portable. If the program is compiled and run on a different architecture, the New method will simply instantiate a different concrete subclass, and since all interaction with the window occurs via the methods of the abstract vtkRenderWindow class, the code will work without modification. Elegant, isn't it?

This is excellent object-oriented design and highly admirable, but we want to be a bit less admirable here. We want to be able to do things in the Cocoa way, creating a custom view in Interface Builder, dragging it into an NSWindow, connecting outlets and setting actions, the way we know and love. To achieve this, we have to change the implementation of a class called vtkCocoaWindow; thanks to the dynamism of Objective-C, we can do this without subclassing, simply by using a category.

Diagram.
UML diagram for the Cocoa-related classes in VTK. Inheritance is represented by an arrow, which points to the superclass. Other connectors represent associations between classes. Cocoa classes are enclosed in red boxes, VTK classes in blue boxes, and classes written here in green boxes

But first I probably should explain what vtkCocoaWindow is. vtkCocoaWindow is an Objective-C subclass of NSWindow. When the "New" method of the abstract vtkRenderWindow class is called, an instance of vtkCocoaRenderWindow gets created. The vtkCocoaRenderWindow, which is a C++ object, needs to be able to draw with the Cocoa framework, so it creates a Cocoa object for this purpose: vtkCocoaWindow. The vtkCocoaWindow has a single NSOpenGLView in it, of the subclass vtkCocoaGLView, and this is where VTK renders using OpenGL.

Phew, that was a mouthful. If this is a bit overwhelming, check out the VTK Cocoa Classes figure, which tells the same thousand words a little more concisely. And if that doesn't help, have a quick lie down and then proceed with the rest of the article, completely ignoring what you just read. If you don't care how it works, just that it does, you don't need to understand this explanation.

vtkCocoaWindow and vtkCocoaGLView are designed to work in close collaboration. If vtkCocoaWindow changes size, it resizes its vtkCocoaGLView as well. This is the way VTK expects things to be done. But what we would prefer is to have a single NSView subclass, which we can drop in any window in Interface Builder, and have everything work. So we basically want to keep vtkCocoaGLView and get rid of vtkCocoaWindow. Unfortunately, vtkCocoaWindow does some important stuff, so we can't get rid of it completely. But we can hide it in the background. We will change the methods of vtkCocoaWindow that don't fit with our aim to use vtkCocoaGLView as a standalone class by creating a category.

Categorizing vtkCocoaWindow

Due to the dynamism of Objective-C, it is possible to actually change the implementation of a class without subclassing, even without having access to the source code. We do have the source code in this case, but it would be nice not to have to change the official source of VTK, just extend it a bit. For this purpose, we will use a category.

Begin by selecting "New File..." from the File menu, and give it the name "vtkCocoaWindowModifications". Move the header and implementation files created to the Classes group. Now include the following code in vtkCocoaWindowModifications.h

#import <AppKit/AppKit.h>
#import "vtkCocoaWindow.h"

@interface vtkCocoaWindow (CocoaWindowModifications)
- (void)setvtkCocoaGLView:(vtkCocoaGLView *)thevtkCocoaGLView;
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize;
- (BOOL)windowShouldZoom:(NSWindow *)sender toFrame:(NSRect)newFrame;
@end

As you can see, this is a category of the vtkCocoaWindow class called CocoaWindowModifications. Now add the implementation for these methods to the vtkCocoaWindowModifications.m file:

@implementation vtkCocoaWindow (CocoaWindowModifications)

// This has been overloaded to prevent the window making the vtkCocoaGLView its contentView.
- (void)setvtkCocoaGLView:(vtkCocoaGLView *)thevtkCocoaGLView {
myvtkCocoaGLView = thevtkCocoaGLView;
}

// This method, and the next one, have been changed to prevent them trying to
// to resize the render window.
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize {
    return proposedFrameSize;
}

- (BOOL)windowShouldZoom:(NSWindow *)sender toFrame:(NSRect)newFrame {
    return YES;
}

@end

As indicated by the in-code comments, the implementation of the setvtkCocoaGLView: has been modified so that it does not attempt to set the vtkCocoaGLView as its content view. We don't want this here because our vtkCocoaGLView should behave like any other NSView. We want to be able to use the view in any NSWindow, or as the subview of any other NSView, and not be restricted to using it only as the content view of a vtkCocoaWindow.

The other two methods have been modified so that they no longer send messages to resize and position the vtkCocoaRenderWindow with which the vtkCocoaWindow is associated. Our objective is to transform the vtkCocoaWindow from a view object, which appears on the screen as a UI element, into a controller object, which is invisible to the user and whose role is simply to mediate between the vtkCocoaRenderWindow and vtkCocoaGLView objects. Because it is no longer a view object, we don't care about its size, and we don't want it to send geometry-related messages to the vtkCocoaRenderWindow.

The end is in VTKView

Ultimately we would like to have a single, simple NSView subclass for use in all of our Cocoa/VTK apps. We should be able to instantiate this class via Interface Builder or programmatically, and it should take care of setting up any VTK state that it needs to render itself. vtkCocoaGLView is a nice start, but it wasn't created with this degree of responsibility in mind. The way VTK is setup by default, vtkCocoaGLView is actually one of the last classes instantiated; it doesn't create any other instances itself.

Since vtkCocoaGLView already does most of what we need, we will just subclass it, and add the extra features we require to create our all-powerful view class: VTKView. First, create files for the VTKView class as described above. Before we begin to edit the new files, we need to change the extension of the VTKView.m file to .mm. Why?

VTKView will be written in Objective-C++, an Objective-C/C++ hybrid. Once you have changed the file extension, you are free to use C++ code and Objective-C code in the same file. When Project Builder sees the .mm extension, it knows to engage the Objective-C++ compiler. It's that simple. There are a few restrictions to what you can do with Objective-C++--probably the most significant is that you cannot mix the inheritance hierarchies, for example, subclassing a C++ class with an Objective-C class--but otherwise you are free to mix the two languages to your heart's content, and that is what we are going to do here.

Back to the job at hand: add the following includes/imports to the VTKView.h file:

#import <AppKit/AppKit.h>
#import "vtkCocoaGLView.h"
#import "vtkCocoaWindow.h"

#define id Id
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#undef id

As you can see, when including VTK C++ headers, such as vtkRenderer.h, it is necessary to wrap the includes in a #define/#undef block. The reason for this is that in Objective-C, "id" is a reserved keyword, and in C++ it is not. "id" gets used regularly throughout the VTK source code as a variable name, and this causes the Obj-C++ compiler problems. To avoid this, we simply redefine "id" to "Id" whenever we include VTK C++ header files.

Add the following interface block to the VTKView.h file:

@interface VTKView : vtkCocoaGLView {
    vtkCocoaWindow 			*_cocoaWindow;
    vtkCocoaRenderWindow		*_cocoaRenderWindow;
    vtkRenderer				*_renderer;
    vtkCocoaRenderWindowInteractor	*_interactor;
}

-(id)initWithFrame:(NSRect)frame;
-(void)dealloc;

// Access to VTK instances
-(vtkRenderer *)renderer;
-(vtkRenderWindow *)renderWindow;
-(vtkRenderWindowInteractor *)renderWindowInteractor;

-(void)removeAllActors;

@end

The VTKView is a subclass of vtkCocoaGLView and has pointers to all of the other Cocoa specific classes in VTK. VTKView will instantiate and coordinate these objects. The interface includes the expected initialization and deallocation methods, along with accessors for the various VTK objects required to render a scene in the view. The "removeAllActors" method is a convenience method which clears the view of "actors", which are basically the entities that make up a VTK scene.

So what does the implementation of these methods look like? Well, here it is:

#define id Id
#include "vtkRenderer.h"
#include "vtkCocoaRenderWindow.h"
#include "vtkCocoaRenderWindowInteractor.h"
#include "vtkCommand.h"
#include "vtkCamera.h"
#undef id

#import "vtkCocoaWindow.h"
#import "vtkCocoaWindowModifications.h"
#import "VTKView.h"

@implementation VTKView

-(id)initWithFrame:(NSRect)frame {

    if ( self = [super initWithFrame:frame] ) {
    
        // Create instances of VTK classes. The vtkCocoaWindow used only passes messages,
// and is not displayed.
        _cocoaWindow = [[vtkCocoaWindow alloc] initWithContentRect:frame
            styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
        [_cocoaWindow setvtkCocoaGLView:self];

        _renderer = vtkRenderer::New();
        _cocoaRenderWindow = vtkCocoaRenderWindow::New();
        _cocoaRenderWindow->SetWindowId(_cocoaWindow);
            _cocoaRenderWindow->AddRenderer(_renderer);
        _interactor = vtkCocoaRenderWindowInteractor::New();
            _interactor->SetRenderWindow(_cocoaRenderWindow);
    
        [self setVTKRenderWindow:_cocoaRenderWindow];
        [self setVTKRenderWindowInteractor:_interactor];
        [_cocoaWindow setVTKRenderWindow:_cocoaRenderWindow];
        [_cocoaWindow setVTKRenderWindowInteractor:_interactor];
        
        _interactor->Initialize();
        
     }
    
    return self;    
}

-(void)dealloc {
    _renderer->Delete();
    _cocoaRenderWindow->Delete();
    _interactor->Delete();
    [_cocoaWindow release];
    [super dealloc];
}

-(vtkRenderer *)renderer {
    return _renderer;
}

-(vtkRenderWindowInteractor *)renderWindowInteractor {
    return [self getVTKRenderWindowInteractor];
}

-(vtkRenderWindow *)renderWindow {
    return [self getVTKRenderWindow];
}    

-(void)removeAllActors {
    vtkRenderer *renderer = [self renderer];
    if ( ! renderer ) return;
    vtkActor *actor;
    vtkActorCollection *coll = renderer->GetActors();
    coll->RemoveAllItems();
}

@end

Much of this is C++ code related to constructing and destroying the VTK objects. The initWithFrame: method is the designated constructor and constructs the various VTK objects, connecting them together in the correct manner. The VTK objects are generally constructed using the Abstract Factory design pattern, which involves calling the New method.

The dealloc method releases the Objective-C members, as you would expect, but it also sends a Delete method to the VTK C++ objects. The Delete method is not exactly what you might expect: it is actually analogous to the NSObject release method. VTK, like Cocoa, uses a reference counting approach to memory management, so the Delete method does not necessarily cause the messaged object to be deallocated. The object will only really get deleted when the reference count drops to zero.

The methods following dealloc are just simple accessors. renderWindowInteractor and renderWindow are renamed vtkCocoaGLView methods.

The removeAllActors method is not strictly necessary but is convenient, so it has been included. It simply removes all vtkActor objects in the view, providing a clean slate to build a new scene in.

Download sample and code files for this article.

If you download the code for this article, you will find that several other methods are included in the VTKView class. These methods all relate to mouse events and override methods in vtkCocoaGLView. These are actually bug fixes: You will undoubtedly find that if you don't include this code, and try to interact with a VTK view by clicking or dragging your mouse, it will not behave as described in the VTK documentation. The correct behavior can be recovered by including the mouse-event methods from the downloaded code.

Ready to Roll

That's it for part 1. You should now have a VTK build and Project Builder project setup to create that killer visualization app you have been dreaming about. If you can't wait for our next installment, when we will see how you make use of VTK within Cocoa, you can checkout the Kitware site, where VTK is hosted: http://www.kitware.com/. There you will find lots of support for VTK, including documentation and mailing lists. While there you can also purchase the "VTK Users's Guide", which is a very good overview of what you can do with the VTK and how you do it.

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.

Copyright © 2009 O'Reilly Media, Inc.