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