macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

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

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.