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

Revision 7655, 54.9 kB (checked in by robert, 6 years ago)

From Eric Wing, "For osgviewerCocoa, a very simple change to allow toggling between
fullscreen mode and back between views. (To activate, double click on
the view to toggle.) It demonstrates/uses the new one-liner fullscreen
method introduced in Leopard. Code will still compile and run in
pre-Leopard (thanks to Obj-C dynamic/late binding), but code path is
treated as a no-op in those cases."

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               
679                // Let's toggle fullscreen for show
680                [self toggleFullScreen:nil];
681
682        }
683        [self setNeedsDisplay:YES];
684}
685
686- (void) doLeftMouseButtonUp:(NSEvent*)the_event
687{
688        // We must convert the mouse event locations from the window coordinate system to the
689        // local view coordinate system.
690        NSPoint the_point = [the_event locationInWindow];
691    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
692       
693        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 1);
694        [self setNeedsDisplay:YES];
695}
696
697- (void) doRightMouseButtonDown:(NSEvent*)the_event
698{
699        // We must convert the mouse event locations from the window coordinate system to the
700        // local view coordinate system.
701        NSPoint the_point = [the_event locationInWindow];
702    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
703        if([the_event clickCount] == 1)
704        {
705                theViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 3);
706        }
707        else
708        {
709                theViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 3);
710        }
711        [self setNeedsDisplay:YES];
712}
713
714
715- (void) doRightMouseButtonUp:(NSEvent*)the_event
716{
717        // We must convert the mouse event locations from the window coordinate system to the
718        // local view coordinate system.
719        NSPoint the_point = [the_event locationInWindow];
720    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
721       
722        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 3);
723        [self setNeedsDisplay:YES];
724}
725
726- (void) doMiddleMouseButtonDown:(NSEvent*)the_event
727{
728        // We must convert the mouse event locations from the window coordinate system to the
729        // local view coordinate system.
730        NSPoint the_point = [the_event locationInWindow];
731    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
732       
733        if([the_event clickCount] == 1)
734        {
735                theViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 2);
736        }
737        else
738        {
739                theViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 2);
740        }
741        [self setNeedsDisplay:YES];
742}
743
744- (void) doExtraMouseButtonDown:(NSEvent*)the_event buttonNumber:(int)button_number
745{
746        // We must convert the mouse event locations from the window coordinate system to the
747        // local view coordinate system.
748        NSPoint the_point = [the_event locationInWindow];
749    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
750
751        if([the_event clickCount] == 1)
752        {
753                theViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, button_number+1);
754        }
755        else
756        {
757                theViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, button_number+1);
758        }
759        [self setNeedsDisplay:YES];
760}
761
762
763- (void) doMiddleMouseButtonUp:(NSEvent*)the_event
764{
765        // We must convert the mouse event locations from the window coordinate system to the
766        // local view coordinate system.        NSPoint the_point = [the_event locationInWindow];
767        NSPoint the_point = [the_event locationInWindow];
768        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
769        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 2);
770        [self setNeedsDisplay:YES];
771}
772
773- (void) doExtraMouseButtonUp:(NSEvent*)the_event buttonNumber:(int)button_number
774{
775        // We must convert the mouse event locations from the window coordinate system to the
776        // local view coordinate system.        NSPoint the_point = [the_event locationInWindow];
777        NSPoint the_point = [the_event locationInWindow];
778        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
779        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, button_number+1);
780       
781        [self setNeedsDisplay:YES];
782}
783
784// This is a job for Mighty Mouse!
785// For the most fluid experience turn on 360 degree mode availble in 10.4.8+.
786// With your Mighty Mouse plugged in,
787// open 'Keyboard & Mouse' in 'System Preferences'.
788// Select the 'Mouse' tab.
789// Under 'Scrolling Options' select '360 degree'.
790// That should improve diagonal scrolling.
791// You should also be able to use 'two-finger scrolling' on newer laptops.
792- (void) scrollWheel:(NSEvent*)the_event
793{
794        // Unfortunately, it turns out mouseScroll2D doesn't actually do anything.
795        // The camera manipulators don't seem to implement any code that utilize the scroll values.
796        // This this call does nothing.
797//      theViewer->getEventQueue()->mouseScroll2D([the_event deltaX], [the_event deltaY]);
798
799        // With the absense of a useful mouseScroll2D API, we can manually simulate the desired effect.
800        NSPoint the_point = [the_event locationInWindow];
801        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
802        theViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 1);
803        theViewer->getEventQueue()->mouseMotion(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY]);
804        theViewer->getEventQueue()->mouseButtonRelease(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY], 1);
805
806        [self setNeedsDisplay:YES];
807}
808
809////////////////////////////////////////////////////////////////////////
810/////////////////////////// End Mouse Stuff ////////////////////////////
811////////////////////////////////////////////////////////////////////////
812
813////////////////////////////////////////////////////////////////////////
814/////////////////////////// Keyboard Stuff /////////////////////////////
815////////////////////////////////////////////////////////////////////////
816// Needed to accept keyboard events
817- (BOOL) acceptsFirstResponder
818{
819        return YES;
820}
821
822- (void) keyDown:(NSEvent*)the_event
823{
824        // Do you want characters or charactersIgnoringModifiers?
825        NSString* event_characters = [the_event characters];
826//      NSString* event_characters = [the_event charactersIgnoringModifiers];
827
828        unichar unicode_character = [event_characters characterAtIndex:0];
829//      NSLog(@"unicode_character: %d", unicode_character);
830        theViewer->getEventQueue()->keyPress(static_cast<osgGA::GUIEventAdapter::KeySymbol>(unicode_character));
831
832        [self setNeedsDisplay:YES];
833}
834
835- (void) keyUp:(NSEvent*)the_event
836{
837        // Do you want characters or charactersIgnoringModifiers?
838        NSString* event_characters = [the_event characters];
839//      NSString* event_characters = [the_event charactersIgnoringModifiers];
840        unichar unicode_character = [event_characters characterAtIndex:0];
841        theViewer->getEventQueue()->keyRelease(static_cast<osgGA::GUIEventAdapter::KeySymbol>(unicode_character));
842        [self setNeedsDisplay:YES];
843}
844
845////////////////////////////////////////////////////////////////////////
846/////////////////////////// End Keyboard Stuff /////////////////////////
847////////////////////////////////////////////////////////////////////////
848
849////////////////////////////////////////////////////////////////////////
850/////////////////////////// View and Draw Stuff ////////////////////////
851////////////////////////////////////////////////////////////////////////
852
853// This method is periodically called by my timer.
854- (void) animationCallback
855{
856        // Simply notify Cocoa that a drawRect needs to take place.
857        // Potential optimization is to query the OSG stuff to find out if a redraw is actually necessary.
858        [self setNeedsDisplay:YES];
859}
860
861// This is an optional optimization. This states you don't have a transparent view/window.
862// Obviously don't use this or set it to NO if you intend for your view to be see-through.
863- (BOOL) isOpaque
864{
865        return YES;
866}
867
868// Resolution Independent UI is coming... (Tiger's Quartz Debug already has the tool.)
869// We must think in 'point sizes', not pixel sizes, so a conversion is needed for OpenGL.
870- (void) resizeViewport
871{
872        NSSize size_in_points = [self bounds].size;
873        // This coordinate system conversion seems to make things work with Quartz Debug.
874        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
875        theViewer->getEventQueue()->windowResize(0, 0, size_in_window_coordinates.width, size_in_window_coordinates.height);
876        graphicsWindow->resized(0, 0, size_in_window_coordinates.width, size_in_window_coordinates.height);
877}
878
879// For window resize
880- (void) reshape
881{
882        [super reshape];
883        [self resizeViewport];
884}
885
886// This is the code that actually draws.
887// Remember you shouldn't call drawRect: directly and should use setNeedsDisplay:YES
888// This is so the operating system can optimize when a draw is actually needed.
889// (e.g. No sense drawing when the application is hidden.)
890- (void) drawRect:(NSRect)the_rect
891{
892        if([[NSGraphicsContext currentContext] isDrawingToScreen])
893        {
894                [[self openGLContext] makeCurrentContext];
895                theViewer->frame();
896                [[self openGLContext] flushBuffer];
897        }
898        else // This is usually the print case
899        {
900                [[self openGLContext] makeCurrentContext];
901
902                // FIXME: We should be computing a size that fits best to the paper target
903                NSSize size_in_points = [self bounds].size;
904                NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
905                NSBitmapImageRep * bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
906               
907                NSImage* ns_image = [self imageFromBitmapImageRep:bitmap_image_rep];
908
909                if(ns_image)
910                {
911                        NSSize image_size = [ns_image size];
912                        [ns_image drawAtPoint:NSMakePoint(0.0, 0.0)
913                                        fromRect: NSMakeRect(0.0, 0.0, image_size.width, image_size.height)
914//                                 operation: NSCompositeSourceOver
915                                   operation: NSCompositeCopy
916                                        fraction: 1.0];         
917                }
918                else
919                {
920                        NSLog(@"Image not valid");
921                }
922        }
923}
924
925
926/* Optional render to framebuffer stuff below. The code renders offscreen to assist in screen capture stuff.
927 * This can be useful for things like the Dock minimization picture, drag-and-drop dropImage, copy and paste,
928 * and printing.
929 */
930
931// Convenience version. Will use the current view's bounds and produce an RGB image with the current clear color.
932- (NSBitmapImageRep*) renderOpenGLSceneToFramebuffer
933{
934        NSSize size_in_points = [self bounds].size;
935        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
936        const osg::Vec4& clear_color = theViewer->getCamera()->getClearColor();
937
938        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]];
939}
940
941// Convenience version. Allows you to specify the view and height and format, but uses the current the current clear color.
942- (NSBitmapImageRep*) renderOpenGLSceneToFramebufferAsFormat:(int)gl_format viewWidth:(float)view_width viewHeight:(float)view_height
943{
944        const osg::Vec4& clear_color = theViewer->getCamera()->getClearColor();
945
946        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]];
947}
948
949// Renders to an offscreen buffer and returns a copy of the data to an NSBitmapImageRep.
950// Allows you to specify the gl_format, width and height, and the glClearColor
951// gl_format is only GL_RGB or GLRGBA.
952- (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
953{
954        // Round values and bring to closest integer.
955        int viewport_width = (int)(view_width + 0.5f);
956        int viewport_height = (int)(view_height + 0.5f);
957       
958        NSBitmapImageRep* ns_image_rep;
959        osg::ref_ptr<osg::Image> osg_image = new osg::Image;
960       
961        if(GL_RGBA == gl_format)
962        {
963                // Introduced in 10.4, but gives much better looking results if you utilize transparency
964                if([NSBitmapImageRep instancesRespondToSelector:@selector(initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:)])
965                {
966                        ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
967                                                                                                  pixelsWide:viewport_width
968                                                                                                  pixelsHigh:viewport_height
969                                                                                           bitsPerSample:8
970                                                                                         samplesPerPixel:4
971                                                                                                        hasAlpha:YES
972                                                                                                        isPlanar:NO
973                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
974                                                                                                bitmapFormat:NSAlphaNonpremultipliedBitmapFormat // 10.4+, gives much better looking results if you utilize transparency
975                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGBA, GL_UNSIGNED_BYTE, 1)
976                                                                                                bitsPerPixel:32]
977                                        autorelease];
978                }
979                else // fallback for 10.0 to 10.3
980                {
981                        ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
982                                                                                                  pixelsWide:viewport_width
983                                                                                                  pixelsHigh:viewport_height
984                                                                                           bitsPerSample:8
985                                                                                         samplesPerPixel:4
986                                                                                                        hasAlpha:YES
987                                                                                                        isPlanar:NO
988                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
989                                                                                                // bitmapFormat:NSAlphaNonpremultipliedBitmapFormat // 10.4+, gives much better looking results if you utilize transparency
990                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGBA, GL_UNSIGNED_BYTE, 1)
991                                                                                                bitsPerPixel:32]
992                                        autorelease];
993                }
994                // This is an optimization. Instead of creating data in both an osg::Image and NSBitmapImageRep,
995                // Allocate just the memory in the NSBitmapImageRep and give the osg::Image a reference to the data.
996                // I let NSBitmapImageRep control the memory because I think it will be easier to deal with
997                // memory management in the cases where it must interact with other Cocoa mechanisms like Drag-and-drop
998                // where the memory persistence is less obvious. Make sure that you don't expect to use the osg::Image
999                // outside the scope of this function because there is no telling when the data will be removed out
1000                // from under it by Cocoa since osg::Image will not retain.
1001                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);
1002        }
1003        else if(GL_RGB == gl_format)
1004        {
1005                ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1006                                                                                                  pixelsWide:viewport_width
1007                                                                                                  pixelsHigh:viewport_height
1008                                                                                           bitsPerSample:8
1009                                                                                         samplesPerPixel:3
1010                                                                                                        hasAlpha:NO
1011                                                                                                        isPlanar:NO
1012                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
1013                                                                                                // bitmapFormat:(NSBitmapFormat)0 // 10.4+
1014                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGB, GL_UNSIGNED_BYTE, 1)
1015                                                                                                bitsPerPixel:24]
1016                                        autorelease];
1017
1018                // This is an optimization. Instead of creating data in both an osg::Image and NSBitmapImageRep,
1019                // Allocate just the memory in the NSBitmapImageRep and give the osg::Image a reference to the data.
1020                // I let NSBitmapImageRep control the memory because I think it will be easier to deal with
1021                // memory management in the cases where it must interact with other Cocoa mechanisms like Drag-and-drop
1022                // where the memory persistence is less obvious. Make sure that you don't expect to use the osg::Image
1023                // outside the scope of this function because there is no telling when the data will be removed out
1024                // from under it by Cocoa since osg::Image will not retain.
1025                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);
1026        }
1027        else
1028        {
1029                NSLog(@"Sorry, unsupported format in renderOpenGLSceneToFramebufferAsFormat");
1030                return nil;
1031        }
1032
1033        // Can't find a way to query Viewer for the current values, so recompute current view size.
1034        NSSize original_size_in_points = [self bounds].size;
1035        NSSize original_size_in_window_coordinates = [self convertSize:original_size_in_points toView:nil];
1036//      theViewer->getEventQueue()->windowResize(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1037
1038        theViewer->getEventQueue()->windowResize(0, 0, viewport_width, viewport_height);
1039        graphicsWindow->resized(0, 0, viewport_width, viewport_height);
1040
1041        /*
1042         * I want to use a Framebuffer Object because it seems to be the OpenGL sanctioned way of rendering offscreen.
1043         * Also, I want to try to decouple the image capture from the onscreen rendering. This is potentially useful
1044         * for two reasons:
1045         * 1) You may want to customize the image dimensions to best fit the situation (consider printing to a page to fit)
1046         * 2) You may want to customize the scene for the target (consider special output for a printer, or removed data for a thumbnail)
1047         * Unfortunately, I have hit two problems.
1048         * 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.
1049         * The workaround is to copy the camera attributes into another camera, and then add a second camera node into the scene.
1050         * I'm hoping OSG will simplify this in the future.
1051         * 2) I may have encountered a bug. Under some circumstances, the offscreen renderbuffer seems to get drawn into the onscreen view
1052         * 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.
1053         * So I'm wondering if OSG made the same mistake.
1054         * But the problem doesn't seem critical. It just looks bad.
1055         */
1056        //NSLog(@"Before camera glGetError: %s", gluErrorString(glGetError()));
1057        osg::Camera* root_camera = theViewer->getCamera();
1058
1059        // I originally tried the clone() method and the copy construction, but it didn't work right,
1060        // so I manually copy the attributes.
1061        osg::Camera* the_camera = new osg::Camera;
1062
1063        the_camera->setClearMask(root_camera->getClearMask());
1064        the_camera->setProjectionMatrix(root_camera->getProjectionMatrix());
1065        the_camera->setViewMatrix(root_camera->getViewMatrix());
1066        the_camera->setViewport(root_camera->getViewport());
1067        the_camera->setClearColor(
1068                osg::Vec4(
1069                        clear_red,
1070                        clear_green,
1071                        clear_blue,
1072                        clear_alpha
1073                )
1074        );
1075
1076        // This must be ABSOLUTE_RF, and not a copy of the root camera because the transforms are additive.
1077        the_camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
1078
1079        // We need to insert the new (second) camera into the scene (below the root camera) and attach
1080        // the scene data to the new camera.
1081        osg::ref_ptr<osg::Node> root_node = theViewer->getSceneData();
1082
1083        the_camera->addChild(root_node.get());
1084        // Don't call (bypass) Viewer's setSceneData, but the underlying SceneView's setSceneData.
1085        // Otherwise, the camera position gets reset to the home position.
1086        theViewer->setSceneData(the_camera);
1087
1088        // set the camera to render before the main camera.
1089        the_camera->setRenderOrder(osg::Camera::PRE_RENDER);
1090
1091        // tell the camera to use OpenGL frame buffer object where supported.
1092        the_camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
1093
1094
1095        // attach the image so its copied on each frame.
1096        the_camera->attach(osg::Camera::COLOR_BUFFER, osg_image.get());
1097
1098
1099        //NSLog(@"Before frame(), glGetError: %s", gluErrorString(glGetError()));
1100
1101
1102        // Render the scene
1103        theViewer->frame();
1104
1105        // Not sure if I really need this (seems to work without it), and if so, not sure if I need flush or finish
1106        glFlush();
1107//      glFinish();
1108
1109        //NSLog(@"After flush(), glGetError: %s", gluErrorString(glGetError()));
1110
1111
1112
1113        // The image is upside-down to Cocoa, so invert it.
1114        osg_image.get()->flipVertical();
1115
1116        // Clean up everything I changed
1117//      the_camera->detach(osg::Camera::COLOR_BUFFER);
1118//      the_camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER);
1119        // Don't call (bypass) Viewer's setSceneData, but the underlying SceneView's setSceneData.
1120        // Otherwise, the camera position gets reset to the home position.
1121        theViewer->setSceneData(root_node.get());
1122        theViewer->getEventQueue()->windowResize(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1123        graphicsWindow->resized(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1124
1125
1126        // Ugh. Because of the bug I mentioned, I'm losing the picture in the display when I print.
1127        [self setNeedsDisplay:YES];
1128        //NSLog(@"at return, glGetError: %s", gluErrorString(glGetError()));
1129
1130        return ns_image_rep;
1131}
1132
1133// Convenience method
1134- (NSImage*)imageFromBitmapImageRep:(NSBitmapImageRep*)bitmap_image_rep
1135{
1136        if(nil == bitmap_image_rep)
1137        {
1138                return nil;
1139        }
1140        NSImage* image = [[[NSImage alloc] initWithSize:[bitmap_image_rep size]] autorelease];
1141        [image addRepresentation:bitmap_image_rep];
1142        // This doesn't seem to work as I want it to. The image only gets flipped when rendered in a regular view.
1143        // It doesn't flip for the printer view. I must actually invert the pixels.
1144//      [image setFlipped:YES];
1145        return image;
1146}
1147
1148
1149
1150
1151////////////////////////////////////////////////////////////////////////
1152/////////////////////////// End View and Draw Stuff ////////////////////
1153////////////////////////////////////////////////////////////////////////
1154
1155////////////////////////////////////////////////////////////////////////
1156/////////////////////////// For drag and drop //////////////////////////
1157////////////////////////////////////////////////////////////////////////
1158- (unsigned int) draggingEntered:(id <NSDraggingInfo>)the_sender
1159{
1160        if([the_sender draggingSource] != self)
1161        {
1162                NSPasteboard* paste_board = [the_sender draggingPasteboard];
1163                // I respond to filename types or URL types
1164                NSArray* supported_types = [NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil];
1165                // If any of the supported types are being dragged in, activate the copy operation
1166                NSString* first_type = [paste_board availableTypeFromArray:supported_types];
1167                if(first_type != nil)
1168                {
1169                        return NSDragOperationCopy;
1170                }
1171        }
1172        // Means we don't support this type
1173        return NSDragOperationNone;
1174}
1175
1176// We're not using this method, but here it is as an example.
1177- (void) draggingExited:(id <NSDraggingInfo>)the_sender
1178{
1179}
1180
1181- (BOOL) prepareForDragOperation:(id <NSDraggingInfo>)the_sender
1182{
1183        return YES;
1184}
1185
1186- (BOOL) performDragOperation:(id <NSDraggingInfo>)the_sender
1187{
1188        NSPasteboard* paste_board = [the_sender draggingPasteboard];
1189
1190 
1191    if([[paste_board types] containsObject:NSFilenamesPboardType])
1192        {
1193        NSArray* file_names = [paste_board propertyListForType:NSFilenamesPboardType];
1194//        int number_of_files = [file_names count];
1195                // Exercise for the reader: Try loading all files in the array
1196                NSString* single_file = [file_names objectAtIndex:0];
1197            osg::ref_ptr<osg::Node> loaded_model = osgDB::readNodeFile([single_file fileSystemRepresentation]);
1198                if(!loaded_model)
1199                {
1200                        NSLog(@"File: %@ failed to load", single_file);
1201                        return NO;
1202                }
1203                theViewer->setSceneData(loaded_model.get());
1204                return YES;
1205    }
1206        else if([[paste_board types] containsObject:NSURLPboardType])
1207        {
1208                NSURL* file_url = [NSURL URLFromPasteboard:paste_board];
1209                // See if the URL is valid file path
1210                if(![file_url isFileURL])
1211                {
1212                        NSLog(@"URL: %@ needs to be a file for readNodeFile()", file_url);
1213                        return NO;
1214                }
1215                NSString* file_path = [file_url path];
1216            osg::ref_ptr<osg::Node> loaded_model = osgDB::readNodeFile([file_path fileSystemRepresentation]);
1217                if(!loaded_model)
1218                {
1219                        NSLog(@"URL: %@ failed to load, %@", file_url, file_path);
1220                        return NO;
1221                }
1222                theViewer->setSceneData(loaded_model.get());
1223                return YES;
1224        }
1225    return NO;
1226}
1227
1228// This method isn't really needed (I could move setNeedsDisplay up), but is here as an example
1229- (void) concludeDragOperation:(id <NSDraggingInfo>)the_sender
1230{
1231        [self setNeedsDisplay:YES];
1232}
1233
1234////////////////////////////////////////////////////////////////////////
1235/////////////////////////// End of drag and drop (receiver) ////////////
1236////////////////////////////////////////////////////////////////////////
1237
1238
1239//////////////////////////////////////////////////////////////////////////////////////
1240/////////////////////////// For drag and drop and copy/paste (source) ////////////////
1241//////////////////////////////////////////////////////////////////////////////////////
1242- (IBAction) copy:(id)sender
1243{
1244    NSString* type = NSTIFFPboardType;
1245    NSData* image_data = [self contentsAsDataOfType:type];
1246       
1247    if(image_data)
1248        {
1249        NSPasteboard* general_pboard = [NSPasteboard generalPasteboard];
1250        [general_pboard declareTypes:[NSArray arrayWithObjects:type, nil] owner: nil];
1251        [general_pboard setData:image_data forType:type];
1252    }
1253}
1254
1255- (NSData*) dataWithTIFFOfContentView
1256{
1257        [[self openGLContext] makeCurrentContext];
1258        NSBitmapImageRep * image = [self renderOpenGLSceneToFramebuffer];
1259        NSData* data = nil;
1260
1261        if(image != nil)
1262        {
1263                data = [image TIFFRepresentation];
1264        }
1265        return data;
1266}
1267
1268/* Returns a data object containing the current contents of the receiving window */
1269- (NSData*) contentsAsDataOfType:(NSString *)pboardType
1270{
1271        NSData * data = nil;
1272        if ([pboardType isEqualToString: NSTIFFPboardType] == YES)
1273        {
1274                data = [self dataWithTIFFOfContentView];
1275        }
1276    return data;
1277}
1278
1279
1280- (void) startDragAndDropAsSource:(NSEvent*)the_event
1281{
1282        NSPasteboard* drag_paste_board;
1283        NSImage* the_image;
1284        NSSize the_size;
1285        NSPoint the_point;
1286
1287        NSSize size_in_points = [self bounds].size;
1288        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
1289
1290        // Create the image that will be dragged
1291        NSString * type = NSTIFFPboardType;
1292
1293        [[self openGLContext] makeCurrentContext];
1294
1295        // I want two images. One to be rendered for the target, and one as the drag-image.
1296        // I want the drag-image to be translucent.
1297        // I think this is where render GL_COLOR_ATTACHMENTn (COLOR_BUFFERn?) would be handy.
1298        // But my hardware only returns 1 for glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, maxbuffers);
1299        // So I won't bother and will just render twice.
1300        NSBitmapImageRep* bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
1301        NSBitmapImageRep* bitmap_image_rep_transparent_copy = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGBA viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
1302//      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];
1303
1304//NSBitmapImageRep* bitmap_image_rep_transparent_copy = bitmap_image_rep;
1305
1306        // 0x32 is an arbitrary number. Basically, I want something between 0 and 0xFF.
1307        Internal_SetAlpha(bitmap_image_rep_transparent_copy, 0x32);
1308
1309        NSData* image_data = [bitmap_image_rep TIFFRepresentation];
1310
1311    if(image_data)
1312        {
1313       
1314                drag_paste_board = [NSPasteboard pasteboardWithName:NSDragPboard];
1315                // is owner:self or nil? (Hillegass says self)
1316        [drag_paste_board declareTypes: [NSArray arrayWithObjects: type, nil] owner: self];
1317        [drag_paste_board setData:image_data forType: type];
1318               
1319                // create an image from the data
1320                the_image = [[NSImage alloc] initWithData:[bitmap_image_rep_transparent_copy TIFFRepresentation]];
1321               
1322                the_point = [self convertPoint:[the_event locationInWindow] fromView:nil];
1323                the_size = [the_image size];
1324               
1325                // shift the point to the center of the image
1326                the_point.x = the_point.x - the_size.width/2.0;
1327                the_point.y = the_point.y - the_size.height/2.0;
1328
1329                // start drag
1330                [self dragImage:the_image
1331                                         at:the_point
1332                                 offset:NSMakeSize(0,0)
1333                                  event:the_event
1334                         pasteboard:drag_paste_board
1335                                 source:self
1336                          slideBack:YES];
1337                         
1338                [the_image release];
1339        }
1340        else
1341        {
1342                NSLog(@"Error, failed to create image data");
1343        }
1344       
1345}
1346
1347//////////////////////////////////////////////////////////////////////////////////////
1348/////////////////////////// For drag and drop and copy/paste (source) ////////////////
1349//////////////////////////////////////////////////////////////////////////////////////
1350
1351
1352
1353
1354
1355
1356////////////////////////////////////////////////////////////////////////
1357/////////////////////////// IBAction examples  /////////////////////////
1358////////////////////////////////////////////////////////////////////////
1359
1360// Connect a button to this to stop and reset the position.
1361- (IBAction) resetPosition:(id)the_sender
1362{
1363        //      osgGA::MatrixManipulator* camera_manipulator = theViewer->getCameraManipulator();
1364        // This only resets the position
1365        //      camera_manipulator->home(0.0);
1366       
1367        // There is no external API from SimpleViewer that I can see that will stop movement.
1368        // So fake the 'spacebar' to stop things and reset.
1369        // (Changed in Viewer?)
1370        //      printf("I'am here");
1371       
1372        // Reset to start position
1373        theViewer->home();
1374        [self setNeedsDisplay:YES];
1375}
1376
1377// Connect a NSColorWell to this to change color.
1378// A better way to do this is use Cocoa Bindings because it will automatically
1379// synchronize between your model and view, but since this demo lacks a model and controller
1380// aspect it wouldn't do much good.
1381- (IBAction) takeBackgroundColorFrom:(id)the_sender
1382{
1383        NSColor* the_color = [the_sender color];
1384
1385        theViewer->getCamera()->setClearColor(
1386                osg::Vec4(
1387                        [the_color redComponent],
1388                        [the_color greenComponent],
1389                        [the_color blueComponent],
1390                        [the_color alphaComponent]
1391                )
1392        );
1393        [self setNeedsDisplay:YES];
1394}
1395
1396- (IBAction) toggleFullScreen:(id)the_sender
1397{
1398        // I'm lazy and rather use the new 10.5 Cocoa Fullscreen API.
1399        // For now, no legacy support for fullscreen.
1400        // One of the cool things about Obj-C is dynamic/late binding.
1401        // We can compile and run this code on versions prior to 10.5.
1402        // At run-time, we check to see if these methods actually exist
1403        // and if they do, we message them. If not, we avoid them.
1404        if([self respondsToSelector:@selector(isInFullScreenMode)])
1405        {
1406                if([self isInFullScreenMode])
1407                {
1408                        [self exitFullScreenModeWithOptions:nil];
1409                }
1410                else
1411                {
1412                        [self enterFullScreenMode:[NSScreen mainScreen] withOptions:nil];
1413                }
1414        }
1415}
1416
1417////////////////////////////////////////////////////////////////////////
1418/////////////////////////// End IBAction examples  /////////////////////
1419////////////////////////////////////////////////////////////////////////
1420
1421@end
Note: See TracBrowser for help on using the browser.