1/*
2	File:		MBCBoardView.mm
3	Contains:	General view handling infrastructure
4	Copyright:	© 2002-2012 by Apple Inc., all rights reserved.
5
6	IMPORTANT: This Apple software is supplied to you by Apple Computer,
7	Inc.  ("Apple") in consideration of your agreement to the following
8	terms, and your use, installation, modification or redistribution of
9	this Apple software constitutes acceptance of these terms.  If you do
10	not agree with these terms, please do not use, install, modify or
11	redistribute this Apple software.
12
13	In consideration of your agreement to abide by the following terms,
14	and subject to these terms, Apple grants you a personal, non-exclusive
15	license, under Apple's copyrights in this original Apple software (the
16	"Apple Software"), to use, reproduce, modify and redistribute the
17	Apple Software, with or without modifications, in source and/or binary
18	forms; provided that if you redistribute the Apple Software in its
19	entirety and without modifications, you must retain this notice and
20	the following text and disclaimers in all such redistributions of the
21	Apple Software.  Neither the name, trademarks, service marks or logos
22	of Apple Inc. may be used to endorse or promote products
23	derived from the Apple Software without specific prior written
24	permission from Apple.  Except as expressly stated in this notice, no
25	other rights or licenses, express or implied, are granted by Apple
26	herein, including but not limited to any patent rights that may be
27	infringed by your derivative works or by other works in which the
28	Apple Software may be incorporated.
29
30	The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
31	MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
32	THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND
33	FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS
34	USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
35
36	IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
37	INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
38	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
39	PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
40	REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE,
41	HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING
42	NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
43	ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44*/
45#import "MBCBoardView.h"
46
47//
48// Our implementation is distributed across several other files
49//
50#import "MBCBoardViewDraw.h"
51#import "MBCBoardViewModels.h"
52#import "MBCBoardViewTextures.h"
53#import "MBCBoardViewMouse.h"
54
55#import "MBCAnimation.h"
56#import "MBCController.h"
57#import "MBCPlayer.h"
58#import "MBCBoardWin.h"
59#import "MBCUserDefaults.h"
60
61#include <algorithm>
62
63NSColor * MBCColor::GetColor() const
64{
65	return [NSColor
66			   colorWithCalibratedRed:color[0] green:color[1] blue:color[2]
67			   alpha:color[3]];
68}
69
70void MBCColor::SetColor(NSColor * newColor)
71{
72	color[0] = [newColor redComponent];
73	color[1] = [newColor greenComponent];
74	color[2] = [newColor blueComponent];
75}
76
77@implementation MBCBoardView
78
79- (NSOpenGLPixelFormat *)pixelFormatWithFSAA:(int)fsaaSamples
80{
81    NSOpenGLPixelFormatAttribute fsaa_attr[] =
82    {
83		NSOpenGLPFAAllowOfflineRenderers,
84        NSOpenGLPFADoubleBuffer,
85		NSOpenGLPFAWindow,
86		NSOpenGLPFAAccelerated,
87		NSOpenGLPFANoRecovery,
88		NSOpenGLPFAColorSize, 		(NSOpenGLPixelFormatAttribute)24,
89		NSOpenGLPFAAlphaSize, 		(NSOpenGLPixelFormatAttribute)8,
90   		NSOpenGLPFADepthSize, 		(NSOpenGLPixelFormatAttribute)16,
91        NSOpenGLPFAStencilSize, 	(NSOpenGLPixelFormatAttribute)1,
92		NSOpenGLPFAMultisample,
93		NSOpenGLPFASampleBuffers, 	(NSOpenGLPixelFormatAttribute)1,
94		NSOpenGLPFASamples, 		(NSOpenGLPixelFormatAttribute)fsaaSamples,
95        (NSOpenGLPixelFormatAttribute)0
96	};
97    NSOpenGLPixelFormatAttribute jaggy_attr[] =
98    {
99		NSOpenGLPFAAllowOfflineRenderers,
100		NSOpenGLPFADoubleBuffer,
101		NSOpenGLPFAWindow,
102		NSOpenGLPFAColorSize, 		(NSOpenGLPixelFormatAttribute)24,
103		NSOpenGLPFAAlphaSize, 		(NSOpenGLPixelFormatAttribute)8,
104   		NSOpenGLPFADepthSize, 		(NSOpenGLPixelFormatAttribute)16,
105        NSOpenGLPFAStencilSize, 	(NSOpenGLPixelFormatAttribute)1,
106        (NSOpenGLPixelFormatAttribute)0
107	};
108	NSOpenGLPixelFormat *	pixelFormat =
109		[[NSOpenGLPixelFormat alloc] initWithAttributes:(fsaaSamples > 0) ? fsaa_attr : jaggy_attr];
110    if (fsaaSamples > 0) {
111        //
112        // We don't accept any substitutes
113        //
114        GLint actualFsaa;
115        [pixelFormat getValues:&actualFsaa forAttribute:NSOpenGLPFASamples forVirtualScreen:0];
116        if (actualFsaa != fsaaSamples) {
117            [pixelFormat release];
118
119            return nil;
120        }
121    }
122
123	return [pixelFormat autorelease];
124}
125
126- (int) maxAntiAliasing
127{
128    //
129    //  Analyze VRAM configuration and limit FSAA for low memory configurations.
130    //  On retina displays, always limit FSAA, because the VRAM consumption is even more
131    //  staggering and the payoff just isn't there.
132    //
133	static int sMax = -1;
134
135	if (sMax < 0) {
136        //
137        // Test VRAM size
138        //
139		GLint min_vram = 0;
140		CGLRendererInfoObj rend;
141		GLint n_rend = 0;
142		CGLQueryRendererInfo(0xffffff, &rend, &n_rend);
143		for (GLint i=0; i<n_rend; ++i) {
144			GLint cur_vram = 0;
145			CGLDescribeRenderer(rend, i, kCGLRPVideoMemoryMegabytes, &cur_vram);
146            if (!min_vram)
147                min_vram = cur_vram;
148            else if (cur_vram)
149                min_vram = std::min(min_vram, cur_vram);
150		}
151		sMax = (min_vram > 256 ? 8 : 4);
152        //
153        // Test for retina display
154        //
155        float backingScaleFactor = [[self window] backingScaleFactor];
156        if (!backingScaleFactor)
157            backingScaleFactor = [[NSScreen mainScreen] backingScaleFactor];
158        if (backingScaleFactor > 1.5f)
159            sMax = (min_vram > 512 ? 4 : 2);
160	}
161
162	return sMax;
163}
164
165- (id) initWithFrame:(NSRect)rect
166{
167	float	light_ambient		= 0.125f;
168	GLfloat light_pos[4] 		= { -60.0, 200.0, 0.0, 0.0};
169
170	//
171	// We first try to enable Full Scene Anti Aliasing if our graphics
172	// hardware lets use get away with it.
173	//
174	NSOpenGLPixelFormat * pixelFormat = nil;
175	for (fMaxFSAA = [self maxAntiAliasing]+2; !pixelFormat; )
176		pixelFormat = [self pixelFormatWithFSAA:(fMaxFSAA -= 2)];
177	fCurFSAA = fMaxFSAA;
178    [super initWithFrame:rect pixelFormat:pixelFormat];
179    [self setWantsBestResolutionOpenGLSurface:YES];
180    [[self openGLContext] makeCurrentContext];
181    if (fCurFSAA)
182        glEnable(GL_MULTISAMPLE);
183
184	//
185	// Determine some of our graphics limit
186	//
187	const char * const kGlExt = (const char *)glGetString(GL_EXTENSIONS);
188	if (kGlExt && strstr(kGlExt, "GL_EXT_texture_filter_anisotropic")) {
189		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fAnisotropy);
190		fAnisotropy = std::min(fAnisotropy, 4.0f);
191	} else
192		fAnisotropy	= 0.0f;
193
194
195	fBoardReflectivity	= 0.3f;
196	fBoardDrawStyle[0]	= [[MBCDrawStyle alloc] init];
197	fBoardDrawStyle[1]	= [[MBCDrawStyle alloc] init];
198	fPieceDrawStyle[0]	= [[MBCDrawStyle alloc] init];
199	fPieceDrawStyle[1]	= [[MBCDrawStyle alloc] init];
200	fBorderDrawStyle	= [[MBCDrawStyle alloc] init];
201	fSelectedPieceDrawStyle	= [[MBCDrawStyle alloc] init];
202	fBoardAttr 			= nil;
203	fPieceAttr			= nil;
204	fBoardStyle			= nil;
205	fPieceStyle			= nil;
206	fNeedStaticModels	= true;
207
208	fElevation			=  60.0f;
209	fAzimuth			= 180.0f;
210	fInAnimation		= false;
211	fInBoardManipulation= false;
212	fPickedSquare		= kInvalidSquare;
213	fSelectedPiece		= EMPTY;
214	fSelectedDest		= kInvalidSquare;
215	fWantMouse			= false;
216	fNeedPerspective	= true;
217	fAmbient			= light_ambient;
218	fIsPickingFormat	= false;
219	fLastFSAASize		= 2000000000;
220	memcpy(fLightPos, light_pos, sizeof(fLightPos));
221	fKeyBuffer			= 0;
222
223	fHandCursor			= [[NSCursor pointingHandCursor] retain];
224	fArrowCursor		= [[NSCursor arrowCursor] retain];
225    [self updateTrackingAreas];
226
227    return self;
228}
229
230- (void)dealloc
231{
232    [fBoardAttr release];
233    [fPieceAttr release];
234    [fBoardStyle release];
235    [fPieceStyle release];
236    [fBoardDrawStyle[0] release];
237    [fBoardDrawStyle[1] release];
238    [fPieceDrawStyle[0] release];
239    [fPieceDrawStyle[1] release];
240    [fBorderDrawStyle release];
241    [fSelectedPieceDrawStyle release];
242
243    [super dealloc];
244}
245
246- (void)updateTrackingAreas
247{
248    [self removeTrackingArea:fTrackingArea];
249    [fTrackingArea release];
250    fTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
251                                                 options: (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow)
252                                                   owner:self userInfo:nil];
253    [self addTrackingArea:fTrackingArea];
254}
255
256- (void) pickPixelFormat:(BOOL)afterFailure
257{
258	if (fIsPickingFormat)
259		return; // Avoid recursive picking
260	//
261	// If we didn't fail with the current format, try whether we can become more aggressive again
262	//
263	NSRect 	bounds = [self bounds];
264	int 	curSize= bounds.size.width*bounds.size.height;
265	if (afterFailure) {
266		fLastFSAASize 		= curSize;
267	} else {
268		if (fCurFSAA == fMaxFSAA || curSize >= fLastFSAASize)
269			return; // Won't get any better
270		fCurFSAA 		= fMaxFSAA*2;
271		fLastFSAASize	= 2000000000;
272	}
273	fIsPickingFormat = true;
274    NSOpenGLPixelFormat * pixelFormat;
275	do {
276        fCurFSAA    = (fCurFSAA > 1) ? fCurFSAA/2 : 0;
277        pixelFormat = [self pixelFormatWithFSAA:fCurFSAA];
278        if (pixelFormat) {
279            [self clearGLContext];
280            [self setPixelFormat:pixelFormat];
281            [[self openGLContext] setView:self];
282        }
283	} while (fCurFSAA && (!pixelFormat || [[self openGLContext] view] != self));
284	[[self openGLContext] makeCurrentContext];
285	[self loadStyles];
286    fNeedStaticModels	= true;
287	if (fCurFSAA)
288		glEnable(GL_MULTISAMPLE);
289    else
290        glDisable(GL_MULTISAMPLE);
291	fIsPickingFormat 	= false;
292	NSLog(@"Size is now %.0fx%.0f FSAA = %d [Max %d]\n", bounds.size.width, bounds.size.height, fCurFSAA, fMaxFSAA);
293}
294
295- (void) setStyleForBoard:(NSString *)boardStyle pieces:(NSString *)pieceStyle
296{
297	[[self openGLContext] makeCurrentContext];
298	[fBoardStyle release];
299	fBoardStyle =
300		[[@"Styles" stringByAppendingPathComponent:boardStyle] retain];
301	[fPieceStyle release];
302	fPieceStyle =
303		[[@"Styles" stringByAppendingPathComponent:pieceStyle] retain];
304	[self loadStyles];
305    [self setNeedsDisplay: YES];
306}
307
308- (void)awakeFromNib
309{
310    fController     = [[self window] windowController];
311	fBoard          = [fController board];
312	fInteractive    = [fController interactive];
313}
314
315- (BOOL) isOpaque
316{
317	return NO;
318}
319
320- (BOOL) mouseDownCanMoveWindow
321{
322	return NO;
323}
324
325- (void) drawRect:(NSRect)rect
326{
327	[self drawPosition];
328}
329
330- (void) reshape
331{
332	[self pickPixelFormat:NO];
333	[self needsUpdate];
334}
335
336- (void) drawNow
337{
338	[self lockFocus];
339	[self drawPosition];
340	[self unlockFocus];
341    [self setNeedsDisplay:NO];
342}
343
344- (void) profileDraw
345{
346    dispatch_apply(100, dispatch_get_main_queue(), ^(size_t) {
347        [self drawNow];
348    });
349}
350
351- (void) needsUpdate
352{
353    fNeedPerspective	= true;
354
355    [self setNeedsDisplay: YES];
356}
357
358- (void) endGame;
359{
360	fSelectedPiece		= EMPTY;
361	fPickedSquare		= kInvalidSquare;
362	fWantMouse			= false;
363	[self hideMoves];
364	[self needsUpdate];
365}
366
367- (void) startGame:(MBCVariant)variant playing:(MBCSide)side
368{
369	fVariant 		= variant;
370	fSide	 		= side;
371	[self endGame];
372	if (side != kNeitherSide && [self facing] != kNeitherSide) {
373		//
374		// We have humans involved, turn board right side up unless
375		// it was in a neutral position
376		//
377		if (side == kBothSides)
378			side = ([fBoard numMoves]&1) ? kBlackSide : kWhiteSide;
379		if ([self facing] != side)
380			fAzimuth	= fmod(fAzimuth+180.0f, 360.0f);
381	}
382}
383
384- (void) showMoveAsHint:(MBCMove *)move
385{
386	[move retain];
387	[fHintMove autorelease];
388	fHintMove	= move;
389	[self setNeedsDisplay:YES];
390}
391
392- (void) showMoveAsLast:(MBCMove *)move
393{
394	[move retain];
395	[fLastMove autorelease];
396	fLastMove	= move;
397	[self setNeedsDisplay:YES];
398}
399
400- (void) hideMoves
401{
402	[fHintMove release];
403	[fLastMove release];
404	fHintMove	= nil;
405	fLastMove	= nil;
406}
407
408- (void) clickPiece
409{
410	fPickedSquare = fSelectedSquare;
411
412    [self unselectPiece];
413}
414
415- (void) selectPiece:(MBCPiece)piece at:(MBCSquare)square
416{
417	fPickedSquare	=   kInvalidSquare;
418	fSelectedPiece	=	piece;
419	fSelectedSquare	=   square;
420
421	if (square != kInvalidSquare)
422		fSelectedPos	= [self squareToPosition:square];
423
424	[self setNeedsDisplay:YES];
425}
426
427- (void) selectPiece:(MBCPiece)piece at:(MBCSquare)square to:(MBCSquare)dest
428{
429	fSelectedDest	= dest;
430
431	[self selectPiece:piece at:square];
432}
433
434- (void) moveSelectionTo:(MBCPosition *)position
435{
436	fSelectedPos	= *position;
437
438	[self setNeedsDisplay:YES];
439}
440
441- (void) unselectPiece
442{
443	fSelectedPiece	= EMPTY;
444	fSelectedSquare = kInvalidSquare;
445	fSelectedDest 	= kInvalidSquare;
446
447	[self setNeedsDisplay:YES];
448}
449
450- (MBCSquare) 	positionToSquare:(const MBCPosition *)position
451{
452	GLfloat	px	= (*position)[0];
453	GLfloat pz	= (*position)[2];
454	GLfloat pxa = fabs(px);
455	GLfloat pza = fabs(pz);
456
457	if (fabs(px - kInHandPieceX) < (kInHandPieceSize/2.0f)) {
458		pza -= kInHandPieceZOffset;
459		if (pza > 0.0f && pza < kInHandPieceSize*5.0f) {
460			MBCPieceCode piece = gInHandOrder[(int)(pza / kInHandPieceSize)];
461			return kInHandSquare+(pz < 0 ? Black(piece) : White(piece));
462		} else
463			return kInvalidSquare;
464	}
465	if (pxa > kBoardRadius || pza > kBoardRadius)
466		return kInvalidSquare;
467
468	int row	= static_cast<int>((kBoardRadius-pz)/10.0f);
469	int col = static_cast<int>((px+kBoardRadius)/10.0f);
470
471	return (row<<3)|col;
472}
473
474- (MBCSquare) positionToSquareOrRegion:(const MBCPosition *)position
475{
476	GLfloat	px	= (*position)[0];
477	GLfloat pz	= (*position)[2];
478	GLfloat pxa = fabs(px);
479	GLfloat pza = fabs(pz);
480
481	if (fPromotionSide == kWhiteSide) {
482		if (fabs(px + kPromotionPieceX) < 8.0f
483		 && fabs(pz + kPromotionPieceZ) < 8.0f
484		)
485			return kWhitePromoSquare;
486	} else if (fPromotionSide == kBlackSide) {
487		if (fabs(px - kPromotionPieceX) < 8.0f
488		 && fabs(pz - kPromotionPieceZ) < 8.0f
489		)
490			return kBlackPromoSquare;
491	}
492
493	if (fabs(px - kInHandPieceX) < (kInHandPieceSize/2.0f)) {
494		pza -= kInHandPieceZOffset;
495		if (pza > 0.0f && pza < kInHandPieceSize*5.0f) {
496			MBCPieceCode piece = gInHandOrder[(int)(pza / kInHandPieceSize)];
497			return kInHandSquare+(pz < 0 ? Black(piece) : White(piece));
498		} else
499			return kInvalidSquare;
500	}
501	if (pxa > kBoardRadius || pza > kBoardRadius)
502		if (pxa < kBoardRadius+kBorderWidth+.1f
503		 && pza < kBoardRadius+kBorderWidth+.1f)
504			return kBorderRegion;
505		else
506			return kInvalidSquare;
507
508	int row	= static_cast<int>((kBoardRadius-pz)/10.0f);
509	int col = static_cast<int>((px+kBoardRadius)/10.0f);
510
511	return (row<<3)|col;
512}
513
514- (void) snapToSquare:(MBCPosition *)position
515{
516#if 0
517	GLfloat	&	px	= (*position)[0];
518	GLfloat	&	py	= (*position)[1];
519	GLfloat & 	pz	= (*position)[2];
520	GLfloat 	pxa = fabs(px);
521	GLfloat 	pza = fabs(pz);
522
523	py 		= 0.0f;
524	if (pxa < kBoardRadius && pza < kBoardRadius) {
525		const float kRes	= 10.0f;
526		const float kRes2	= kRes / 2.0f;
527		//
528		// Within board, snap to square
529		//
530		px = copysignf(floorf(pxa/kRes)*kRes+kRes2, px);
531		pz = copysignf(floorf(pza/kRes)*kRes+kRes2, pz);
532	}
533#else
534	(*position)[1] = 0.0f;
535#endif
536}
537
538- (MBCPosition)	squareToPosition:(MBCSquare)square
539{
540	MBCPosition	pos;
541
542	if (square > kInHandSquare) {
543		pos[0] = 44.0f;
544		pos[1] = 0.0f;
545		pos[2] = Color(square-kInHandSquare) == kBlackPiece ? -20.0f : 20.0f;
546	} else {
547		pos[0] = (square&7)*10.0f-35.0f;
548		pos[1] = 0.0f;
549		pos[2] = 35.0f - (square>>3)*10.0f;
550	}
551
552	return pos;
553}
554
555- (BOOL) facingWhite
556{
557	return fAzimuth > 90.0f && fAzimuth <= 270.0f;
558}
559
560- (MBCSide) facing
561{
562	if (fAzimuth > 95.0f && fAzimuth < 265.0f)
563		return kWhiteSide;
564	else if (fAzimuth < 85.0f || fAzimuth > 275.0f)
565		return kBlackSide;
566	else
567		return kNeitherSide;
568}
569
570- (void) wantMouse:(BOOL)wantIt
571{
572	fWantMouse	= wantIt;
573}
574
575- (void) startAnimation
576{
577	fInAnimation	= true;
578}
579
580- (void) animationDone
581{
582	fInAnimation	= false;
583}
584
585- (IBAction)increaseFSAA:(id)sender
586{
587    fMaxFSAA = fMaxFSAA ? fMaxFSAA * 2 : 2;
588    [self pickPixelFormat:NO];
589    fMaxFSAA = fCurFSAA;
590	[self needsUpdate];
591}
592
593- (IBAction)decreaseFSAA:(id)sender
594{
595    fMaxFSAA = fMaxFSAA > 2 ? fMaxFSAA / 2 : 0;
596    [self pickPixelFormat:NO];
597	[self needsUpdate];
598}
599
600@end
601
602// Local Variables:
603// mode:ObjC
604// End:
605