* Working on 3D rotation of game board.
authorJens Alfke <jens@mooseyard.com>
Thu Jul 31 11:18:13 2008 -0700 (2008-07-31)
changeset 224cb50131788f
parent 21 2eb229411d73
child 23 efe5d4523a23
* Working on 3D rotation of game board.
* Added some colored balls ("drawn" myself using Photoshop glass effect.)
Resources/Balls/ball-black.png
Resources/Balls/ball-cyan.png
Resources/Balls/ball-gray.png
Resources/Balls/ball-green.png
Resources/Balls/ball-orange.png
Resources/Balls/ball-purple.png
Resources/Balls/ball-red.png
Resources/Balls/ball-white.png
Source/Bit.h
Source/Bit.m
Source/BoardView.h
Source/BoardView.m
Source/CheckersGame.h
Source/CheckersGame.m
Source/GGBLayer.h
Source/GGBLayer.m
Source/Game+Protected.h
Source/Game.h
Source/Game.m
Source/GoGame.m
Source/Grid.h
Source/Grid.m
Source/HexchequerGame.m
     1.1 Binary file Resources/Balls/ball-black.png has changed
     2.1 Binary file Resources/Balls/ball-cyan.png has changed
     3.1 Binary file Resources/Balls/ball-gray.png has changed
     4.1 Binary file Resources/Balls/ball-green.png has changed
     5.1 Binary file Resources/Balls/ball-orange.png has changed
     6.1 Binary file Resources/Balls/ball-purple.png has changed
     7.1 Binary file Resources/Balls/ball-red.png has changed
     8.1 Binary file Resources/Balls/ball-white.png has changed
     9.1 --- a/Source/Bit.h	Mon Jul 21 17:32:21 2008 -0700
     9.2 +++ b/Source/Bit.h	Thu Jul 31 11:18:13 2008 -0700
     9.3 @@ -33,7 +33,7 @@
     9.4      kCardZ  = 2,
     9.5      kPieceZ = 3,
     9.6      
     9.7 -    kPickedUpZ = 100
     9.8 +    kPickedUpZ = 20
     9.9  };
    9.10  
    9.11  
    9.12 @@ -43,6 +43,7 @@
    9.13  {
    9.14      @private
    9.15      int _restingZ;      // Original z position, saved while pickedUp
    9.16 +    CATransform3D _restingTransform;
    9.17  #if !TARGET_OS_IPHONE
    9.18      float _restingShadowOpacity, _restingShadowRadius;
    9.19      CGSize _restingShadowOffset;
    10.1 --- a/Source/Bit.m	Mon Jul 21 17:32:21 2008 -0700
    10.2 +++ b/Source/Bit.m	Thu Jul 31 11:18:13 2008 -0700
    10.3 @@ -79,6 +79,12 @@
    10.4          forKeyPath: @"transform.scale"];
    10.5  }
    10.6  
    10.7 +- (void) scaleBy: (CGFloat)scale
    10.8 +{
    10.9 +    self.transform = CATransform3DConcat(self.transform,
   10.10 +                                         CATransform3DMakeScale(scale, scale, scale));
   10.11 +}
   10.12 +
   10.13  
   10.14  - (int) rotation
   10.15  {
   10.16 @@ -101,16 +107,19 @@
   10.17  - (void) setPickedUp: (BOOL)up
   10.18  {
   10.19      if( up != _pickedUp ) {
   10.20 -        CGFloat shadow, radius, opacity, z, scale;
   10.21 +        CGFloat shadow, radius, opacity, z;
   10.22          CGSize offset;
   10.23 +        CATransform3D transform;
   10.24 +        
   10.25          if( up ) {
   10.26              shadow = 0.8;
   10.27              offset = CGSizeMake(2,2);
   10.28              radius = 8;
   10.29              opacity = kPickedUpOpacity;
   10.30 -            scale = kPickedUpScale;
   10.31              z = kPickedUpZ;
   10.32              _restingZ = self.zPosition;
   10.33 +            _restingTransform = self.transform;
   10.34 +            transform = CATransform3DScale(_restingTransform, kPickedUpScale, kPickedUpScale, kPickedUpScale);
   10.35  #if !TARGET_OS_IPHONE
   10.36              _restingShadowOpacity = self.shadowOpacity;
   10.37              _restingShadowOffset  = self.shadowOffset;
   10.38 @@ -123,7 +132,7 @@
   10.39              radius = _restingShadowRadius;
   10.40  #endif
   10.41              opacity = 1;
   10.42 -            scale = 1.0/kPickedUpScale;
   10.43 +            transform = _restingTransform;
   10.44              z = _restingZ;
   10.45          }
   10.46  
   10.47 @@ -134,7 +143,7 @@
   10.48          self.shadowRadius = radius;
   10.49  #endif
   10.50          self.opacity = opacity;
   10.51 -        self.scale *= scale;
   10.52 +        self.transform = transform;
   10.53          _pickedUp = up;
   10.54      }
   10.55  }
    11.1 --- a/Source/BoardView.h	Mon Jul 21 17:32:21 2008 -0700
    11.2 +++ b/Source/BoardView.h	Thu Jul 31 11:18:13 2008 -0700
    11.3 @@ -33,6 +33,8 @@
    11.4      Game *_game;                                // Current Game
    11.5      GGBLayer *_table;                           // Game's root layer
    11.6      NSSize _oldSize;
    11.7 +    CGSize _gameBoardInset;
    11.8 +    CGFloat _perspective;
    11.9      
   11.10      // Used during mouse-down tracking:
   11.11      NSPoint _dragStartPos;                      // Starting position of mouseDown
   11.12 @@ -57,8 +59,11 @@
   11.13  
   11.14  - (void) createGameBoard;
   11.15  
   11.16 +@property CGFloat perspective;
   11.17 +
   11.18  - (IBAction) enterFullScreen: (id)sender;
   11.19  
   11.20 +@property CGSize gameBoardInset;
   11.21  - (CGRect) gameBoardFrame;
   11.22  
   11.23  @end
    12.1 --- a/Source/BoardView.m	Mon Jul 21 17:32:21 2008 -0700
    12.2 +++ b/Source/BoardView.m	Thu Jul 31 11:18:13 2008 -0700
    12.3 @@ -23,13 +23,16 @@
    12.4  #import "BoardView.h"
    12.5  #import "Bit.h"
    12.6  #import "BitHolder.h"
    12.7 -#import "Game.h"
    12.8 +#import "Game+Protected.h"
    12.9  #import "Turn.h"
   12.10  #import "Player.h"
   12.11  #import "QuartzUtils.h"
   12.12  #import "GGBUtils.h"
   12.13  
   12.14  
   12.15 +#define kMaxPerspective 0.965   // 55 degrees
   12.16 +
   12.17 +
   12.18  @interface BoardView ()
   12.19  - (void) _findDropTarget: (NSPoint)pos;
   12.20  @end
   12.21 @@ -38,7 +41,7 @@
   12.22  @implementation BoardView
   12.23  
   12.24  
   12.25 -@synthesize table=_table;
   12.26 +@synthesize table=_table, gameBoardInset=_gameBoardInset;
   12.27  
   12.28  
   12.29  - (void) dealloc
   12.30 @@ -48,6 +51,41 @@
   12.31  }
   12.32  
   12.33  
   12.34 +- (void) _applyPerspective
   12.35 +{
   12.36 +    CATransform3D t;
   12.37 +    if( fabs(_perspective) >= M_PI/180 ) {
   12.38 +        CGSize size = self.layer.bounds.size;
   12.39 +        t = CATransform3DMakeTranslation(-size.width/2, -size.height/4, 0);
   12.40 +        t = CATransform3DConcat(t, CATransform3DMakeRotation(-_perspective, 1,0,0));
   12.41 +        
   12.42 +        CATransform3D pers = CATransform3DIdentity;
   12.43 +        pers.m34 = 1.0/-800;
   12.44 +        t = CATransform3DConcat(t, pers);
   12.45 +        t = CATransform3DConcat(t, CATransform3DMakeTranslation(size.width/2, 
   12.46 +                                                                size.height*(0.25 + 0.05*sin(2*_perspective)),
   12.47 +                                                                0));
   12.48 +        self.layer.borderWidth = 3;
   12.49 +    } else {
   12.50 +        t = CATransform3DIdentity;
   12.51 +        self.layer.borderWidth = 0;
   12.52 +    }
   12.53 +    self.layer.transform = t;
   12.54 +}    
   12.55 +
   12.56 +- (CGFloat) perspective {return _perspective;}
   12.57 +
   12.58 +- (void) setPerspective: (CGFloat)p
   12.59 +{
   12.60 +    p = MAX(0.0, MIN(kMaxPerspective, p));
   12.61 +    if( p != _perspective ) {
   12.62 +        _perspective = p;
   12.63 +        [self _applyPerspective];
   12.64 +        _game.tablePerspectiveAngle = p;
   12.65 +    }
   12.66 +}
   12.67 +
   12.68 +
   12.69  - (void) _removeGameBoard
   12.70  {
   12.71      if( _table ) {
   12.72 @@ -62,7 +100,7 @@
   12.73      _table = [[CALayer alloc] init];
   12.74      _table.frame = [self gameBoardFrame];
   12.75      _table.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin;
   12.76 -
   12.77 +    
   12.78      // Tell the game to set up the board:
   12.79      _game.table = _table;
   12.80  
   12.81 @@ -98,7 +136,7 @@
   12.82  
   12.83  - (CGRect) gameBoardFrame
   12.84  {
   12.85 -    return self.layer.bounds;
   12.86 +    return CGRectInset(self.layer.bounds, _gameBoardInset.width,_gameBoardInset.height);
   12.87  }
   12.88  
   12.89  
   12.90 @@ -115,7 +153,6 @@
   12.91      return _fullScreenView ?: self;
   12.92  }
   12.93  
   12.94 -
   12.95  - (IBAction) enterFullScreen: (id)sender
   12.96  {
   12.97      //[self _removeGameBoard];
   12.98 @@ -142,6 +179,7 @@
   12.99          CGAffineTransform xform = _table.affineTransform;
  12.100          xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
  12.101          BeginDisableAnimations();
  12.102 +        [self _applyPerspective];
  12.103          _table.affineTransform = xform;
  12.104          EndDisableAnimations();
  12.105      } else
  12.106 @@ -181,7 +219,9 @@
  12.107  - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
  12.108  {
  12.109      NSPoint where = [self convertPoint: locationInWindow fromView: nil];    // convert to view coords
  12.110 -    return NSPointToCGPoint( [self convertPointToBase: where] );            // then to layer coords
  12.111 +    where = [self convertPointToBase: where];                               // then to layer base coords
  12.112 +    return [self.layer convertPoint: NSPointToCGPoint(where)                // then to transformed layer coords
  12.113 +                          fromLayer: self.layer.superlayer];
  12.114  }
  12.115  
  12.116  
  12.117 @@ -387,6 +427,13 @@
  12.118  }
  12.119  
  12.120  
  12.121 +- (void)scrollWheel:(NSEvent *)e
  12.122 +{
  12.123 +    self.perspective += e.deltaY * M_PI/180;
  12.124 +    //Log(@"Perspective = %2.0f degrees (%5.3f radians)", self.perspective*180/M_PI, self.perspective);
  12.125 +}
  12.126 +
  12.127 +
  12.128  #pragma mark -
  12.129  #pragma mark INCOMING DRAGS:
  12.130  
    13.1 --- a/Source/CheckersGame.h	Mon Jul 21 17:32:21 2008 -0700
    13.2 +++ b/Source/CheckersGame.h	Thu Jul 31 11:18:13 2008 -0700
    13.3 @@ -32,6 +32,7 @@
    13.4  }
    13.5  
    13.6  //protected
    13.7 +- (Piece*) pieceForPlayer: (int)playerNum;
    13.8  - (BOOL) canOpponentMoveFrom: (GridCell*)src;
    13.9  
   13.10  @end
    14.1 --- a/Source/CheckersGame.m	Mon Jul 21 17:32:21 2008 -0700
    14.2 +++ b/Source/CheckersGame.m	Thu Jul 31 11:18:13 2008 -0700
    14.3 @@ -27,6 +27,9 @@
    14.4  #import "GGBUtils.h"
    14.5  
    14.6  
    14.7 +#define kKingScale 1.4
    14.8 +
    14.9 +
   14.10  @implementation CheckersGame
   14.11  
   14.12  
   14.13 @@ -63,6 +66,13 @@
   14.14      return GetCGImageNamed( playerNum==0 ?@"Green.png" :@"Red.png" );
   14.15  }
   14.16  
   14.17 +- (void) _transformPiece: (Piece*)piece
   14.18 +{
   14.19 +    CGFloat scale = piece.tag ?kKingScale :1.0;
   14.20 +    piece.transform = CATransform3DMakeScale(scale, scale/cos(self.tablePerspectiveAngle), scale);
   14.21 +    piece.anchorPoint = CGPointMake(0.5, 0.5*cos(self.tablePerspectiveAngle));
   14.22 +}
   14.23 +
   14.24  - (Piece*) pieceForPlayer: (int)playerNum
   14.25  {
   14.26      Piece *p = [[Piece alloc] init];
   14.27 @@ -70,12 +80,22 @@
   14.28      p.style = (playerNum ?kPieceStyle2 :kPieceStyle1);
   14.29      p.owner = [self.players objectAtIndex: playerNum];
   14.30      p.name = playerNum ?@"2" :@"1";
   14.31 +    [self _transformPiece: p];
   14.32      return [p autorelease];
   14.33  }
   14.34  
   14.35 +- (void) perspectiveChanged
   14.36 +{
   14.37 +    for( GridCell *cell in _board.cells ) {
   14.38 +        Piece *piece = (Piece*) cell.bit;
   14.39 +        if( piece )
   14.40 +            [self _transformPiece: piece];
   14.41 +    }
   14.42 +}
   14.43 +
   14.44  - (void) makeKing: (Piece*)piece
   14.45  {
   14.46 -    piece.scale = 1.4;
   14.47 +    piece.scale = kKingScale;
   14.48      piece.tag = YES;        // tag property stores the 'king' flag
   14.49      piece.name = piece.owner.index ?@"4" :@"3";
   14.50  }
    15.1 --- a/Source/GGBLayer.h	Mon Jul 21 17:32:21 2008 -0700
    15.2 +++ b/Source/GGBLayer.h	Thu Jul 31 11:18:13 2008 -0700
    15.3 @@ -43,6 +43,17 @@
    15.4      and update every other layer that shares the same style dictionary. */
    15.5  - (void) setValue: (id)value ofStyleProperty: (NSString*)prop;
    15.6  
    15.7 +/** Send a message to all sublayers in my tree */
    15.8 +- (void) makeSublayersPerformSelector: (SEL)selector withObject: (id)object;
    15.9 +
   15.10 +@property (readonly) CATransform3D aggregateTransform;
   15.11 +
   15.12 +/** Call this to notify all sublayers that their aggregate transform has changed. */
   15.13 +- (void) changedTransform;
   15.14 +
   15.15 +/** Called to notify that a superlayer's transform has changed. */
   15.16 +- (void) aggregateTransformChanged;
   15.17 +
   15.18  @end
   15.19  
   15.20  
   15.21 @@ -57,3 +68,5 @@
   15.22  void EndDisableAnimations(void);
   15.23  
   15.24  CGColorRef GetEffectiveBackground( CALayer *layer );
   15.25 +
   15.26 +NSString* StringFromTransform3D( CATransform3D xform );
    16.1 --- a/Source/GGBLayer.m	Mon Jul 21 17:32:21 2008 -0700
    16.2 +++ b/Source/GGBLayer.m	Thu Jul 31 11:18:13 2008 -0700
    16.3 @@ -124,7 +124,24 @@
    16.4  }
    16.5  
    16.6  
    16.7 -#if 0
    16.8 +- (void) makeSublayersPerformSelector: (SEL)selector withObject: (id)object
    16.9 +{
   16.10 +    for( GGBLayer *layer in self.sublayers ) {
   16.11 +        [layer performSelector: selector withObject: object withObject: nil];
   16.12 +        [layer makeSublayersPerformSelector: selector withObject: object];
   16.13 +    }
   16.14 +}
   16.15 +
   16.16 +- (void) changedTransform
   16.17 +{
   16.18 +    [self makeSublayersPerformSelector: @selector(aggregateTransformChanged) withObject: nil];
   16.19 +}
   16.20 +
   16.21 +- (void) aggregateTransformChanged
   16.22 +{
   16.23 +}
   16.24 +
   16.25 +
   16.26  - (CATransform3D) aggregateTransform
   16.27  {
   16.28      CATransform3D xform = CATransform3DIdentity;
   16.29 @@ -147,7 +164,6 @@
   16.30      }
   16.31      return str;
   16.32  }
   16.33 -#endif
   16.34  
   16.35  
   16.36  #if TARGET_OS_IPHONE
    17.1 --- a/Source/Game+Protected.h	Mon Jul 21 17:32:21 2008 -0700
    17.2 +++ b/Source/Game+Protected.h	Thu Jul 31 11:18:13 2008 -0700
    17.3 @@ -25,11 +25,14 @@
    17.4  
    17.5  #pragma mark  Abstract methods for subclasses to implement:
    17.6  
    17.7 -/** Called by -setBoard: Should all all necessary Grids/Pieces/Cards/etc. to _board.
    17.8 +/** Called by -setTable: Should all all necessary Grids/Pieces/Cards/etc. to _table.
    17.9      This method is always called during initialization of a new Game, and may be called
   17.10 -    again afterwards, for example if the board area is resized. */
   17.11 +    again afterwards, for example if the table area is resized. */
   17.12  - (void) setUpBoard;
   17.13  
   17.14 +/** Called after the tablePerspectiveAngle property changes. */
   17.15 +- (void) perspectiveChanged;
   17.16 +
   17.17  /** Should return the winning player, if the current position is a win, else nil.
   17.18      Default implementation returns nil. */
   17.19  - (Player*) checkForWinner;
   17.20 @@ -40,6 +43,10 @@
   17.21  /** Sets the number of players in the game. Subclass initializers should call this. */
   17.22  - (void) setNumberOfPlayers: (unsigned)n;
   17.23  
   17.24 +/** The angle by which the table is tilted "away from" the viewer to give 3D perspective.
   17.25 +    Subclasses should not change this! It won't do anything. */
   17.26 +@property CGFloat tablePerspectiveAngle;
   17.27 +
   17.28  /** Animate a piece moving from src to dst. Used in implementing -applyMoveString:. */
   17.29  - (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst;
   17.30  
    18.1 --- a/Source/Game.h	Mon Jul 21 17:32:21 2008 -0700
    18.2 +++ b/Source/Game.h	Thu Jul 31 11:18:13 2008 -0700
    18.3 @@ -36,6 +36,7 @@
    18.4      unsigned _currentTurnNo;
    18.5      NSMutableDictionary *_extraValues;
    18.6      BOOL _requireConfirmation;
    18.7 +    CGFloat _tablePerspectiveAngle;
    18.8  }
    18.9  
   18.10  #pragma mark  Class properties:
    19.1 --- a/Source/Game.m	Mon Jul 21 17:32:21 2008 -0700
    19.2 +++ b/Source/Game.m	Thu Jul 31 11:18:13 2008 -0700
    19.3 @@ -143,6 +143,23 @@
    19.4      }
    19.5  }
    19.6  
    19.7 +- (CGFloat) tablePerspectiveAngle
    19.8 +{
    19.9 +    return _tablePerspectiveAngle;
   19.10 +}
   19.11 +
   19.12 +- (void) setTablePerspectiveAngle: (CGFloat)angle
   19.13 +{
   19.14 +    if( angle != _tablePerspectiveAngle ) {
   19.15 +        _tablePerspectiveAngle = angle;
   19.16 +        [self perspectiveChanged];
   19.17 +    }
   19.18 +}
   19.19 +
   19.20 +- (void) perspectiveChanged
   19.21 +{
   19.22 +}
   19.23 +
   19.24  
   19.25  #pragma mark -
   19.26  #pragma mark PLAYERS:
    20.1 --- a/Source/GoGame.m	Mon Jul 21 17:32:21 2008 -0700
    20.2 +++ b/Source/GoGame.m	Thu Jul 31 11:18:13 2008 -0700
    20.3 @@ -106,13 +106,13 @@
    20.4  
    20.5  - (CGImageRef) iconForPlayer: (int)playerNum
    20.6  {
    20.7 -    return GetCGImageNamed( playerNum ?@"Stone-white.png" :@"Stone-black.png" );
    20.8 +    return GetCGImageNamed( playerNum ?@"ball-white.png" :@"ball-black.png" );
    20.9  }
   20.10  
   20.11  - (Piece*) pieceForPlayer: (int)index
   20.12  {
   20.13 -    NSString *imageName = index ?@"Stone-white.png" :@"Stone-black.png";
   20.14 -    CGFloat pieceSize = (int)(_board.spacing.width * 1.8) & ~1;  // make sure it's even
   20.15 +    NSString *imageName = index ?@"ball-white.png" :@"ball-black.png";
   20.16 +    CGFloat pieceSize = (int)(_board.spacing.width * 1.0) & ~1;  // make sure it's even
   20.17      Piece *stone = [[Piece alloc] initWithImageNamed: imageName scale: pieceSize];
   20.18      stone.owner = [self.players objectAtIndex: index];
   20.19      return [stone autorelease];
    21.1 --- a/Source/Grid.h	Mon Jul 21 17:32:21 2008 -0700
    21.2 +++ b/Source/Grid.h	Thu Jul 31 11:18:13 2008 -0700
    21.3 @@ -34,6 +34,7 @@
    21.4      CGImageRef _backgroundImage;
    21.5      BOOL _usesDiagonals, _allowsMoves, _allowsCaptures, _reversed;
    21.6      NSMutableArray *_cells;                             // Really a 2D array, in row-major order.
    21.7 +    CATransform3D _bitTransform;
    21.8  }
    21.9  
   21.10  /** Initializes a new Grid with the given dimensions and cell size, and position in superview.
   21.11 @@ -57,6 +58,9 @@
   21.12  @property BOOL allowsMoves, allowsCaptures;     // Can pieces be moved, and can they land on others?
   21.13  @property BOOL reversed;                        // Reverses board (rotates 180°) by exchanging cell positions
   21.14  
   21.15 +@property CATransform3D bitTransform;
   21.16 +- (void) updateCellTransform;
   21.17 +
   21.18  @property (readonly) NSArray *cells;
   21.19  
   21.20  /** Returns the GridCell at the given coordinates, or nil if there is no cell there.
    22.1 --- a/Source/Grid.m	Mon Jul 21 17:32:21 2008 -0700
    22.2 +++ b/Source/Grid.m	Thu Jul 31 11:18:13 2008 -0700
    22.3 @@ -28,6 +28,11 @@
    22.4  #import "QuartzUtils.h"
    22.5  
    22.6  
    22.7 +@interface GridCell ()
    22.8 +- (void) setBitTransform: (CATransform3D)bitTransform;
    22.9 +@end
   22.10 +
   22.11 +
   22.12  @implementation Grid
   22.13  
   22.14  
   22.15 @@ -45,6 +50,7 @@
   22.16          self.lineColor = kBlackColor;
   22.17          _allowsMoves = YES;
   22.18          _usesDiagonals = YES;
   22.19 +        _bitTransform = CATransform3DIdentity;
   22.20  
   22.21          self.bounds = CGRectMake(-1, -1, nColumns*spacing.width+2, nRows*spacing.height+2);
   22.22          self.position = pos;
   22.23 @@ -113,7 +119,8 @@
   22.24  }
   22.25  
   22.26  @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing, reversed=_reversed,
   22.27 -            usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures;
   22.28 +            usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures,
   22.29 +            bitTransform=_bitTransform;
   22.30  
   22.31  
   22.32  #pragma mark -
   22.33 @@ -160,7 +167,8 @@
   22.34          cell = [self createCellAtRow: row column: col suggestedFrame: frame];
   22.35          if( cell ) {
   22.36              [_cells replaceObjectAtIndex: index withObject: cell];
   22.37 -            [self addSublayer: cell];
   22.38 +            //[self addSublayer: cell];
   22.39 +            [self insertSublayer: cell atIndex: 0];
   22.40              [self setNeedsDisplay];
   22.41          }
   22.42      }
   22.43 @@ -221,6 +229,26 @@
   22.44  }
   22.45  
   22.46  
   22.47 +- (CATransform3D) bitTransform
   22.48 +{
   22.49 +    return _bitTransform;
   22.50 +}
   22.51 +
   22.52 +- (void) setBitTransform: (CATransform3D)t
   22.53 +{
   22.54 +    _bitTransform = t;
   22.55 +    for( GridCell *cell in self.cells )
   22.56 +        [cell setBitTransform: t];
   22.57 +}
   22.58 +
   22.59 +- (void) updateCellTransform
   22.60 +{
   22.61 +    CATransform3D t = self.aggregateTransform;
   22.62 +    t.m41 = t.m42 = t.m43 = 0.0f;           // remove translation component
   22.63 +    t = CATransform3DInvert(t);
   22.64 +    self.bitTransform = t;
   22.65 +}
   22.66 +
   22.67  
   22.68  #pragma mark -
   22.69  #pragma mark GAME STATE:
   22.70 @@ -332,13 +360,14 @@
   22.71          _grid = grid;
   22.72          _row = row;
   22.73          _column = col;
   22.74 +        self.anchorPoint = CGPointMake(0,0);
   22.75          self.position = frame.origin;
   22.76          CGRect bounds = frame;
   22.77          bounds.origin.x -= floor(bounds.origin.x);  // make sure my coords fall on pixel boundaries
   22.78          bounds.origin.y -= floor(bounds.origin.y);
   22.79          self.bounds = bounds;
   22.80 -        self.anchorPoint = CGPointMake(0,0);
   22.81          self.borderColor = kHighlightColor;         // Used when highlighting (see -setHighlighted:)
   22.82 +        [self setBitTransform: grid.bitTransform];
   22.83      }
   22.84      return self;
   22.85  }
   22.86 @@ -351,6 +380,18 @@
   22.87  @synthesize grid=_grid, row=_row, column=_column;
   22.88  
   22.89  
   22.90 +- (void) setBitTransform: (CATransform3D)bitTransform
   22.91 +{
   22.92 +    // To make the bitTransform relative to my center, I need to offset the center to the origin
   22.93 +    // first, and then back afterwards.
   22.94 +    CGSize size = self.bounds.size;
   22.95 +    CATransform3D x = CATransform3DMakeTranslation(-size.width/2, -size.height/2,0);
   22.96 +    x = CATransform3DConcat(x, bitTransform);
   22.97 +    x = CATransform3DConcat(x, CATransform3DMakeTranslation(size.width/2, size.height/2,0));
   22.98 +    self.sublayerTransform = x;    
   22.99 +}
  22.100 +
  22.101 +
  22.102  - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
  22.103  {
  22.104      // Default implementation just fills or outlines the cell.
  22.105 @@ -366,12 +407,8 @@
  22.106  {
  22.107      if( bit != self.bit ) {
  22.108          [super setBit: bit];
  22.109 -        if( bit ) {
  22.110 -            // Center it:
  22.111 -            CGSize size = self.bounds.size;
  22.112 -            bit.position = CGPointMake(floor(size.width/2.0),
  22.113 -                                       floor(size.height/2.0));
  22.114 -        }
  22.115 +        if( bit )
  22.116 +            bit.position = GetCGRectCenter(self.bounds);
  22.117      }
  22.118  }
  22.119  
    23.1 --- a/Source/HexchequerGame.m	Mon Jul 21 17:32:21 2008 -0700
    23.2 +++ b/Source/HexchequerGame.m	Thu Jul 31 11:18:13 2008 -0700
    23.3 @@ -39,9 +39,11 @@
    23.4                                             spacing: CGSizeMake(s,s)
    23.5                                            position: GetCGRectCenter(tableBounds)];
    23.6      board.anchorPoint = CGPointMake(0.47,0.5);  // Missing half-cells on right edge perturb center pt
    23.7 -    [board setValue: [NSNumber numberWithDouble: M_PI/6] forKeyPath: @"transform.rotation"];
    23.8 +    board.transform = CATransform3DMakeRotation(M_PI/6, 0,0,1);
    23.9 +    board.bitTransform = CATransform3DMakeRotation(-M_PI/6, 0,0,1);    // counteract board rotation
   23.10      _board = board;
   23.11      [_table addSublayer: _board];
   23.12 +
   23.13      board.allowsMoves = YES;
   23.14      board.allowsCaptures = NO;      // no land-on captures, that is
   23.15      board.cellColor = CreateGray(1.0, 0.25);