Cocoa for Scientists (Part XIV): Beginning 3D Visualization

Author: Drew McCormack
Website: http://www.mentalfaculty.com

We have now covered the fundamental aspects of Cocoa development in this tutorial series. From here on, we will branch out in many diverse directions, with each tutorial being more stand-alone than the ones preceding this one. I want to begin this exploration by revamping a pair of tutorials (1, 2) that I wrote for MacDevCenter several years ago. In this tutorial, and the next, you will learn how to write a simple visualization application for Mac OS X using the Visualization Toolkit (VTK) and Cocoa.

The Visualization Toolkit (VTK)

VTK is a 3D visualization library written in C++, with interfaces to scripting languages like Tcl and Python. To prepare for this tutorial I recently presented an introduction to installing VTK on Mac OS X. You will need a working copy of the VTK libraries on your system before you can follow this tutorial, so if you missed it the first time, I suggest you go back and follow the steps to installing VTK.

Setting up an Xcode project

Before we actually create an Xcode project, it is best to add the path to the VTK libraries and headers to the Xcode source tree preferences. Any directories declared there can be accessed throughout the rest of Xcode, including in the build settings, via a macro variable.

  1. Start up Xcode.
  2. Choose Xcode > Preferences, and click on the Source Trees tab.
  3. Click the + button under the table to add a new path. Enter ‘vtk-include’ for Setting Name, and for Display Name. In the Path, enter the full path to the directory containing the VTK header files. On my system, this is /Users/cormack/Develop/VTKBin/include/vtk-5.0
  4. Repeat this for a setting called ‘vtk-lib’, setting the path to the lib directory of your VTK installation. On my system, this is /Users/cormack/Develop/VTKBin/lib.

To create an Xcode project, start up Xcode, and choose File > New Project…. Then

  1. In the New Project panel, choose Cocoa Document-based Application in the Application group, and click on the Next button.
  2. Fill in the name ‘Animoltion’ for the project, and choose a directory for the project.
  3. Finally click the Finish button.

Now we need to add the VTK libraries to the project.

  1. Select the Animoltion project in the Groups & Files table on the left.
  2. Choose File > New Group.
  3. Enter the name ‘Libraries’ for the group.
  4. With the Libraries group selected, choose Project > Add to Project…
  5. Navigate to your VTK libraries, and select them all. Click Add.
  6. When presented with the configuration sheet, choose ‘Relative to vtk-lib’ in the Reference Type popup button. Leave the rest of the sheet unchanged, and click the Add button.

We won’t include the VTK header files directly in the project; instead we will simply add the VTK include directory to the header search path. To do that

  1. Double click the Animoltion project icon in the Groups & Files table to open the Project Info panel.
  2. Click the Build tab.
  3. Choose All Configurations from the Configuration popup button.
  4. Find the setting Header Search Paths in the table, and enter $(vtk-include) as the value. Xcode should take your variable and expand it into a path when you press enter if all goes well.
  5. Close the info panel.

VTK on Mac OS X is based on OpenGL, so we need to add the OpenGL framework to our new project.

  1. Open the Frameworks group in Groups & Files, and select Linked Frameworks.
  2. Choose Project > Add to Project…, and browse to /System/Library/Frameworks/OpenGL.framework.
  3. Select it, and click Add.
  4. In the panel, choose Default from the Reference Type popup button, and click the Add button.

Your Xcode project should now be ready to accept some source code.

Writing the BasicVTKView class

VTK already comes with a Cocoa view class — vtkCocoaGLView — that you can render your visualizations in, but we need to customize things a little, so a subclass is in order.

  1. Select the Classes group in Groups & Files, and choose File > New File…
  2. Choose Objective-C class from the Cocoa group in the New File panel, and click Next.
  3. Enter the file name BasicVTKView.mm, and click Finish.

You may be thinking that the .mm extension above is a typo, but actually it tells Xcode that the file in question is an Objective-C++ file. Objective-C++ is a hybrid of Objective-C and C++; you can basically mix the two to your heart’s content, with the restriction that you can’t combine the class inheritance trees of the two languages. A C++ object cannot subclass an Objective-C object, and vice versa.

Now enter the following code in the BasicVTKView.h file. (You will need to select the file in Groups & Files, and possibly drag the editor split view up to see the file content.)

#import <Cocoa/Cocoa.h>

#import "vtkCocoaGLView.h"

#define id Id
#import "vtkRenderer.h"
#undef id

@interface BasicVTKView : vtkCocoaGLView 
{
    vtkRenderer *renderer;
}

- (vtkRenderer *)getRenderer;
- (void)setRenderer:(vtkRenderer *)theRenderer;

@end

I should point out that most of the code I will be presenting is based on a recent rewrite of Animoltion by Marc Baaden. The BasicVTKView class is a subclass of vtkCocoaGLView which is in turn a subclass of the built-in Cocoa class NSOpenGLView. The class has a single instance variable, renderer, which is a C++ object with the class vtkRenderer. The methods declared are just accessors for the renderer.

You may have noticed a bit of witchcraft going on in the import block of BasicVTKView.h. id is defined to be Id, with a capital I, and after the VTK includes, is undefined again. The reason for this is that id is a reserved keyword in Objective-C, but not in C++. Unfortunately, in the VTK code, there are a few places where id is used as a variable, and this confuses the Objective-C compiler. So its a good idea to bracket all VTK imports with these #define/#undef directives in Objective-C++ files.

Now open BasicVTKView.mm, and add the following source code:

#import "BasicVTKView.h"

#define id Id
#import "vtkRenderer.h"
#import "vtkRenderWindow.h"
#import "vtkRenderWindowInteractor.h"
#import "vtkCocoaRenderWindowInteractor.h"
#import "vtkCocoaRenderWindow.h"
#undef id

@implementation BasicVTKView

-(id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        vtkRenderer *ren = vtkRenderer::New();
        vtkRenderWindow *renWin = vtkRenderWindow::New();
        vtkRenderWindowInteractor *renWinInt = vtkRenderWindowInteractor::New();

        renWin->SetWindowId([self window]);
        renWin->SetDisplayId(self);
        renWin->AddRenderer(ren);
        renWinInt->SetRenderWindow(renWin);

        [self setVTKRenderWindow:(vtkCocoaRenderWindow *)renWin];
        [self setRenderer:ren];

        if ( !renWinInt->GetInitialized() ) renWinInt->Initialize();
    }
    return self;
}

-(void)dealloc {
    vtkRenderer *ren = [self getRenderer];
    vtkRenderWindow *renWin = [self getVTKRenderWindow];
    vtkRenderWindowInteractor *renWinInt = [self getInteractor];

    if (ren) ren->Delete();
    if (renWin) renWin->Delete();
    if (renWinInt) renWinInt->Delete();

    [self setRenderer:NULL];
    [self setVTKRenderWindow:NULL];

    [super dealloc];
}

-(vtkRenderer*)getRenderer {
    return renderer;
}

-(void)setRenderer:(vtkRenderer*)theRenderer {
    renderer = theRenderer;
}


@end

The initializer method creates a vtkRenderer, a vtkRenderWindow, and a vtkRenderWindowInteractor.

-(id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        vtkRenderer *ren = vtkRenderer::New();
        vtkRenderWindow *renWin = vtkRenderWindow::New();
        vtkRenderWindowInteractor *renWinInt = vtkRenderWindowInteractor::New();

        renWin->SetWindowId([self window]);
        renWin->SetDisplayId(self);
        renWin->AddRenderer(ren);
        renWinInt->SetRenderWindow(renWin);

        [self setVTKRenderWindow:(vtkCocoaRenderWindow *)renWin];
        [self setRenderer:ren];

        if ( !renWinInt->GetInitialized() ) renWinInt->Initialize();
    }
    return self;
}

These VTK C++ classes are fairly self explanatory: The vtkRenderer is what renders the 3D scene in OpenGL; the vtkRenderWindow is simply the window in which the rendering takes place; and the vtkRenderWindowInteractor takes care of any interaction with the view, such as mouse clicks and keyboard presses.

VTK uses an Factory design pattern to create new objects. Instead of using the usual C++ new operator to create a new object, you call the class method New. The New method will select a subclass suitable for the environment that VTK is running in. For example, the call to vtkRenderWindow::New() will actually return an object of the class vtkCocoaRenderWindow in this case; vtkCocoaRenderWindow is a subclass of vtkRenderWindow for use with a Cocoa window. If you were to use VTK on Windows or with Carbon, you would get an object of a different concrete subclass. In this way, VTK abstracts the interface of a render window from its implementation, achieving cross-platform compatibility. This is nice object-oriented design.

The rest of the initWithFrame: method sets the relationships between the various VTK objects, the BasicVTKView object, and the Cocoa window containing the view. Finally, it checks if the vtkRenderWindowInteractor has been initialized already, and if not, initializes it, so that it can start receiving events.

The dealloc method breaks the whole stack of cards down again, as you would expect:

-(void)dealloc {
    vtkRenderer *ren = [self getRenderer];
    vtkRenderWindow *renWin = [self getVTKRenderWindow];
    vtkRenderWindowInteractor *renWinInt = [self getInteractor];

    if (ren) ren->Delete();
    if (renWin) renWin->Delete();
    if (renWinInt) renWinInt->Delete();

    [self setRenderer:NULL];
    [self setVTKRenderWindow:NULL];

    [super dealloc];
}

The Delete method may surprise you, because it is actually equivalent release in Cocoa. VTK uses reference counting to govern object lifetime, just like Cocoa, and Delete simply decrements the retain count of the object. When it reaches zero the object will get deleted.

Concluding

That’s it for this time. You should now be able to build the project to make sure that all has gone to plan. If you are too lazy to follow along, here is the source code for this week. Next time we will introduce VTK code to visualize moving atoms, and build the interface in Interface Builder.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

for clarity

The line "4. Find the Setting Header Search Paths in the table, ..."

could perhaps be changed to "Find the Setting: 'Header Search Paths' in the table, ..."

Other than wasting some time there, everything went smoothly. Another helpful tutorial.

VTK with Cocoa

I'm developing a fairly complex fluid dynamics simulation using Cocoa / VTK as the user interface / visualization component.

The earlier article from Drew was a big help in getting VTK working with Cocoa.

The project is nowhere near ready form prime time, but it anyone is interested, source code is at:

http://numerator.svn.sourceforge.net/viewvc/numerator/Slime/

And a universal binary prototype is avail at
http://numerator.sourceforge.net/p1.dmg

It has some neat features, such as all fluid components are processed in parallel using SSE / Altivec instructions, processing is on background thread so UI remains responsive.

Add what to Libraries group?

You say "Navigate to your VTK libraries, and select them all. Click Add.", but what does that mean? Navigate to the libraries folder we just told XCode about? Add all of those files? There seems to be a lot of cmake and other files in there. Do I use those too? I'm just not sure from the instructions.

Re: Libraries

You only need to add the lib*.a files, not all the other stuff.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Directly access doxygen from XCode?

I'm currently learning VTK and was wondering if there is a way to make XCode display directly the Doxygen Documentation of a class or a method? Like some Java IDEs (Eclipse, Netbeans, etc) directly display the Javadoc documentation.

Could not compile on 10.6

I downloaded the source and tried to compile it on snow leopard. I get the following error;

error: There is no SDK with the name or path '/Developer/SDKs/MacOSX10.4u.sdk'

and indeed there isn't. Anyone has an idea about what to do?

(I did 'add to project' etc according to the first the visualization article, and I did install vtk according to the article here on MR)

SDK

To change the SDK, just double click on the project icon in Xcode, and in the first tab, choose your SDK to be 10.5 or 10.6.

Drew

---------------------------
Drew McCormack
http://www.mentalfaculty.com
http://www.macanics.net
http://www.macresearch.org

Of course, what was I

Of course, what was I thinking?
Great tutorial by the way, thanks a lot!

/Carlis

What happens to the interactor after init?

Everything in the initializer makes sense except the fate of the interactor. The only thing we ever do with it is initialize it. It's local to the initWithFrame method, and it presumably is released after the method completes. Down in dealloc, we have a getInteractor, but we never set an interactor above. Am I missing something?

re: Interactor

I suspect the interactor is not needed in this tutorial, but will be used in the next tutorial.

Drew

---------------------------
Drew McCormack
http://www.mentalfaculty.com
http://www.macanics.net
http://www.macresearch.org

Problems building tutorial

Hi all (with a big shout out to Drew),

Great tutorial series so far. I'm really enjoying them.

I've hit a snag with this tutorial though, and was hoping for some guidance. After following the instructions given pretty much to the letter, and downloading the source code, this program won't build for me. VTK appeared to compile fine (also completed using the instructions on MR).

I'm on a Mac Book Pro runnning OSX 10.6 with XCode 3.2.

I'm getting a huge number (828) of errors when building this in XCode. Something within the gnu C++ library is throwing a tantrum. The most prominent error comes from the std_algobase.h file, where I receive the following message:

error: 'ostreambuf_iterator' was not declared in this scope

I've most likely messed up linking to a particular set of libraries or headers, but try as I might, I can't track down the problem. Anybody seen this before? Any help would be appreciated.

Thanks
Chris

Solution to problem building the tutorial

Go to the build options for the project and change the header search path to not be recursive, this solved the problem I had wit xcode giving me 828 errors when building.

- Sau

Thanks Sau!!! Your help is

Thanks Sau!!! Your help is very appreciated.

I'm still getting used to this whole "IDE" thing, having spent most of my programming life banging away with vi and the command line.

Chris

what do you mean of "When

what do you mean of "When presented with the configuration sheet, choose ‘Relative to vtk-lib’ in the Reference Type popup button. Leave the rest of the sheet unchanged, and click the Add button."
What is the "Reference Type "?? I cannot find this popup button in XCode