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

Revision 12292, 59.0 kB (checked in by robert, 3 years ago)

Ran svn propset -R svn:eol-style native . on the OpenSceneGraph

  • Property svn:eol-style set to native
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    const GLint 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.