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

Revision 6901, 53.2 kB (checked in by robert, 8 years ago)

From Eric Wing, "So, something strange happened to the repo and some of this project's
files got messed up, most notiably the Nib and also the Localized
strings file. I didn't notice the latter until now so Martin is
missing this file.

Anyway, the attached tar contains all new versions of all the
necessary files. There are cleanups and fixes to a lot of things.
Martin did a good job porting the thing to osg::Viewer so most of the
code changes I made address other areas.

Two things I noticed in the new port you might want to consider as
feedback. First, there might be a bug with osgViewer when the view
size goes to 0. If you play with the splitviews in this program and
shrink the view until it is closed, and then re-expand it, the model
doesn't come back, not even after a home() call. SimpleViewer? didn't
have this problem.

Second, a more minor thing, this program has a
take-screenshot--and-copy-to-clipboard feature via Cmd-C (or Menu
item). I achieve this by using osg::Camera to render to an FBO and
then copy the contents to Cocoa. To insert the camera, I manipulate
the scenegraph so I can get the camera node in and out. I end up
calling setSceneData at the end of eveything to restore everything to
the original state before I started mucking with the scenegraph. This
unfortunately, triggers a home() reset. So in this particular case, it
make Copy look like it's changing the scene. The old SimpleViewer? had
the same problem, but I was able to work around it by directly
invoking the underlying SceneView?'s setSceneData so the home()
mechanism was bypassed. The viewer design seems to protect this data
more carefully so the bypass trick won't work. My feedback is that
maybe a flag or extra parameter can be introduced so a reset is not
triggered if not desired.

I have checked in a ton of Xcode fixes for the entire build process in
general so once this piece gets checked in, hopefully everything will
build cleanly."

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