root/OpenSceneGraph/trunk/examples/osgviewerCocoa/ViewerCocoa.mm @ 6941

Revision 6941, 54.2 kB (checked in by robert, 8 years ago)

From Martin Lavery and Robert Osfield, Updated examples to use a variation of the MIT License

Line 
1/* OpenSceneGraph example, osgviewerCacoa.
2*
3*  Permission is hereby granted, free of charge, to any person obtaining a copy
4*  of this software and associated documentation files (the "Software"), to deal
5*  in the Software without restriction, including without limitation the rights
6*  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7*  copies of the Software, and to permit persons to whom the Software is
8*  furnished to do so, subject to the following conditions:
9*
10*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11*  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12*  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13*  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14*  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15*  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
16*  THE SOFTWARE.
17*/
18
19//
20//  ViewerCocoa.mm
21//  osgviewerCocoa
22//
23//  Created by Eric Wing on 11/12/06.
24//  Copyright 2006. Released under the OSGPL.
25//  Ported to osgViewer::Viewer by Martin Lavery 7/06/07
26//
27/* This class demonstrates how to subclass NSOpenGLView to integrate with the
28 * osgViewer::Viewer.
29 * This class demonstrates:
30 * Objective-C++
31 * How to subclass NSOpenGLView
32 * Specifying OpenGL pixel formats
33 * V-sync
34 * Timer based animation
35 * One button and multibutton mice.
36 * Scroll events (Mighty Mouse, Two-finger trackpads)
37 * Keyboard events
38 * Drag and drop (as target)
39 * Drag and drop (as source)
40 * Copy (for copy/paste)
41 * Resolution Independent UI (maybe, not Leopard tested/only Tiger Quartz Debug)
42 * Target-Action (for other widgets to invoke actions on this view)
43 * Shared OpenGLContexts
44 * Multithreaded OpenGL Engine (with CPU count check)
45 * Getting an image for minimization in the Dock
46 * Using osg::Camera's Framebuffer Objects to generate screen captures
47 * Code to detect drawing to offscreen (e.g. printer)
48 * Localization/Localizable strings (really need some different languages)
49 * Use of respondsToSelector and instancesRespondToSelector to demonstrate
50 *   runtime feature checking to provide access to newer features while still
51 *   supporting legacy versions.
52 *
53 * Things not demonstrated by this view or application example (but would be interesting):
54 * Cocoa Bindings (highly recommended)
55 * Core Data (works great with Cocoa Bindings)
56 * Custom Interface Builder palette with Inspector configurable options
57 * More PixelFormat options
58 * Fullscreen mode
59 * Low-level CGL access (kind of have with multithreaded OpenGL engine).
60 * Delegates for your view
61 * Multithreading
62 * Updating images of the window for the minimized view in the Dock
63 * Full Printing support
64 * Non-view stuff (Application Delegates, models, controllers)
65 * Launching by double-clicking a model or drag-and-drop onto Application Icon (non-view)
66 * Launching via commandline with parameters (non-view)
67 */ 
68
69/* Commentary:
70 * This class packs a lot of functionality that you might not expect from a "Viewer".
71 * The reason is that Mac users have high expectations of their applications, so things
72 * that might not even be considered on other platforms (like drag-and-drop), are almost
73 * a requirment on Mac OS X.
74 * The good news is that this class can almost be used as a template for other purposes.
75 * If you are looking for the bare minimum code needed, focus your attention on
76 * the init* routines and drawRect.
77 */
78
79/*
80 * Coding conventions:
81 * My coding style is slightly different than what you normally see in Cocoa.
82 * So here is the cheat sheet:
83 * I hate Hungarian (Microsoft) notation. And prefixed underscore _variables
84 * are technically reserved by the compiler which is why I avoid them. So...
85 * Member variables use camelCase (Pascal/Java)
86 * Local variables use under_scores (Ada)
87 * For methods, I follow standard Cocoa conventions.
88 * I tend to keep * with the type (e.g. NSView* foo) instead of with the variable (NSView *foo).
89 * (I tend to think of the pointer as part of the type.)
90 * For Obj-C named parameters, I tend to keep the namedParameter and the value
91 * together instead of separated by spaces
92 * e.g. [self initWithX:x_val yVal:y_val zVal:z_val].
93 * (When I was first learning Objective-C, this made it easier for me to
94 * figure out which things were paired.)
95 */
96#import "ViewerCocoa.h"
97
98#include <OpenGL/gl.h>
99#include <OpenGL/glu.h> // handy for gluErrorString
100
101#include <osgViewer/Viewer>
102#include <osgViewer/ViewerEventHandlers>
103#include <osgGA/TrackballManipulator>
104// Needed to explicitly typecast keys to the OSG type
105#include <osgGA/GUIEventAdapter>
106// Needed to load models
107#include <osgDB/ReadFile>
108
109// Used so I can change the background (clear) color
110
111#include <osg/Vec4>
112
113// For debugging
114//#include <osg/Notify>
115
116// osgText is used only to announce that you can use drag-and-drop to load models.
117// osgViewer itself does not have a dependency on osgText.
118#include <osg/ref_ptr>
119#include <osgText/Text>
120
121#include <osg/Geode>
122
123// Needed for Multithreaded OpenGL Engine (sysctlbyname for num CPUs)
124#include <sys/types.h>
125#include <sys/sysctl.h>
126#include <OpenGL/OpenGL.h> // for CoreOpenGL (CGL) for Multithreaded OpenGL Engine
127
128
129
130// This is optional. This allows memory for things like textures and displaylists to be shared among different contexts.
131#define VIEWER_USE_SHARED_CONTEXTS
132#ifdef VIEWER_USE_SHARED_CONTEXTS
133static NSOpenGLContext* s_sharedOpenGLContext = NULL;
134#endif // VIEWER_USE_SHARED_CONTEXTS
135
136// Taken/Adapted from one of the Apple OpenGL developer examples
137static void Internal_SetAlpha(NSBitmapImageRep *imageRep, unsigned char alpha_value)
138{
139        register unsigned char * sp = [imageRep bitmapData];
140        register int bytesPerRow = [imageRep bytesPerRow];
141        register int height = [imageRep pixelsHigh];
142        register int width = [imageRep pixelsWide];
143
144        for(int i=0; i<height; i++)
145        {
146                register unsigned int * the_pixel = (unsigned int *) sp;
147                register int w = width;
148                while (w-- > 0)
149                {
150                        unsigned char* sp_char = (unsigned char *) the_pixel;
151//                      register unsigned char * the_red = sp_char;
152//                      register unsigned char * the_green = (sp_char+1);
153//                      register unsigned char * the_blue = (sp_char+2);
154                        register unsigned char * the_alpha = (sp_char+3);
155       
156                        *the_alpha = alpha_value;
157                        *the_pixel++;
158                }
159                sp += bytesPerRow;
160        }
161}
162
163
164@implementation ViewerCocoa
165
166// My simple pixel format definition
167+ (NSOpenGLPixelFormat*) basicPixelFormat
168{
169        NSOpenGLPixelFormatAttribute pixel_attributes[] =
170        {
171                NSOpenGLPFAWindow,
172                NSOpenGLPFADoubleBuffer,  // double buffered
173                NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)32, // depth buffer size in bits
174//              NSOpenGLPFAColorSize, (NSOpenGLPixelFormatAttribute)24, // Not sure if this helps
175//              NSOpenGLPFAAlphaSize, (NSOpenGLPixelFormatAttribute)8, // Not sure if this helps
176                (NSOpenGLPixelFormatAttribute)nil
177    };
178    return [[[NSOpenGLPixelFormat alloc] initWithAttributes:pixel_attributes] autorelease];
179}
180
181
182
183////////////////////////////////////////////////////////////////////////
184/////////////////////////// Init Stuff /////////////////////////////////
185////////////////////////////////////////////////////////////////////////
186
187/* This is the designated initializer for an NSOpenGLView. However, since I'm
188 * using Interface Builder to help, initWithCoder: is the initializer that gets called.
189 * But for completeness, I implement this method here.
190 */
191- (id) initWithFrame:(NSRect)frame_rect pixelFormat:(NSOpenGLPixelFormat*)pixel_format
192{
193        self = [super initWithFrame:frame_rect pixelFormat:pixel_format];
194        if(self)
195        {
196                [self commonInit];
197        }
198        return self;
199}
200
201/* Going through the IB palette, this initializer is calling instead of the designated initializer
202 * initWithFrame:pixelFormat:
203 * But for some reason, the pixel format set in IB selected seems to be either ignored or is missing
204 * a value I need. (The depth buffer looks too shallow to me and glErrors are triggered.)
205 * So I explicitly set the pixel format inside here (overriding the IB palette options).
206 * This probably should be investigated, but since IB is getting an overhaul for Leopard,
207 * I'll wait on this for now.
208 */
209- (id) initWithCoder:(NSCoder*)the_coder
210{
211        self = [super initWithCoder:the_coder];
212        if(self)
213        {
214                NSOpenGLPixelFormat* pixel_format = [ViewerCocoa basicPixelFormat];
215                [self setPixelFormat:pixel_format];
216                [self commonInit];
217        }
218        return self;
219}
220
221/* Some generic code expecting regular NSView's may call this initializer instead of the specialized NSOpenGLView designated initializer.
222 * I override this method here to make sure it does the right thing.
223 */
224- (id) initWithFrame:(NSRect)frame_rect
225{
226        self = [super initWithFrame:frame_rect pixelFormat:[ViewerCocoa basicPixelFormat]];
227        if(self)
228        {
229                [self commonInit];
230        }
231        return self;
232}
233
234
235// My custom methods to centralize common init stuff
236- (void) commonInit
237{
238        isUsingCtrlClick = NO;
239        isUsingOptionClick = NO;
240        isUsingMultithreadedOpenGLEngine = NO;
241
242        [self initSharedOpenGLContext];
243
244        [self initOSGViewer];
245        [self initAnimationTimer];
246       
247        // Register for Drag and Drop
248        [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]];
249       
250    // Add minification observer so we can set the Dock picture since OpenGL views don't do this automatically for us.
251        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(prepareForMiniaturization:) name:NSWindowWillMiniaturizeNotification object:nil];
252
253}
254
255/* Optional: This will setup shared OpenGL contexts so resources like textures, etc, can be shared/reused
256 * by multiple instances of SimpleViwerCocoa views.
257 */
258- (void) initSharedOpenGLContext
259{
260#ifdef VIEWER_USE_SHARED_CONTEXTS
261
262        NSOpenGLContext* this_views_opengl_context = nil;
263       
264        // create a context the first time through
265        if(s_sharedOpenGLContext == NULL)
266        {
267                s_sharedOpenGLContext = [[NSOpenGLContext alloc]
268                      initWithFormat:[ViewerCocoa basicPixelFormat]
269                                                shareContext:nil];
270               
271        }
272       
273        this_views_opengl_context = [[NSOpenGLContext alloc]
274                      initWithFormat:[ViewerCocoa basicPixelFormat]
275                                                shareContext:s_sharedOpenGLContext];
276        [self setOpenGLContext:this_views_opengl_context];
277//      [this_views_opengl_context makeCurrentContext];
278#endif // VIEWER_USE_SHARED_CONTEXTS
279}
280
281// Allocate a Viewer and do basic initialization. No assumption about having an
282// a valid OpenGL context is made by this function.
283- (void) initOSGViewer
284{
285
286
287//      osg::setNotifyLevel( osg::DEBUG_FP );
288        theViewer = new osgViewer::Viewer;
289        graphicsWindow = theViewer->setUpViewerAsEmbeddedInWindow(0,0,740,650); // numbers from Nib
290        // Builts in Stats handler
291        theViewer->addEventHandler(new osgViewer::StatsHandler);
292#ifdef VIEWER_USE_SHARED_CONTEXTS
293        // Workaround: osgViewer::Viewer automatically increments its context ID values.
294        // Since we're using a shared context, we want all Viewer's to use the same context ID.
295        // There is no API to avoid this behavior, so we need to undo what Viewer's constructor did.
296    graphicsWindow->getState()->setContextID(0);
297        osg::DisplaySettings::instance()->setMaxNumberOfGraphicsContexts(1);
298#endif // VIEWER_USE_SHARED_CONTEXTS
299
300        // Cocoa follows the same coordinate convention as OpenGL. osgViewer's default is inverted.
301        theViewer->getEventQueue()->getCurrentEventState()->setMouseYOrientation(osgGA::GUIEventAdapter::Y_INCREASING_UPWARDS);
302        // Use a trackball manipulator...matches nicely with the Mighty Mouse Scrollball.
303        theViewer->setCameraManipulator(new osgGA::TrackballManipulator);
304       
305}
306
307- (void) initAnimationTimer
308{
309        // Cocoa is event driven, so by default, there is nothing to trigger redraws for animation.
310        // The easiest way to animate is to set a repeating NSTimer which triggers a redraw.
311        SEL the_selector;
312        NSMethodSignature* a_signature;
313        NSInvocation* an_invocation;
314        // animationCallback is my animation callback method
315        the_selector = @selector( animationCallback );
316        a_signature = [ViewerCocoa instanceMethodSignatureForSelector:the_selector];
317        an_invocation = [NSInvocation invocationWithMethodSignature:a_signature] ;
318        [an_invocation setSelector:the_selector];
319        [an_invocation setTarget:self];
320       
321        animationTimer = [NSTimer
322                scheduledTimerWithTimeInterval:1.0/60.0 // fps
323                invocation:an_invocation
324                repeats:YES];
325        [animationTimer retain];
326       
327        // For single threaded apps like this one,
328        // Cocoa seems to block timers or events sometimes. This can be seen
329        // when I'm animating (via a timer) and you open an popup box or move a slider.
330        // Apparently, sheets and dialogs can also block (try printing).
331        // To work around this, Cocoa provides different run-loop modes. I need to
332        // specify the modes to avoid the blockage.
333        // NSDefaultRunLoopMode seems to be the default. I don't think I need to explicitly
334        // set this one, but just in case, I will set it anyway.
335        [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSDefaultRunLoopMode];
336        // This seems to be the one for preventing blocking on other events (popup box, slider, etc)
337        [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSEventTrackingRunLoopMode];
338        // This seems to be the one for dialogs.
339        [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSModalPanelRunLoopMode];
340}
341
342- (void) dealloc
343{
344        [animationTimer invalidate];
345        [animationTimer release];
346        delete theViewer;
347        theViewer = NULL;
348        [super dealloc];
349}
350
351- (void) finalize
352{
353        delete theViewer;
354        theViewer = NULL;
355        [super finalize];
356}
357
358/* NSOpenGLView defines this method to be called (only once) after the OpenGL
359 * context is created and made the current context. It is intended to be used to setup
360 * your initial OpenGL state. This seems like a good place to initialize the
361 * OSG stuff. This method exists in 10.3 and later. If you are running pre-10.3, you
362 * must manually call this method sometime after the OpenGL context is created and
363 * made current, or refactor this code.
364 */
365- (void) prepareOpenGL
366{
367        [super prepareOpenGL];
368       
369        // The NSOpenGLCPSwapInterval seems to be vsync. If 1, buffers are swapped with vertical refresh.
370        // If 0, flushBuffer will execute as soon as possible.
371        long swap_interval = 1 ;
372    [[self openGLContext] setValues:&swap_interval forParameter:NSOpenGLCPSwapInterval];
373
374
375        // Try new multithreaded OpenGL engine?
376        // See Technical Note TN2085 Enabling multi-threaded execution of the OpenGL framework
377        // http://developer.apple.com/technotes/tn2006/tn2085.html
378        // For this simple viewer, you are probably not going to see a speed boost, but possibly a speed hit,
379        // since we probably don't have much data to dispatch,
380        // but it is enabled anyway for demonstration purposes.
381        uint64_t num_cpus = 0;
382        size_t num_cpus_length = sizeof(num_cpus);
383        // Multithreaded engine only benefits with muliple CPUs, so do CPU count check
384        // I've been told that Apple has started doing this check behind the scenes in some version of Tiger.
385        if(sysctlbyname("hw.activecpu", &num_cpus, &num_cpus_length, NULL, 0) == 0)
386        {
387//              NSLog(@"Num CPUs=%d", num_cpus);
388                if(num_cpus >= 2)
389                {
390                        // Cleared to enable multi-threaded engine
391                        // kCGLCEMPEngine may not be defined before certain versions of Tiger/Xcode,
392                        // so use the numeric value 313 instead to keep things compiling.
393                        CGLError error_val = CGLEnable((CGLContextObj)[[self openGLContext] CGLContextObj], static_cast<CGLContextEnable>(313));
394                        if(error_val != 0)
395                        {
396                                // The likelihood of failure seems quite high on older hardware, at least for now.
397                                // NSLog(@"Failed to enable Multithreaded OpenGL Engine: %s", CGLErrorString(error_val));
398                                isUsingMultithreadedOpenGLEngine = NO;
399                        }
400                        else
401                        {
402                                // NSLog(@"Success! Multithreaded OpenGL Engine activated!");
403                                isUsingMultithreadedOpenGLEngine = YES;
404                        }
405                }
406                else
407                {
408                        isUsingMultithreadedOpenGLEngine = NO;
409                }
410        }
411
412        // This is also might be a good place to setup OpenGL state that OSG doesn't control.
413        glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
414        glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
415        glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
416        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
417
418/*
419        GLint maxbuffers[1];
420        glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, maxbuffers);
421        NSLog(@"GL_MAX_COLOR_ATTACHMENTS=%d", maxbuffers[0]);
422*/
423
424        // We need to tell the osgViewer what the viewport size is
425        [self resizeViewport];
426
427
428        // This is optional:
429        // This is to setup some default text in the OpenGL view so the
430        // user knows that they should drag and drop a model into the view.
431        osg::ref_ptr<osgText::Text> default_text = new osgText::Text;
432
433    default_text->setAlignment(osgText::Text::CENTER_CENTER);
434    default_text->setBackdropType(osgText::Text::OUTLINE);
435//      default_text->setBackdropImplementation(osgText::Text::POLYGON_OFFSET);
436        default_text->setColor(osg::Vec4(1.0, 1.0, 0.0, 1.0));
437        default_text->setBackdropColor(osg::Vec4(0.0, 0.0, 0.0, 1.0));
438    default_text->setAxisAlignment(osgText::Text::XZ_PLANE);
439       
440        // We should use a (Cocoa) localizable string instead of a hard coded string.
441//      default_text->setText("Drag-and-Drop\nyour .osg model here!");
442        // The first string is the key name (you need a Localizable.strings file for your Nib). The second string is just a comment.
443        NSString* localized_string = NSLocalizedString(@"DragAndDropHere", @"Drag-and-Drop\nyour .osg model here!");
444        default_text->setText([localized_string UTF8String]);
445       
446        osg::ref_ptr<osg::Geode> the_geode = new osg::Geode;
447        the_geode->addDrawable(default_text.get());
448
449        theViewer->setSceneData(the_geode.get());
450}
451
452/* disableScreenUpdatesUntilFlush was introduced in Tiger. It will prevent
453 * unnecessary screen flashing caused by NSSplitViews or NSScrollviews.
454 * From Apple's release notes:
455 
456NSWindow -disableScreenUpdatesUntilFlush API (Section added since WWDC)
457
458When a view that renders to a hardware surface (such as an OpenGL view) is placed in an NSScrollView or NSSplitView, there can be a noticeable flicker or lag when the scroll position or splitter position is moved. This happens because each move of the hardware surface takes effect immediately, before the remaining window content has had the chance to draw and flush.
459
460To enable applications to eliminate this visual artifact, Tiger AppKit provides a new NSWindow message, -disableScreenUpdatesUntilFlush. This message asks a window to suspend screen updates until the window's updated content is subsequently flushed to the screen. This message can be sent from a view that is about to move its hardware surface, to insure that the hardware surface move and window redisplay will be visually synchronized. The window responds by immediately disabling screen updates via a call to NSDisableScreenUpdates(), and setting a flag that will cause it to call NSEnableScreenUpdates() later, when the window flushes. It is permissible to send this message to a given window more than once during a given window update cycle; the window will only suspend and re-enable updates once during that cycle.
461
462A view class that renders to a hardware surface can send this message from an override of -renewGState (a method that is is invoked immediately before the view's surface is moved) to effectively defer compositing of the moved surface until the window has finished drawing and flushing its remaining content.
463A -respondsToSelector: check has been used to provide compatibility with previous system releases. On pre-Tiger systems, where NSWindow has no -disableScreenUpdatesUntilFlush method, the -renewGState override will have no effect.
464 */
465- (void) renewGState
466{
467    NSWindow* the_window = [self window];
468    if([the_window respondsToSelector:@selector(disableScreenUpdatesUntilFlush)])
469        {
470                [the_window disableScreenUpdatesUntilFlush];
471    }
472    [super renewGState];
473}
474
475
476/* When you minimize an app, you usually can see its shrunken contents
477 * in the dock. However, an OpenGL view by default only produces a blank
478 * white window. So we use this method to do an image capture of our view
479 * which will be used as its minimized picture.
480 * (A possible enhancement to consider is to update the picture over time.)
481 */
482- (void) prepareForMiniaturization:(NSNotification*)notification
483{
484        [[self openGLContext] makeCurrentContext];
485        NSBitmapImageRep* ns_image_rep = [self renderOpenGLSceneToFramebuffer];
486        if([self lockFocusIfCanDraw])
487        {
488                [ns_image_rep draw];
489                [self unlockFocus];
490                [[self window] flushWindow];
491        }
492}
493
494/* Allow people to easily query if the multithreaded OpenGL engine is activated.
495 */
496- (BOOL) isUsingMultithreadedOpenGLEngine
497{
498        return isUsingMultithreadedOpenGLEngine;
499}
500
501
502////////////////////////////////////////////////////////////////////////
503/////////////////////////// End Init Stuff /////////////////////////////
504////////////////////////////////////////////////////////////////////////
505
506
507
508////////////////////////////////////////////////////////////////////////
509/////////////////////////// Mouse Stuff ////////////////////////////////
510////////////////////////////////////////////////////////////////////////
511
512- (void) mouseDown:(NSEvent*)the_event
513{
514        // Because many Mac users have only a 1-button mouse, we should provide ways
515        // to access the button 2 and 3 actions of osgViewer.
516        // I will use the Ctrl modifer to represent right-clicking
517        // and Option modifier to represent middle clicking.
518        if([the_event modifierFlags] & NSControlKeyMask)
519        {
520                [self setIsUsingCtrlClick:YES];
521                [self doRightMouseButtonDown:the_event];
522        }
523        else if([the_event modifierFlags] & NSAlternateKeyMask)
524        {
525                [self setIsUsingOptionClick:YES];
526                [self doMiddleMouseButtonDown:the_event];
527        }
528        else if([the_event modifierFlags] & NSCommandKeyMask)
529        {
530                [self startDragAndDropAsSource:the_event];
531        }
532        else
533        {
534                [self doLeftMouseButtonDown:the_event];
535        }
536}
537
538- (void) mouseDragged:(NSEvent*)the_event
539{
540        // We must convert the mouse event locations from the window coordinate system to the
541        // local view coordinate system.
542        NSPoint the_point = [the_event locationInWindow];
543    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
544       
545        theViewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y);
546        [self setNeedsDisplay:YES];
547}
548
549- (void) mouseUp:(NSEvent*)the_event
550{
551        // Because many Mac users have only a 1-button mouse, we should provide ways
552        // to access the button 2 and 3 actions of osgViewer.
553        // I will use the Ctrl modifer to represent right-clicking
554        // and Option modifier to represent middle clicking.
555        if([self isUsingCtrlClick] == YES)
556        {
557                [self setIsUsingCtrlClick:NO];
558                [self doRightMouseButtonUp:the_event];
559        }
560        else if([self isUsingOptionClick] == YES)
561        {
562                [self setIsUsingOptionClick:NO];
563                [self doMiddleMouseButtonUp:the_event];
564        }
565        else
566        {
567                [self doLeftMouseButtonUp:the_event];
568        }
569}
570
571- (void) rightMouseDown:(NSEvent*)the_event
572{
573        [self doRightMouseButtonDown:the_event];
574}
575
576- (void) rightMouseDragged:(NSEvent*)the_event
577{
578        // We must convert the mouse event locations from the window coordinate system to the
579        // local view coordinate system.
580        NSPoint the_point = [the_event locationInWindow];
581    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
582       
583        theViewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y);
584        [self setNeedsDisplay:YES];
585}
586
587- (void) rightMouseUp:(NSEvent*)the_event
588{
589        [self doRightMouseButtonUp:the_event];
590}
591
592// "otherMouse" seems to capture middle button and any other buttons beyond (4th, etc).
593- (void) otherMouseDown:(NSEvent*)the_event
594{
595        // Button 0 is left
596        // Button 1 is right
597        // Button 2 is middle
598        // Button 3 keeps going
599        // osgViewer expects 1 for left, 3 for right, 2 for middle
600        // osgViewer has a reversed number mapping for right and middle compared to Cocoa
601        if([the_event buttonNumber] == 2)
602        {
603                [self doMiddleMouseButtonDown:the_event];
604        }
605        else // buttonNumber should be 3,4,5,etc; must map to 4,5,6,etc in osgViewer
606        {
607                [self doExtraMouseButtonDown:the_event buttonNumber:[the_event buttonNumber]];
608        }
609}
610
611- (void) otherMouseDragged:(NSEvent*)the_event
612{
613        // We must convert the mouse event locations from the window coordinate system to the
614        // local view coordinate system.
615        NSPoint the_point = [the_event locationInWindow];
616    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
617       
618        theViewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y);
619        [self setNeedsDisplay:YES];
620}
621
622// "otherMouse" seems to capture middle button and any other buttons beyond (4th, etc).
623- (void) otherMouseUp:(NSEvent*)the_event
624{
625        // Button 0 is left
626        // Button 1 is right
627        // Button 2 is middle
628        // Button 3 keeps going
629        // osgViewer expects 1 for left, 3 for right, 2 for middle
630        // osgViewer has a reversed number mapping for right and middle compared to Cocoa
631        if([the_event buttonNumber] == 2)
632        {
633                [self doMiddleMouseButtonUp:the_event];
634        }
635        else // buttonNumber should be 3,4,5,etc; must map to 4,5,6,etc in osgViewer
636        {
637                // I don't think osgViewer does anything for these additional buttons,
638                // but just in case, pass them along. But as a Cocoa programmer, you might
639                // think about things you can do natively here instead of passing the buck.
640        }       [self doExtraMouseButtonUp:the_event buttonNumber:[the_event buttonNumber]];
641}
642
643- (void) setIsUsingCtrlClick:(BOOL)is_using_ctrl_click
644{
645        isUsingCtrlClick = is_using_ctrl_click;
646}
647
648- (BOOL) isUsingCtrlClick
649{
650        return isUsingCtrlClick;
651}
652
653- (void) setIsUsingOptionClick:(BOOL)is_using_option_click
654{
655        isUsingOptionClick = is_using_option_click;
656}
657
658- (BOOL) isUsingOptionClick
659{
660        return isUsingOptionClick;
661}
662
663
664- (void) doLeftMouseButtonDown:(NSEvent*)the_event
665{
666        // We must convert the mouse event locations from the window coordinate system to the
667        // local view coordinate system.
668        NSPoint the_point = [the_event locationInWindow];
669    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
670        if([the_event clickCount] == 1)
671        {
672                theViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 1);
673        }
674        else
675        {
676                theViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 1);
677        }
678        [self setNeedsDisplay:YES];
679}
680
681- (void) doLeftMouseButtonUp:(NSEvent*)the_event
682{
683        // We must convert the mouse event locations from the window coordinate system to the
684        // local view coordinate system.
685        NSPoint the_point = [the_event locationInWindow];
686    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
687       
688        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 1);
689        [self setNeedsDisplay:YES];
690}
691
692- (void) doRightMouseButtonDown:(NSEvent*)the_event
693{
694        // We must convert the mouse event locations from the window coordinate system to the
695        // local view coordinate system.
696        NSPoint the_point = [the_event locationInWindow];
697    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
698        if([the_event clickCount] == 1)
699        {
700                theViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 3);
701        }
702        else
703        {
704                theViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 3);
705        }
706        [self setNeedsDisplay:YES];
707}
708
709
710- (void) doRightMouseButtonUp:(NSEvent*)the_event
711{
712        // We must convert the mouse event locations from the window coordinate system to the
713        // local view coordinate system.
714        NSPoint the_point = [the_event locationInWindow];
715    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
716       
717        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 3);
718        [self setNeedsDisplay:YES];
719}
720
721- (void) doMiddleMouseButtonDown:(NSEvent*)the_event
722{
723        // We must convert the mouse event locations from the window coordinate system to the
724        // local view coordinate system.
725        NSPoint the_point = [the_event locationInWindow];
726    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
727       
728        if([the_event clickCount] == 1)
729        {
730                theViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 2);
731        }
732        else
733        {
734                theViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 2);
735        }
736        [self setNeedsDisplay:YES];
737}
738
739- (void) doExtraMouseButtonDown:(NSEvent*)the_event buttonNumber:(int)button_number
740{
741        // We must convert the mouse event locations from the window coordinate system to the
742        // local view coordinate system.
743        NSPoint the_point = [the_event locationInWindow];
744    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
745
746        if([the_event clickCount] == 1)
747        {
748                theViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, button_number+1);
749        }
750        else
751        {
752                theViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, button_number+1);
753        }
754        [self setNeedsDisplay:YES];
755}
756
757
758- (void) doMiddleMouseButtonUp:(NSEvent*)the_event
759{
760        // We must convert the mouse event locations from the window coordinate system to the
761        // local view coordinate system.        NSPoint the_point = [the_event locationInWindow];
762        NSPoint the_point = [the_event locationInWindow];
763        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
764        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 2);
765        [self setNeedsDisplay:YES];
766}
767
768- (void) doExtraMouseButtonUp:(NSEvent*)the_event buttonNumber:(int)button_number
769{
770        // We must convert the mouse event locations from the window coordinate system to the
771        // local view coordinate system.        NSPoint the_point = [the_event locationInWindow];
772        NSPoint the_point = [the_event locationInWindow];
773        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
774        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, button_number+1);
775       
776        [self setNeedsDisplay:YES];
777}
778
779// This is a job for Mighty Mouse!
780// For the most fluid experience turn on 360 degree mode availble in 10.4.8+.
781// With your Mighty Mouse plugged in,
782// open 'Keyboard & Mouse' in 'System Preferences'.
783// Select the 'Mouse' tab.
784// Under 'Scrolling Options' select '360 degree'.
785// That should improve diagonal scrolling.
786// You should also be able to use 'two-finger scrolling' on newer laptops.
787- (void) scrollWheel:(NSEvent*)the_event
788{
789        // Unfortunately, it turns out mouseScroll2D doesn't actually do anything.
790        // The camera manipulators don't seem to implement any code that utilize the scroll values.
791        // This this call does nothing.
792//      theViewer->getEventQueue()->mouseScroll2D([the_event deltaX], [the_event deltaY]);
793
794        // With the absense of a useful mouseScroll2D API, we can manually simulate the desired effect.
795        NSPoint the_point = [the_event locationInWindow];
796        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
797        theViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 1);
798        theViewer->getEventQueue()->mouseMotion(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY]);
799        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY], 1);
800
801        [self setNeedsDisplay:YES];
802}
803
804////////////////////////////////////////////////////////////////////////
805/////////////////////////// End Mouse Stuff ////////////////////////////
806////////////////////////////////////////////////////////////////////////
807
808////////////////////////////////////////////////////////////////////////
809/////////////////////////// Keyboard Stuff /////////////////////////////
810////////////////////////////////////////////////////////////////////////
811// Needed to accept keyboard events
812- (BOOL) acceptsFirstResponder
813{
814        return YES;
815}
816
817- (void) keyDown:(NSEvent*)the_event
818{
819        // Do you want characters or charactersIgnoringModifiers?
820        NSString* event_characters = [the_event characters];
821//      NSString* event_characters = [the_event charactersIgnoringModifiers];
822
823        unichar unicode_character = [event_characters characterAtIndex:0];
824//      NSLog(@"unicode_character: %d", unicode_character);
825        theViewer->getEventQueue()->keyPress(static_cast<osgGA::GUIEventAdapter::KeySymbol>(unicode_character));
826
827        [self setNeedsDisplay:YES];
828}
829
830- (void) keyUp:(NSEvent*)the_event
831{
832        // Do you want characters or charactersIgnoringModifiers?
833        NSString* event_characters = [the_event characters];
834//      NSString* event_characters = [the_event charactersIgnoringModifiers];
835        unichar unicode_character = [event_characters characterAtIndex:0];
836        theViewer->getEventQueue()->keyRelease(static_cast<osgGA::GUIEventAdapter::KeySymbol>(unicode_character));
837        [self setNeedsDisplay:YES];
838}
839
840////////////////////////////////////////////////////////////////////////
841/////////////////////////// End Keyboard Stuff /////////////////////////
842////////////////////////////////////////////////////////////////////////
843
844////////////////////////////////////////////////////////////////////////
845/////////////////////////// View and Draw Stuff ////////////////////////
846////////////////////////////////////////////////////////////////////////
847
848// This method is periodically called by my timer.
849- (void) animationCallback
850{
851        // Simply notify Cocoa that a drawRect needs to take place.
852        // Potential optimization is to query the OSG stuff to find out if a redraw is actually necessary.
853        [self setNeedsDisplay:YES];
854}
855
856// This is an optional optimization. This states you don't have a transparent view/window.
857// Obviously don't use this or set it to NO if you intend for your view to be see-through.
858- (BOOL) isOpaque
859{
860        return YES;
861}
862
863// Resolution Independent UI is coming... (Tiger's Quartz Debug already has the tool.)
864// We must think in 'point sizes', not pixel sizes, so a conversion is needed for OpenGL.
865- (void) resizeViewport
866{
867        NSSize size_in_points = [self bounds].size;
868        // This coordinate system conversion seems to make things work with Quartz Debug.
869        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
870        theViewer->getEventQueue()->windowResize(0, 0, size_in_window_coordinates.width, size_in_window_coordinates.height);
871        graphicsWindow->resized(0, 0, size_in_window_coordinates.width, size_in_window_coordinates.height);
872}
873
874// For window resize
875- (void) reshape
876{
877        [super reshape];
878        [self resizeViewport];
879}
880
881// This is the code that actually draws.
882// Remember you shouldn't call drawRect: directly and should use setNeedsDisplay:YES
883// This is so the operating system can optimize when a draw is actually needed.
884// (e.g. No sense drawing when the application is hidden.)
885- (void) drawRect:(NSRect)the_rect
886{
887        if([[NSGraphicsContext currentContext] isDrawingToScreen])
888        {
889                [[self openGLContext] makeCurrentContext];
890                theViewer->frame();
891                [[self openGLContext] flushBuffer];
892        }
893        else // This is usually the print case
894        {
895                [[self openGLContext] makeCurrentContext];
896
897                // FIXME: We should be computing a size that fits best to the paper target
898                NSSize size_in_points = [self bounds].size;
899                NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
900                NSBitmapImageRep * bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
901               
902                NSImage* ns_image = [self imageFromBitmapImageRep:bitmap_image_rep];
903
904                if(ns_image)
905                {
906                        NSSize image_size = [ns_image size];
907                        [ns_image drawAtPoint:NSMakePoint(0.0, 0.0)
908                                        fromRect: NSMakeRect(0.0, 0.0, image_size.width, image_size.height)
909//                                 operation: NSCompositeSourceOver
910                                   operation: NSCompositeCopy
911                                        fraction: 1.0];         
912                }
913                else
914                {
915                        NSLog(@"Image not valid");
916                }
917        }
918}
919
920
921/* Optional render to framebuffer stuff below. The code renders offscreen to assist in screen capture stuff.
922 * This can be useful for things like the Dock minimization picture, drag-and-drop dropImage, copy and paste,
923 * and printing.
924 */
925
926// Convenience version. Will use the current view's bounds and produce an RGB image with the current clear color.
927- (NSBitmapImageRep*) renderOpenGLSceneToFramebuffer
928{
929        NSSize size_in_points = [self bounds].size;
930        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
931        const osg::Vec4& clear_color = theViewer->getCamera()->getClearColor();
932
933        return [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height clearColorRed:clear_color[0] clearColorGreen:clear_color[1] clearColorBlue:clear_color[2] clearColorAlpha:clear_color[3]];
934}
935
936// Convenience version. Allows you to specify the view and height and format, but uses the current the current clear color.
937- (NSBitmapImageRep*) renderOpenGLSceneToFramebufferAsFormat:(int)gl_format viewWidth:(float)view_width viewHeight:(float)view_height
938{
939        const osg::Vec4& clear_color = theViewer->getCamera()->getClearColor();
940
941        return [self renderOpenGLSceneToFramebufferAsFormat:gl_format viewWidth:view_width viewHeight:view_height clearColorRed:clear_color[0] clearColorGreen:clear_color[1] clearColorBlue:clear_color[2] clearColorAlpha:clear_color[3]];
942}
943
944// Renders to an offscreen buffer and returns a copy of the data to an NSBitmapImageRep.
945// Allows you to specify the gl_format, width and height, and the glClearColor
946// gl_format is only GL_RGB or GLRGBA.
947- (NSBitmapImageRep*) renderOpenGLSceneToFramebufferAsFormat:(int)gl_format viewWidth:(float)view_width viewHeight:(float)view_height clearColorRed:(float)clear_red clearColorGreen:(float)clear_green clearColorBlue:(float)clear_blue clearColorAlpha:(float)clear_alpha
948{
949        // Round values and bring to closest integer.
950        int viewport_width = (int)(view_width + 0.5f);
951        int viewport_height = (int)(view_height + 0.5f);
952       
953        NSBitmapImageRep* ns_image_rep;
954        osg::ref_ptr<osg::Image> osg_image = new osg::Image;
955       
956        if(GL_RGBA == gl_format)
957        {
958                // Introduced in 10.4, but gives much better looking results if you utilize transparency
959                if([NSBitmapImageRep instancesRespondToSelector:@selector(initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:)])
960                {
961                        ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
962                                                                                                  pixelsWide:viewport_width
963                                                                                                  pixelsHigh:viewport_height
964                                                                                           bitsPerSample:8
965                                                                                         samplesPerPixel:4
966                                                                                                        hasAlpha:YES
967                                                                                                        isPlanar:NO
968                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
969                                                                                                bitmapFormat:NSAlphaNonpremultipliedBitmapFormat // 10.4+, gives much better looking results if you utilize transparency
970                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGBA, GL_UNSIGNED_BYTE, 1)
971                                                                                                bitsPerPixel:32]
972                                        autorelease];
973                }
974                else // fallback for 10.0 to 10.3
975                {
976                        ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
977                                                                                                  pixelsWide:viewport_width
978                                                                                                  pixelsHigh:viewport_height
979                                                                                           bitsPerSample:8
980                                                                                         samplesPerPixel:4
981                                                                                                        hasAlpha:YES
982                                                                                                        isPlanar:NO
983                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
984                                                                                                // bitmapFormat:NSAlphaNonpremultipliedBitmapFormat // 10.4+, gives much better looking results if you utilize transparency
985                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGBA, GL_UNSIGNED_BYTE, 1)
986                                                                                                bitsPerPixel:32]
987                                        autorelease];
988                }
989                // This is an optimization. Instead of creating data in both an osg::Image and NSBitmapImageRep,
990                // Allocate just the memory in the NSBitmapImageRep and give the osg::Image a reference to the data.
991                // I let NSBitmapImageRep control the memory because I think it will be easier to deal with
992                // memory management in the cases where it must interact with other Cocoa mechanisms like Drag-and-drop
993                // where the memory persistence is less obvious. Make sure that you don't expect to use the osg::Image
994                // outside the scope of this function because there is no telling when the data will be removed out
995                // from under it by Cocoa since osg::Image will not retain.
996                osg_image->setImage([ns_image_rep pixelsWide], [ns_image_rep pixelsHigh], 1,  GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, [ns_image_rep bitmapData], osg::Image::NO_DELETE, 1);
997        }
998        else if(GL_RGB == gl_format)
999        {
1000                ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1001                                                                                                  pixelsWide:viewport_width
1002                                                                                                  pixelsHigh:viewport_height
1003                                                                                           bitsPerSample:8
1004                                                                                         samplesPerPixel:3
1005                                                                                                        hasAlpha:NO
1006                                                                                                        isPlanar:NO
1007                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
1008                                                                                                // bitmapFormat:(NSBitmapFormat)0 // 10.4+
1009                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGB, GL_UNSIGNED_BYTE, 1)
1010                                                                                                bitsPerPixel:24]
1011                                        autorelease];
1012
1013                // This is an optimization. Instead of creating data in both an osg::Image and NSBitmapImageRep,
1014                // Allocate just the memory in the NSBitmapImageRep and give the osg::Image a reference to the data.
1015                // I let NSBitmapImageRep control the memory because I think it will be easier to deal with
1016                // memory management in the cases where it must interact with other Cocoa mechanisms like Drag-and-drop
1017                // where the memory persistence is less obvious. Make sure that you don't expect to use the osg::Image
1018                // outside the scope of this function because there is no telling when the data will be removed out
1019                // from under it by Cocoa since osg::Image will not retain.
1020                osg_image->setImage([ns_image_rep pixelsWide], [ns_image_rep pixelsHigh], 1,  GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, [ns_image_rep bitmapData], osg::Image::NO_DELETE, 1);
1021        }
1022        else
1023        {
1024                NSLog(@"Sorry, unsupported format in renderOpenGLSceneToFramebufferAsFormat");
1025                return nil;
1026        }
1027
1028        // Can't find a way to query Viewer for the current values, so recompute current view size.
1029        NSSize original_size_in_points = [self bounds].size;
1030        NSSize original_size_in_window_coordinates = [self convertSize:original_size_in_points toView:nil];
1031//      theViewer->getEventQueue()->windowResize(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1032
1033        theViewer->getEventQueue()->windowResize(0, 0, viewport_width, viewport_height);
1034        graphicsWindow->resized(0, 0, viewport_width, viewport_height);
1035
1036        /*
1037         * I want to use a Framebuffer Object because it seems to be the OpenGL sanctioned way of rendering offscreen.
1038         * Also, I want to try to decouple the image capture from the onscreen rendering. This is potentially useful
1039         * for two reasons:
1040         * 1) You may want to customize the image dimensions to best fit the situation (consider printing to a page to fit)
1041         * 2) You may want to customize the scene for the target (consider special output for a printer, or removed data for a thumbnail)
1042         * Unfortunately, I have hit two problems.
1043         * 1) osg::Camera (which seems to be the way to access Framebuffer Objects in OSG) doesn't seem to capture if it is the root node.
1044         * The workaround is to copy the camera attributes into another camera, and then add a second camera node into the scene.
1045         * I'm hoping OSG will simplify this in the future.
1046         * 2) I may have encountered a bug. Under some circumstances, the offscreen renderbuffer seems to get drawn into the onscreen view
1047         * when using a DragImage for drag-and-drop. I reproduced a non-OSG example, but learned I missed two important FBO calls which trigger gl errors.
1048         * So I'm wondering if OSG made the same mistake.
1049         * But the problem doesn't seem critical. It just looks bad.
1050         */
1051        //NSLog(@"Before camera glGetError: %s", gluErrorString(glGetError()));
1052        osg::Camera* root_camera = theViewer->getCamera();
1053
1054        // I originally tried the clone() method and the copy construction, but it didn't work right,
1055        // so I manually copy the attributes.
1056        osg::Camera* the_camera = new osg::Camera;
1057
1058        the_camera->setClearMask(root_camera->getClearMask());
1059        the_camera->setProjectionMatrix(root_camera->getProjectionMatrix());
1060        the_camera->setViewMatrix(root_camera->getViewMatrix());
1061        the_camera->setViewport(root_camera->getViewport());
1062        the_camera->setClearColor(
1063                osg::Vec4(
1064                        clear_red,
1065                        clear_green,
1066                        clear_blue,
1067                        clear_alpha
1068                )
1069        );
1070
1071        // This must be ABSOLUTE_RF, and not a copy of the root camera because the transforms are additive.
1072        the_camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
1073
1074        // We need to insert the new (second) camera into the scene (below the root camera) and attach
1075        // the scene data to the new camera.
1076        osg::ref_ptr<osg::Node> root_node = theViewer->getSceneData();
1077
1078        the_camera->addChild(root_node.get());
1079        // Don't call (bypass) Viewer's setSceneData, but the underlying SceneView's setSceneData.
1080        // Otherwise, the camera position gets reset to the home position.
1081        theViewer->setSceneData(the_camera);
1082
1083        // set the camera to render before the main camera.
1084        the_camera->setRenderOrder(osg::Camera::PRE_RENDER);
1085
1086        // tell the camera to use OpenGL frame buffer object where supported.
1087        the_camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
1088
1089
1090        // attach the image so its copied on each frame.
1091        the_camera->attach(osg::Camera::COLOR_BUFFER, osg_image.get());
1092
1093
1094        //NSLog(@"Before frame(), glGetError: %s", gluErrorString(glGetError()));
1095
1096
1097        // Render the scene
1098        theViewer->frame();
1099
1100        // Not sure if I really need this (seems to work without it), and if so, not sure if I need flush or finish
1101        glFlush();
1102//      glFinish();
1103
1104        //NSLog(@"After flush(), glGetError: %s", gluErrorString(glGetError()));
1105
1106
1107
1108        // The image is upside-down to Cocoa, so invert it.
1109        osg_image.get()->flipVertical();
1110
1111        // Clean up everything I changed
1112//      the_camera->detach(osg::Camera::COLOR_BUFFER);
1113//      the_camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER);
1114        // Don't call (bypass) Viewer's setSceneData, but the underlying SceneView's setSceneData.
1115        // Otherwise, the camera position gets reset to the home position.
1116        theViewer->setSceneData(root_node.get());
1117        theViewer->getEventQueue()->windowResize(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1118        graphicsWindow->resized(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1119
1120
1121        // Ugh. Because of the bug I mentioned, I'm losing the picture in the display when I print.
1122        [self setNeedsDisplay:YES];
1123        //NSLog(@"at return, glGetError: %s", gluErrorString(glGetError()));
1124
1125        return ns_image_rep;
1126}
1127
1128// Convenience method
1129- (NSImage*)imageFromBitmapImageRep:(NSBitmapImageRep*)bitmap_image_rep
1130{
1131        if(nil == bitmap_image_rep)
1132        {
1133                return nil;
1134        }
1135        NSImage* image = [[[NSImage alloc] initWithSize:[bitmap_image_rep size]] autorelease];
1136        [image addRepresentation:bitmap_image_rep];
1137        // This doesn't seem to work as I want it to. The image only gets flipped when rendered in a regular view.
1138        // It doesn't flip for the printer view. I must actually invert the pixels.
1139//      [image setFlipped:YES];
1140        return image;
1141}
1142
1143
1144
1145
1146////////////////////////////////////////////////////////////////////////
1147/////////////////////////// End View and Draw Stuff ////////////////////
1148////////////////////////////////////////////////////////////////////////
1149
1150////////////////////////////////////////////////////////////////////////
1151/////////////////////////// For drag and drop //////////////////////////
1152////////////////////////////////////////////////////////////////////////
1153- (unsigned int) draggingEntered:(id <NSDraggingInfo>)the_sender
1154{
1155        if([the_sender draggingSource] != self)
1156        {
1157                NSPasteboard* paste_board = [the_sender draggingPasteboard];
1158                // I respond to filename types or URL types
1159                NSArray* supported_types = [NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil];
1160                // If any of the supported types are being dragged in, activate the copy operation
1161                NSString* first_type = [paste_board availableTypeFromArray:supported_types];
1162                if(first_type != nil)
1163                {
1164                        return NSDragOperationCopy;
1165                }
1166        }
1167        // Means we don't support this type
1168        return NSDragOperationNone;
1169}
1170
1171// We're not using this method, but here it is as an example.
1172- (void) draggingExited:(id <NSDraggingInfo>)the_sender
1173{
1174}
1175
1176- (BOOL) prepareForDragOperation:(id <NSDraggingInfo>)the_sender
1177{
1178        return YES;
1179}
1180
1181- (BOOL) performDragOperation:(id <NSDraggingInfo>)the_sender
1182{
1183        NSPasteboard* paste_board = [the_sender draggingPasteboard];
1184
1185 
1186    if([[paste_board types] containsObject:NSFilenamesPboardType])
1187        {
1188        NSArray* file_names = [paste_board propertyListForType:NSFilenamesPboardType];
1189//        int number_of_files = [file_names count];
1190                // Exercise for the reader: Try loading all files in the array
1191                NSString* single_file = [file_names objectAtIndex:0];
1192            osg::ref_ptr<osg::Node> loaded_model = osgDB::readNodeFile([single_file fileSystemRepresentation]);
1193                if(!loaded_model)
1194                {
1195                        NSLog(@"File: %@ failed to load", single_file);
1196                        return NO;
1197                }
1198                theViewer->setSceneData(loaded_model.get());
1199                return YES;
1200    }
1201        else if([[paste_board types] containsObject:NSURLPboardType])
1202        {
1203                NSURL* file_url = [NSURL URLFromPasteboard:paste_board];
1204                // See if the URL is valid file path
1205                if(![file_url isFileURL])
1206                {
1207                        NSLog(@"URL: %@ needs to be a file for readNodeFile()", file_url);
1208                        return NO;
1209                }
1210                NSString* file_path = [file_url path];
1211            osg::ref_ptr<osg::Node> loaded_model = osgDB::readNodeFile([file_path fileSystemRepresentation]);
1212                if(!loaded_model)
1213                {
1214                        NSLog(@"URL: %@ failed to load, %@", file_url, file_path);
1215                        return NO;
1216                }
1217                theViewer->setSceneData(loaded_model.get());
1218                return YES;
1219        }
1220    return NO;
1221}
1222
1223// This method isn't really needed (I could move setNeedsDisplay up), but is here as an example
1224- (void) concludeDragOperation:(id <NSDraggingInfo>)the_sender
1225{
1226        [self setNeedsDisplay:YES];
1227}
1228
1229////////////////////////////////////////////////////////////////////////
1230/////////////////////////// End of drag and drop (receiver) ////////////
1231////////////////////////////////////////////////////////////////////////
1232
1233
1234//////////////////////////////////////////////////////////////////////////////////////
1235/////////////////////////// For drag and drop and copy/paste (source) ////////////////
1236//////////////////////////////////////////////////////////////////////////////////////
1237- (IBAction) copy:(id)sender
1238{
1239    NSString* type = NSTIFFPboardType;
1240    NSData* image_data = [self contentsAsDataOfType:type];
1241       
1242    if(image_data)
1243        {
1244        NSPasteboard* general_pboard = [NSPasteboard generalPasteboard];
1245        [general_pboard declareTypes:[NSArray arrayWithObjects:type, nil] owner: nil];
1246        [general_pboard setData:image_data forType:type];
1247    }
1248}
1249
1250- (NSData*) dataWithTIFFOfContentView
1251{
1252        [[self openGLContext] makeCurrentContext];
1253        NSBitmapImageRep * image = [self renderOpenGLSceneToFramebuffer];
1254        NSData* data = nil;
1255
1256        if(image != nil)
1257        {
1258                data = [image TIFFRepresentation];
1259        }
1260        return data;
1261}
1262
1263/* Returns a data object containing the current contents of the receiving window */
1264- (NSData*) contentsAsDataOfType:(NSString *)pboardType
1265{
1266        NSData * data = nil;
1267        if ([pboardType isEqualToString: NSTIFFPboardType] == YES)
1268        {
1269                data = [self dataWithTIFFOfContentView];
1270        }
1271    return data;
1272}
1273
1274
1275- (void) startDragAndDropAsSource:(NSEvent*)the_event
1276{
1277        NSPasteboard* drag_paste_board;
1278        NSImage* the_image;
1279        NSSize the_size;
1280        NSPoint the_point;
1281
1282        NSSize size_in_points = [self bounds].size;
1283        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
1284
1285        // Create the image that will be dragged
1286        NSString * type = NSTIFFPboardType;
1287
1288        [[self openGLContext] makeCurrentContext];
1289
1290        // I want two images. One to be rendered for the target, and one as the drag-image.
1291        // I want the drag-image to be translucent.
1292        // I think this is where render GL_COLOR_ATTACHMENTn (COLOR_BUFFERn?) would be handy.
1293        // But my hardware only returns 1 for glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, maxbuffers);
1294        // So I won't bother and will just render twice.
1295        NSBitmapImageRep* bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
1296        NSBitmapImageRep* bitmap_image_rep_transparent_copy = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGBA viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
1297//      NSBitmapImageRep* bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGBA viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height clearColorRed:1.0f clearColorGreen:1.0f clearColorBlue:0.0f clearColorAlpha:0.4f];
1298
1299//NSBitmapImageRep* bitmap_image_rep_transparent_copy = bitmap_image_rep;
1300
1301        // 0x32 is an arbitrary number. Basically, I want something between 0 and 0xFF.
1302        Internal_SetAlpha(bitmap_image_rep_transparent_copy, 0x32);
1303
1304        NSData* image_data = [bitmap_image_rep TIFFRepresentation];
1305
1306    if(image_data)
1307        {
1308       
1309                drag_paste_board = [NSPasteboard pasteboardWithName:NSDragPboard];
1310                // is owner:self or nil? (Hillegass says self)
1311        [drag_paste_board declareTypes: [NSArray arrayWithObjects: type, nil] owner: self];
1312        [drag_paste_board setData:image_data forType: type];
1313               
1314                // create an image from the data
1315                the_image = [[NSImage alloc] initWithData:[bitmap_image_rep_transparent_copy TIFFRepresentation]];
1316               
1317                the_point = [self convertPoint:[the_event locationInWindow] fromView:nil];
1318                the_size = [the_image size];
1319               
1320                // shift the point to the center of the image
1321                the_point.x = the_point.x - the_size.width/2.0;
1322                the_point.y = the_point.y - the_size.height/2.0;
1323
1324                // start drag
1325                [self dragImage:the_image
1326                                         at:the_point
1327                                 offset:NSMakeSize(0,0)
1328                                  event:the_event
1329                         pasteboard:drag_paste_board
1330                                 source:self
1331                          slideBack:YES];
1332                         
1333                [the_image release];
1334        }
1335        else
1336        {
1337                NSLog(@"Error, failed to create image data");
1338        }
1339       
1340}
1341
1342//////////////////////////////////////////////////////////////////////////////////////
1343/////////////////////////// For drag and drop and copy/paste (source) ////////////////
1344//////////////////////////////////////////////////////////////////////////////////////
1345
1346
1347
1348
1349
1350
1351////////////////////////////////////////////////////////////////////////
1352/////////////////////////// IBAction examples  /////////////////////////
1353////////////////////////////////////////////////////////////////////////
1354
1355// Connect a button to this to stop and reset the position.
1356- (IBAction) resetPosition:(id)the_sender
1357{
1358        //      osgGA::MatrixManipulator* camera_manipulator = theViewer->getCameraManipulator();
1359        // This only resets the position
1360        //      camera_manipulator->home(0.0);
1361       
1362        // There is no external API from SimpleViewer that I can see that will stop movement.
1363        // So fake the 'spacebar' to stop things and reset.
1364        // (Changed in Viewer?)
1365        //      printf("I'am here");
1366       
1367        // Reset to start position
1368        theViewer->home();
1369        [self setNeedsDisplay:YES];
1370}
1371
1372// Connect a NSColorWell to this to change color.
1373// A better way to do this is use Cocoa Bindings because it will automatically
1374// synchronize between your model and view, but since this demo lacks a model and controller
1375// aspect it wouldn't do much good.
1376- (IBAction) takeBackgroundColorFrom:(id)the_sender
1377{
1378        NSColor* the_color = [the_sender color];
1379
1380        theViewer->getCamera()->setClearColor(
1381                osg::Vec4(
1382                        [the_color redComponent],
1383                        [the_color greenComponent],
1384                        [the_color blueComponent],
1385                        [the_color alphaComponent]
1386                )
1387        );
1388        [self setNeedsDisplay:YES];
1389}
1390
1391////////////////////////////////////////////////////////////////////////
1392/////////////////////////// End IBAction examples  /////////////////////
1393////////////////////////////////////////////////////////////////////////
1394
1395@end
Note: See TracBrowser for help on using the browser.