Imported Upstream version 2.81
[platform/upstream/libbullet.git] / Extras / AllBulletDemosOSX / src / toolkit / BTOpenGLView.m
1 /*
2 Bullet Continuous Collision Detection and Physics Library
3 Copyright (c) 2003-2006 Erwin Coumans  http://continuousphysics.com/Bullet/
4
5 This software is provided 'as-is', without any express or implied warranty.
6 In no event will the authors be held liable for any damages arising from the use of this software.
7 Permission is granted to anyone to use this software for any purpose, 
8 including commercial applications, and to alter it and redistribute it freely, 
9 subject to the following restrictions:
10
11 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
12 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
13 3. This notice may not be removed or altered from any source distribution.
14 */
15
16 #import "BTOpenGLView.h"
17
18 #include <CoreFoundation/CoreFoundation.h>
19 #import <OpenGL/OpenGL.h>
20 #import <Carbon/Carbon.h>
21
22 #import "BTFullScreenWindow.h"
23 #import "BTGLUTKeyAdapter.h"
24
25 #pragma mark -
26 #pragma mark Private Methods
27
28 @interface BTOpenGLView (Internal)
29
30 - (void) update;
31 - (void) boundsDidChange: (NSNotification *) notification;
32 - (void) setVBL: (BOOL*) vbl forContext: (NSOpenGLContext*) context;
33 - (void) setMultithreaded: (BOOL) mt;
34 - (NSOpenGLPixelFormat*) windowedPixelFormat: (BOOL*) antialias;
35
36 @end
37
38 @implementation BTOpenGLView
39
40 #pragma mark -
41 #pragma mark Bootstrap
42
43 - (id)initWithFrame:(NSRect)frameRect
44 {
45         self = [super initWithFrame:frameRect];
46         if (self == nil)
47         {
48                 NSLog( @"BTOpenGLView::initWithFrame - Unable to init" );
49                 return nil;
50         }
51
52         _modifierFlags = 0;
53         _multisample = YES;
54         _vblSync = YES;
55         _firstFrame = YES;
56         _setupBoundsChangeNotification = NO;
57
58         /*
59                 Set up the windowed context -- it will be assigned to the
60                 view later, not now.
61         */
62         _windowedContext = [[NSOpenGLContext alloc] initWithFormat: [self windowedPixelFormat: &_multisample]
63                                                                                                   shareContext: nil];
64
65         [self setVBL: &_vblSync forContext: _windowedContext];
66
67         if (_windowedContext == nil)
68         {
69                 NSLog(@"Got nil windowed context");
70                 [self dealloc];
71                 return nil;
72         }
73         
74         /*
75                 Setup and start the update timer.
76         */
77         _interval = 1.0 / 60.0;
78         _timer = [[NSTimer scheduledTimerWithTimeInterval: _interval
79                                                                                            target: self
80                                                                                          selector: @selector(update)
81                                                                                          userInfo: nil
82                                                                                           repeats: YES ] retain];
83         
84         [[NSRunLoop currentRunLoop] addTimer: _timer forMode: NSEventTrackingRunLoopMode];
85         
86         return self;
87 }
88
89 - (void)dealloc
90 {
91         [_timer invalidate];
92         [_timer release];
93
94         [_delegate contextWillBeDestroyed];
95         
96         [_windowedContext release];
97         
98         [[NSNotificationCenter defaultCenter] removeObserver: self];
99         
100         [super dealloc];
101 }
102
103 - (void) awakeFromNib
104 {
105         NSWindow *window = [self window];
106         [window setAcceptsMouseMovedEvents: YES];
107         [window makeFirstResponder: self];
108         [window setInitialFirstResponder: self];                
109 }
110
111 - (void)drawRect:(NSRect)rect
112 {
113         [self update];
114 }
115
116 #pragma mark -
117 #pragma mark Public API
118
119 - (void) setDelegate: (id <BTOpenGLDisplayDelegate>) delegate
120 {
121         // we don't retain delegates
122         _delegate = delegate;
123 }
124
125 - (id <BTOpenGLDisplayDelegate>) delegate
126 {
127         return _delegate;
128 }
129
130 - (void) setTargetFPS: (float) fps
131 {
132         float newInterval = 1.0 / fps;
133         if ( ABS( newInterval - _interval ) > 1.0e-3 )
134         {
135                 _interval = newInterval;
136
137                 [_timer invalidate];
138                 [_timer release];
139
140                 _timer = [[NSTimer scheduledTimerWithTimeInterval: _interval
141                                                                                                    target: self
142                                                                                                  selector: @selector(update)
143                                                                                                  userInfo: nil
144                                                                                                   repeats: YES ] retain];
145                 
146                 [[NSRunLoop currentRunLoop] addTimer: _timer forMode: NSEventTrackingRunLoopMode];
147         }
148         
149 }
150
151 - (float) targetFPS
152 {
153         return 1.0 / _interval;
154 }
155
156 - (float) currentFPS
157 {
158         return _currentFPS;
159 }
160
161 - (void) setFullscreen: (BOOL) fullscreen
162 {
163         if ( fullscreen == _isFullScreen ) return;
164
165         _isFullScreen = fullscreen;
166         _suppressResize = YES;
167         
168         if ( _isFullScreen )
169         {
170                 _windowedWindow = [self window];
171                 
172                 /*
173                         Detach & retain the content view from the non-fullscreen window.
174                 */
175
176                 NSView *contentView = [_windowedWindow contentView];
177                 [contentView retain];
178                 [contentView removeFromSuperviewWithoutNeedingDisplay];
179
180                 /*
181                         Create a fullscreen window, attach the content view,
182                         and release the content view since the fullscreen window retained it.
183                 */
184                 
185                 _fullscreenWindow = [[BTFullScreenWindow alloc] initForScreen: [_windowedWindow screen]];
186                 [_fullscreenWindow setContentView: contentView ];
187                 [_fullscreenWindow makeKeyAndOrderFront:nil];           
188                 [contentView release];
189                 
190                 /*
191                         Hide the old window
192                 */
193                 [_windowedWindow orderOut: nil];
194                 
195                 /*
196                         Now, use the SetSystemUIMode API to auto-hide the dock
197                 */
198
199                 OSStatus error = SetSystemUIMode( kUIModeContentSuppressed, 0 );
200                 if ( error != noErr)
201                 {
202                         NSLog(@"Error couldn't set SystemUIMode: %ld", (long)error);
203                 }
204                 
205         }
206         else if ( _fullscreenWindow )
207         {
208                 /*
209                         Detach and retain the content view from the fullscreen window
210                 */
211                 NSView *contentView = [_fullscreenWindow contentView];
212                 [contentView retain];
213                 [contentView removeFromSuperviewWithoutNeedingDisplay];
214                 
215                 /*
216                         Reparent the content view to the non-fullscreen window,
217                         and release it since it's now owned by the non-fullscreen window
218                 */
219                 [_windowedWindow setContentView: contentView];
220                 [contentView release];
221                 
222                 [_windowedWindow makeKeyAndOrderFront: nil];
223
224                 /*
225                         Release the fullscreen window
226                 */
227                 [_fullscreenWindow orderOut: nil];
228                 [_fullscreenWindow release];
229                 _fullscreenWindow = nil;
230
231                 /*
232                         Restore dock's normal behaior
233                 */
234                 
235                 OSStatus error = SetSystemUIMode( kUIModeNormal, 0 );
236                 if ( error != noErr)
237                 {
238                         NSLog(@"Error couldn't set SystemUIMode: %ld", (long)error);
239                 }
240         }
241
242         _suppressResize = NO;
243         
244         [self boundsDidChange: nil];
245 }
246
247 - (BOOL) fullscreen
248 {
249         return _isFullScreen;
250 }
251
252 - (void) setMultisampleRendering: (BOOL) multisample
253 {
254         if ( multisample == _multisample ) return;
255
256         _multisample = multisample;
257         _firstFrame = YES;
258
259                                                 
260         NSOpenGLContext *oldWindowedContext = _windowedContext;
261         _windowedContext = [[NSOpenGLContext alloc] initWithFormat: [self windowedPixelFormat: &_multisample]
262                                                                                                   shareContext:  oldWindowedContext ];
263
264         [self setVBL: &_vblSync forContext: _windowedContext];
265
266         [oldWindowedContext release];
267         [_windowedContext setView: self];
268         [_windowedContext makeCurrentContext];
269         [_windowedContext update];
270         [self update];          
271 }
272
273 - (BOOL) multisampleRendering
274 {
275         return _multisample;
276 }
277
278 - (void) setVBLSync: (BOOL) sync
279 {
280         if ( sync == _vblSync ) return;
281         
282         _vblSync = sync;
283         [self setVBL: &_vblSync forContext: _windowedContext];
284 }
285
286 - (BOOL) vblSync
287 {
288         return _vblSync;
289 }
290
291 - (NSOpenGLContext *) openGLContext
292 {
293         return _windowedContext;
294 }
295
296 #pragma mark -
297 #pragma mark NSView Overrides
298
299 - (BOOL) isOpaque
300 {
301         return YES;
302 }
303
304 - (BOOL) acceptsFirstResponder
305 {
306         return YES;
307 }
308
309 - (void) keyDown:(NSEvent *)theEvent
310 {
311         int key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];  
312         
313         if ( BTKeyIsAlpha( key ))
314         {
315                 [ _delegate keyPressed: key ];
316         }
317         else
318         {
319                 [_delegate specialKeyPressed: BTKeyTranslateKeyCodeToSpecial( key )];
320         }       
321 }
322
323 - (void) keyUp:(NSEvent *)theEvent
324 {
325         int key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];  
326
327         if ( BTKeyIsAlpha( key ))
328         {
329                 [ _delegate keyReleased: key ];
330         }
331         else
332         {
333                 [_delegate specialKeyReleased: BTKeyTranslateKeyCodeToSpecial( key )];
334         }       
335 }
336
337 - (void) mouseDown: (NSEvent *) event
338 {
339         int button = GLUT_LEFT_BUTTON;
340         switch( [event buttonNumber] )
341         {
342                 case 0: button = GLUT_LEFT_BUTTON; break;
343                 case 1: button = GLUT_RIGHT_BUTTON; break;
344                 case 2: button = GLUT_MIDDLE_BUTTON; break;
345                 default: break;
346         }
347
348         if ( _modifierFlags & NSControlKeyMask ) button = GLUT_RIGHT_BUTTON;
349         else if ( _modifierFlags & NSAlternateKeyMask ) button = GLUT_MIDDLE_BUTTON;
350         
351         [_delegate mouseButtonPressed: button];
352 }
353
354 - (void) mouseUp: (NSEvent *) event
355 {
356         int button = GLUT_LEFT_BUTTON;
357         switch( [event buttonNumber] )
358         {
359                 case 0: button = GLUT_LEFT_BUTTON; break;
360                 case 1: button = GLUT_RIGHT_BUTTON; break;
361                 case 2: button = GLUT_MIDDLE_BUTTON; break;
362                 default: break;
363         }
364
365         if ( _modifierFlags & NSControlKeyMask ) button = GLUT_RIGHT_BUTTON;
366         else if ( _modifierFlags & NSAlternateKeyMask ) button = GLUT_MIDDLE_BUTTON;
367         
368         [_delegate mouseButtonReleased: button];
369 }
370
371 -(void) mouseMoved: (NSEvent *) event
372 {
373         float dx = [event deltaX],
374               dy = [event deltaY];
375
376         NSPoint locationInView = [self convertPoint: [event locationInWindow] fromView: nil ];            
377                 
378         [_delegate mouseMoved: NSMakePoint( dx, dy )];
379         [_delegate newMousePosition: locationInView];
380 }
381
382 -(void) mouseDragged: (NSEvent *) event
383 {
384         [self mouseMoved: event];
385 }
386
387 - (void) scrollWheel: (NSEvent *) event
388 {
389         float dx = [event deltaX],
390               dy = [event deltaY];
391
392         [_delegate scrollWheel: NSMakePoint( dx, dy )];
393 }
394
395 - (void)flagsChanged:(NSEvent *) event
396 {
397         _modifierFlags = [event modifierFlags];
398 }
399
400
401 #pragma mark -
402 #pragma mark Private
403
404 - (void) update
405 {
406         if ( !_setupBoundsChangeNotification )
407         {
408                 _setupBoundsChangeNotification = YES;
409                 
410                 /*
411                          This is hacky, but basically, we can't handle bounds-changing
412                          ops correctly until everything's set up correctly.
413                  */
414                 [self setPostsBoundsChangedNotifications:YES];
415                 [[NSNotificationCenter defaultCenter] addObserver: self
416                                                                                                  selector: @selector( boundsDidChange: )
417                                                                                                          name: NSViewFrameDidChangeNotification
418                                                                                                    object: nil];
419         }
420         
421         if (_firstFrame)
422         {
423                 [_windowedContext setView:self];                        
424         }
425         
426         [_windowedContext makeCurrentContext];
427         
428         if (_firstFrame)
429         {
430                 _firstFrame = NO;
431                 
432                 [_delegate contextCreated];
433                 
434                 if ( _multisample )
435                 {
436                         glEnable (GL_MULTISAMPLE_ARB);
437                         // this fucks up text rendering, on nVIDIA, at least
438                         //glHint (GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST);
439                 }
440                 else
441                 {
442                         glDisable( GL_MULTISAMPLE_ARB );
443                 }
444
445                 [self setMultithreaded: NO];
446                 
447                 NSSize contextSize;
448                 if ( _isFullScreen )
449                 {
450                         contextSize.width = CGDisplayPixelsWide(kCGDirectMainDisplay);
451                         contextSize.height = CGDisplayPixelsHigh(kCGDirectMainDisplay);
452                 }
453                 else
454                 {
455                         contextSize = [self bounds].size;
456                 }
457                 
458                 [_delegate contextWillResize];
459                 [_delegate contextResized: contextSize];
460                 [_delegate contextDidResize];
461                 [_delegate contextStateInvalidated];
462         }
463         
464         double now = CFAbsoluteTimeGetCurrent();
465         
466         if ( _delegate) [_delegate display: now - _lastFrameTime];
467         else
468         {
469                 glClearColor( 0.5, 0.5, 0.5, 1 );
470                 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
471         }
472         
473         _lastFrameTime = now;
474
475
476         [[NSOpenGLContext currentContext] flushBuffer];
477         
478         /*
479                 Now, update our FPS
480         */
481
482         {
483                 static unsigned int frameCounter = 1;
484                 static double lastCheckTime = 0;
485                 
486                 double elapsed = now - lastCheckTime;
487                 if ( elapsed > 1.0 )
488                 {
489                         _currentFPS = (float) ( ((double) frameCounter ) / elapsed );
490
491                         lastCheckTime = now;
492                         frameCounter = 0;
493                 }
494
495                 frameCounter++;
496         }
497 }
498
499 - (void) boundsDidChange: (NSNotification *) notification
500 {
501         if ( _suppressResize ) return;
502         
503         [_windowedContext setView:self];
504         [_windowedContext makeCurrentContext];
505         [_windowedContext update];
506         
507         NSSize contextSize = [self bounds].size;
508
509         if ( _delegate )
510         {
511                 [_delegate contextWillResize];
512                 [_delegate contextResized: contextSize ];
513                 [_delegate contextDidResize];
514         }
515         else
516         {
517                 glViewport( 0, 0, (int) contextSize.width, (int) contextSize.height );
518         }
519 }
520
521 - (void) setVBL: (BOOL*) vbl forContext: (NSOpenGLContext*) context
522 {
523         GLint value = *vbl ? 1 : 0;
524         [context setValues: &value forParameter: NSOpenGLCPSwapInterval];
525         
526         *vbl = value ? YES : NO;
527 }
528
529 - (void) setMultithreaded: (BOOL) mt
530 {
531         CGLError err = kCGLNoError;
532         CGLContextObj ctx = CGLGetCurrentContext();
533         
534         // Enable Apple's multi-threaded GL engine -- it's generally useful for 
535         // high vertex throughput. Not high fragment situations
536
537         if ( mt )
538         {
539                 err =  CGLEnable( ctx, kCGLCEMPEngine );
540         }
541         else
542         {
543                 err = CGLDisable( ctx, kCGLCEMPEngine );
544         }
545         
546         if (err != kCGLNoError )
547         {
548                 NSLog( @"BTOpenGLView -setMultithreaded: forContext: -- Unable to %s multithreaded GL",
549                            mt ? "enable" : "disable" );
550         }       
551 }
552
553 - (NSOpenGLPixelFormat*) windowedPixelFormat: (BOOL*) antialias
554 {
555         NSOpenGLPixelFormatAttribute aaAttrs[] =
556         {
557                 NSOpenGLPFADoubleBuffer,
558                 NSOpenGLPFAAccelerated,
559                 NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)32,
560                 NSOpenGLPFAStencilSize, (NSOpenGLPixelFormatAttribute)8,
561                 NSOpenGLPFASingleRenderer,
562                 NSOpenGLPFASampleBuffers, (NSOpenGLPixelFormatAttribute)( 1 ),
563                 NSOpenGLPFASamples, (NSOpenGLPixelFormatAttribute)( 4 ),
564                 NSOpenGLPFAScreenMask, (NSOpenGLPixelFormatAttribute) CGDisplayIDToOpenGLDisplayMask(kCGDirectMainDisplay),
565                 NSOpenGLPFANoRecovery,
566                 (NSOpenGLPixelFormatAttribute)0
567         };
568
569         NSOpenGLPixelFormatAttribute vanillaAttrs[] =
570         {
571                 NSOpenGLPFADoubleBuffer,
572                 NSOpenGLPFAAccelerated,
573                 NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)32,
574                 NSOpenGLPFAStencilSize, (NSOpenGLPixelFormatAttribute)8,
575                 NSOpenGLPFASingleRenderer,
576                 NSOpenGLPFASampleBuffers, (NSOpenGLPixelFormatAttribute)( 0 ),
577                 NSOpenGLPFASamples, (NSOpenGLPixelFormatAttribute)( 0 ),
578                 NSOpenGLPFAScreenMask, (NSOpenGLPixelFormatAttribute) CGDisplayIDToOpenGLDisplayMask(kCGDirectMainDisplay),
579                 NSOpenGLPFANoRecovery,
580                 (NSOpenGLPixelFormatAttribute)0
581         };
582
583         NSOpenGLPixelFormat* fmt = 0;
584         
585         if ( *antialias )
586         {
587                 fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes: (NSOpenGLPixelFormatAttribute*) aaAttrs]; 
588                 if ( nil == fmt )
589                 {
590                         *antialias = NO;
591                         fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes: (NSOpenGLPixelFormatAttribute*) vanillaAttrs]; 
592                 }
593         }
594         else
595         {
596                 fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes: (NSOpenGLPixelFormatAttribute*) vanillaAttrs]; 
597         }
598         
599         return fmt;
600 }
601
602
603 @end