# HG changeset patch # User Jens Alfke # Date 1217528293 25200 # Node ID 4cb50131788fdf5c6d1a370ddbac234a538abf1b # Parent 2eb229411d73787d875310d47de07f03d4edb0cb * Working on 3D rotation of game board. * Added some colored balls ("drawn" myself using Photoshop glass effect.) diff -r 2eb229411d73 -r 4cb50131788f Resources/Balls/ball-black.png Binary file Resources/Balls/ball-black.png has changed diff -r 2eb229411d73 -r 4cb50131788f Resources/Balls/ball-cyan.png Binary file Resources/Balls/ball-cyan.png has changed diff -r 2eb229411d73 -r 4cb50131788f Resources/Balls/ball-gray.png Binary file Resources/Balls/ball-gray.png has changed diff -r 2eb229411d73 -r 4cb50131788f Resources/Balls/ball-green.png Binary file Resources/Balls/ball-green.png has changed diff -r 2eb229411d73 -r 4cb50131788f Resources/Balls/ball-orange.png Binary file Resources/Balls/ball-orange.png has changed diff -r 2eb229411d73 -r 4cb50131788f Resources/Balls/ball-purple.png Binary file Resources/Balls/ball-purple.png has changed diff -r 2eb229411d73 -r 4cb50131788f Resources/Balls/ball-red.png Binary file Resources/Balls/ball-red.png has changed diff -r 2eb229411d73 -r 4cb50131788f Resources/Balls/ball-white.png Binary file Resources/Balls/ball-white.png has changed diff -r 2eb229411d73 -r 4cb50131788f Source/Bit.h --- a/Source/Bit.h Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/Bit.h Thu Jul 31 11:18:13 2008 -0700 @@ -33,7 +33,7 @@ kCardZ = 2, kPieceZ = 3, - kPickedUpZ = 100 + kPickedUpZ = 20 }; @@ -43,6 +43,7 @@ { @private int _restingZ; // Original z position, saved while pickedUp + CATransform3D _restingTransform; #if !TARGET_OS_IPHONE float _restingShadowOpacity, _restingShadowRadius; CGSize _restingShadowOffset; diff -r 2eb229411d73 -r 4cb50131788f Source/Bit.m --- a/Source/Bit.m Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/Bit.m Thu Jul 31 11:18:13 2008 -0700 @@ -79,6 +79,12 @@ forKeyPath: @"transform.scale"]; } +- (void) scaleBy: (CGFloat)scale +{ + self.transform = CATransform3DConcat(self.transform, + CATransform3DMakeScale(scale, scale, scale)); +} + - (int) rotation { @@ -101,16 +107,19 @@ - (void) setPickedUp: (BOOL)up { if( up != _pickedUp ) { - CGFloat shadow, radius, opacity, z, scale; + CGFloat shadow, radius, opacity, z; CGSize offset; + CATransform3D transform; + if( up ) { shadow = 0.8; offset = CGSizeMake(2,2); radius = 8; opacity = kPickedUpOpacity; - scale = kPickedUpScale; z = kPickedUpZ; _restingZ = self.zPosition; + _restingTransform = self.transform; + transform = CATransform3DScale(_restingTransform, kPickedUpScale, kPickedUpScale, kPickedUpScale); #if !TARGET_OS_IPHONE _restingShadowOpacity = self.shadowOpacity; _restingShadowOffset = self.shadowOffset; @@ -123,7 +132,7 @@ radius = _restingShadowRadius; #endif opacity = 1; - scale = 1.0/kPickedUpScale; + transform = _restingTransform; z = _restingZ; } @@ -134,7 +143,7 @@ self.shadowRadius = radius; #endif self.opacity = opacity; - self.scale *= scale; + self.transform = transform; _pickedUp = up; } } diff -r 2eb229411d73 -r 4cb50131788f Source/BoardView.h --- a/Source/BoardView.h Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/BoardView.h Thu Jul 31 11:18:13 2008 -0700 @@ -33,6 +33,8 @@ Game *_game; // Current Game GGBLayer *_table; // Game's root layer NSSize _oldSize; + CGSize _gameBoardInset; + CGFloat _perspective; // Used during mouse-down tracking: NSPoint _dragStartPos; // Starting position of mouseDown @@ -57,8 +59,11 @@ - (void) createGameBoard; +@property CGFloat perspective; + - (IBAction) enterFullScreen: (id)sender; +@property CGSize gameBoardInset; - (CGRect) gameBoardFrame; @end diff -r 2eb229411d73 -r 4cb50131788f Source/BoardView.m --- a/Source/BoardView.m Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/BoardView.m Thu Jul 31 11:18:13 2008 -0700 @@ -23,13 +23,16 @@ #import "BoardView.h" #import "Bit.h" #import "BitHolder.h" -#import "Game.h" +#import "Game+Protected.h" #import "Turn.h" #import "Player.h" #import "QuartzUtils.h" #import "GGBUtils.h" +#define kMaxPerspective 0.965 // 55 degrees + + @interface BoardView () - (void) _findDropTarget: (NSPoint)pos; @end @@ -38,7 +41,7 @@ @implementation BoardView -@synthesize table=_table; +@synthesize table=_table, gameBoardInset=_gameBoardInset; - (void) dealloc @@ -48,6 +51,41 @@ } +- (void) _applyPerspective +{ + CATransform3D t; + if( fabs(_perspective) >= M_PI/180 ) { + CGSize size = self.layer.bounds.size; + t = CATransform3DMakeTranslation(-size.width/2, -size.height/4, 0); + t = CATransform3DConcat(t, CATransform3DMakeRotation(-_perspective, 1,0,0)); + + CATransform3D pers = CATransform3DIdentity; + pers.m34 = 1.0/-800; + t = CATransform3DConcat(t, pers); + t = CATransform3DConcat(t, CATransform3DMakeTranslation(size.width/2, + size.height*(0.25 + 0.05*sin(2*_perspective)), + 0)); + self.layer.borderWidth = 3; + } else { + t = CATransform3DIdentity; + self.layer.borderWidth = 0; + } + self.layer.transform = t; +} + +- (CGFloat) perspective {return _perspective;} + +- (void) setPerspective: (CGFloat)p +{ + p = MAX(0.0, MIN(kMaxPerspective, p)); + if( p != _perspective ) { + _perspective = p; + [self _applyPerspective]; + _game.tablePerspectiveAngle = p; + } +} + + - (void) _removeGameBoard { if( _table ) { @@ -62,7 +100,7 @@ _table = [[CALayer alloc] init]; _table.frame = [self gameBoardFrame]; _table.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin; - + // Tell the game to set up the board: _game.table = _table; @@ -98,7 +136,7 @@ - (CGRect) gameBoardFrame { - return self.layer.bounds; + return CGRectInset(self.layer.bounds, _gameBoardInset.width,_gameBoardInset.height); } @@ -115,7 +153,6 @@ return _fullScreenView ?: self; } - - (IBAction) enterFullScreen: (id)sender { //[self _removeGameBoard]; @@ -142,6 +179,7 @@ CGAffineTransform xform = _table.affineTransform; xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height); BeginDisableAnimations(); + [self _applyPerspective]; _table.affineTransform = xform; EndDisableAnimations(); } else @@ -181,7 +219,9 @@ - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow { NSPoint where = [self convertPoint: locationInWindow fromView: nil]; // convert to view coords - return NSPointToCGPoint( [self convertPointToBase: where] ); // then to layer coords + where = [self convertPointToBase: where]; // then to layer base coords + return [self.layer convertPoint: NSPointToCGPoint(where) // then to transformed layer coords + fromLayer: self.layer.superlayer]; } @@ -387,6 +427,13 @@ } +- (void)scrollWheel:(NSEvent *)e +{ + self.perspective += e.deltaY * M_PI/180; + //Log(@"Perspective = %2.0f degrees (%5.3f radians)", self.perspective*180/M_PI, self.perspective); +} + + #pragma mark - #pragma mark INCOMING DRAGS: diff -r 2eb229411d73 -r 4cb50131788f Source/CheckersGame.h --- a/Source/CheckersGame.h Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/CheckersGame.h Thu Jul 31 11:18:13 2008 -0700 @@ -32,6 +32,7 @@ } //protected +- (Piece*) pieceForPlayer: (int)playerNum; - (BOOL) canOpponentMoveFrom: (GridCell*)src; @end diff -r 2eb229411d73 -r 4cb50131788f Source/CheckersGame.m --- a/Source/CheckersGame.m Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/CheckersGame.m Thu Jul 31 11:18:13 2008 -0700 @@ -27,6 +27,9 @@ #import "GGBUtils.h" +#define kKingScale 1.4 + + @implementation CheckersGame @@ -63,6 +66,13 @@ return GetCGImageNamed( playerNum==0 ?@"Green.png" :@"Red.png" ); } +- (void) _transformPiece: (Piece*)piece +{ + CGFloat scale = piece.tag ?kKingScale :1.0; + piece.transform = CATransform3DMakeScale(scale, scale/cos(self.tablePerspectiveAngle), scale); + piece.anchorPoint = CGPointMake(0.5, 0.5*cos(self.tablePerspectiveAngle)); +} + - (Piece*) pieceForPlayer: (int)playerNum { Piece *p = [[Piece alloc] init]; @@ -70,12 +80,22 @@ p.style = (playerNum ?kPieceStyle2 :kPieceStyle1); p.owner = [self.players objectAtIndex: playerNum]; p.name = playerNum ?@"2" :@"1"; + [self _transformPiece: p]; return [p autorelease]; } +- (void) perspectiveChanged +{ + for( GridCell *cell in _board.cells ) { + Piece *piece = (Piece*) cell.bit; + if( piece ) + [self _transformPiece: piece]; + } +} + - (void) makeKing: (Piece*)piece { - piece.scale = 1.4; + piece.scale = kKingScale; piece.tag = YES; // tag property stores the 'king' flag piece.name = piece.owner.index ?@"4" :@"3"; } diff -r 2eb229411d73 -r 4cb50131788f Source/GGBLayer.h --- a/Source/GGBLayer.h Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/GGBLayer.h Thu Jul 31 11:18:13 2008 -0700 @@ -43,6 +43,17 @@ and update every other layer that shares the same style dictionary. */ - (void) setValue: (id)value ofStyleProperty: (NSString*)prop; +/** Send a message to all sublayers in my tree */ +- (void) makeSublayersPerformSelector: (SEL)selector withObject: (id)object; + +@property (readonly) CATransform3D aggregateTransform; + +/** Call this to notify all sublayers that their aggregate transform has changed. */ +- (void) changedTransform; + +/** Called to notify that a superlayer's transform has changed. */ +- (void) aggregateTransformChanged; + @end @@ -57,3 +68,5 @@ void EndDisableAnimations(void); CGColorRef GetEffectiveBackground( CALayer *layer ); + +NSString* StringFromTransform3D( CATransform3D xform ); diff -r 2eb229411d73 -r 4cb50131788f Source/GGBLayer.m --- a/Source/GGBLayer.m Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/GGBLayer.m Thu Jul 31 11:18:13 2008 -0700 @@ -124,7 +124,24 @@ } -#if 0 +- (void) makeSublayersPerformSelector: (SEL)selector withObject: (id)object +{ + for( GGBLayer *layer in self.sublayers ) { + [layer performSelector: selector withObject: object withObject: nil]; + [layer makeSublayersPerformSelector: selector withObject: object]; + } +} + +- (void) changedTransform +{ + [self makeSublayersPerformSelector: @selector(aggregateTransformChanged) withObject: nil]; +} + +- (void) aggregateTransformChanged +{ +} + + - (CATransform3D) aggregateTransform { CATransform3D xform = CATransform3DIdentity; @@ -147,7 +164,6 @@ } return str; } -#endif #if TARGET_OS_IPHONE diff -r 2eb229411d73 -r 4cb50131788f Source/Game+Protected.h --- a/Source/Game+Protected.h Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/Game+Protected.h Thu Jul 31 11:18:13 2008 -0700 @@ -25,11 +25,14 @@ #pragma mark Abstract methods for subclasses to implement: -/** Called by -setBoard: Should all all necessary Grids/Pieces/Cards/etc. to _board. +/** Called by -setTable: Should all all necessary Grids/Pieces/Cards/etc. to _table. This method is always called during initialization of a new Game, and may be called - again afterwards, for example if the board area is resized. */ + again afterwards, for example if the table area is resized. */ - (void) setUpBoard; +/** Called after the tablePerspectiveAngle property changes. */ +- (void) perspectiveChanged; + /** Should return the winning player, if the current position is a win, else nil. Default implementation returns nil. */ - (Player*) checkForWinner; @@ -40,6 +43,10 @@ /** Sets the number of players in the game. Subclass initializers should call this. */ - (void) setNumberOfPlayers: (unsigned)n; +/** The angle by which the table is tilted "away from" the viewer to give 3D perspective. + Subclasses should not change this! It won't do anything. */ +@property CGFloat tablePerspectiveAngle; + /** Animate a piece moving from src to dst. Used in implementing -applyMoveString:. */ - (BOOL) animateMoveFrom: (CALayer*)src to: (CALayer*)dst; diff -r 2eb229411d73 -r 4cb50131788f Source/Game.h --- a/Source/Game.h Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/Game.h Thu Jul 31 11:18:13 2008 -0700 @@ -36,6 +36,7 @@ unsigned _currentTurnNo; NSMutableDictionary *_extraValues; BOOL _requireConfirmation; + CGFloat _tablePerspectiveAngle; } #pragma mark Class properties: diff -r 2eb229411d73 -r 4cb50131788f Source/Game.m --- a/Source/Game.m Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/Game.m Thu Jul 31 11:18:13 2008 -0700 @@ -143,6 +143,23 @@ } } +- (CGFloat) tablePerspectiveAngle +{ + return _tablePerspectiveAngle; +} + +- (void) setTablePerspectiveAngle: (CGFloat)angle +{ + if( angle != _tablePerspectiveAngle ) { + _tablePerspectiveAngle = angle; + [self perspectiveChanged]; + } +} + +- (void) perspectiveChanged +{ +} + #pragma mark - #pragma mark PLAYERS: diff -r 2eb229411d73 -r 4cb50131788f Source/GoGame.m --- a/Source/GoGame.m Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/GoGame.m Thu Jul 31 11:18:13 2008 -0700 @@ -106,13 +106,13 @@ - (CGImageRef) iconForPlayer: (int)playerNum { - return GetCGImageNamed( playerNum ?@"Stone-white.png" :@"Stone-black.png" ); + return GetCGImageNamed( playerNum ?@"ball-white.png" :@"ball-black.png" ); } - (Piece*) pieceForPlayer: (int)index { - NSString *imageName = index ?@"Stone-white.png" :@"Stone-black.png"; - CGFloat pieceSize = (int)(_board.spacing.width * 1.8) & ~1; // make sure it's even + NSString *imageName = index ?@"ball-white.png" :@"ball-black.png"; + CGFloat pieceSize = (int)(_board.spacing.width * 1.0) & ~1; // make sure it's even Piece *stone = [[Piece alloc] initWithImageNamed: imageName scale: pieceSize]; stone.owner = [self.players objectAtIndex: index]; return [stone autorelease]; diff -r 2eb229411d73 -r 4cb50131788f Source/Grid.h --- a/Source/Grid.h Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/Grid.h Thu Jul 31 11:18:13 2008 -0700 @@ -34,6 +34,7 @@ CGImageRef _backgroundImage; BOOL _usesDiagonals, _allowsMoves, _allowsCaptures, _reversed; NSMutableArray *_cells; // Really a 2D array, in row-major order. + CATransform3D _bitTransform; } /** Initializes a new Grid with the given dimensions and cell size, and position in superview. @@ -57,6 +58,9 @@ @property BOOL allowsMoves, allowsCaptures; // Can pieces be moved, and can they land on others? @property BOOL reversed; // Reverses board (rotates 180°) by exchanging cell positions +@property CATransform3D bitTransform; +- (void) updateCellTransform; + @property (readonly) NSArray *cells; /** Returns the GridCell at the given coordinates, or nil if there is no cell there. diff -r 2eb229411d73 -r 4cb50131788f Source/Grid.m --- a/Source/Grid.m Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/Grid.m Thu Jul 31 11:18:13 2008 -0700 @@ -28,6 +28,11 @@ #import "QuartzUtils.h" +@interface GridCell () +- (void) setBitTransform: (CATransform3D)bitTransform; +@end + + @implementation Grid @@ -45,6 +50,7 @@ self.lineColor = kBlackColor; _allowsMoves = YES; _usesDiagonals = YES; + _bitTransform = CATransform3DIdentity; self.bounds = CGRectMake(-1, -1, nColumns*spacing.width+2, nRows*spacing.height+2); self.position = pos; @@ -113,7 +119,8 @@ } @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing, reversed=_reversed, - usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures; + usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures, + bitTransform=_bitTransform; #pragma mark - @@ -160,7 +167,8 @@ cell = [self createCellAtRow: row column: col suggestedFrame: frame]; if( cell ) { [_cells replaceObjectAtIndex: index withObject: cell]; - [self addSublayer: cell]; + //[self addSublayer: cell]; + [self insertSublayer: cell atIndex: 0]; [self setNeedsDisplay]; } } @@ -221,6 +229,26 @@ } +- (CATransform3D) bitTransform +{ + return _bitTransform; +} + +- (void) setBitTransform: (CATransform3D)t +{ + _bitTransform = t; + for( GridCell *cell in self.cells ) + [cell setBitTransform: t]; +} + +- (void) updateCellTransform +{ + CATransform3D t = self.aggregateTransform; + t.m41 = t.m42 = t.m43 = 0.0f; // remove translation component + t = CATransform3DInvert(t); + self.bitTransform = t; +} + #pragma mark - #pragma mark GAME STATE: @@ -332,13 +360,14 @@ _grid = grid; _row = row; _column = col; + self.anchorPoint = CGPointMake(0,0); self.position = frame.origin; CGRect bounds = frame; bounds.origin.x -= floor(bounds.origin.x); // make sure my coords fall on pixel boundaries bounds.origin.y -= floor(bounds.origin.y); self.bounds = bounds; - self.anchorPoint = CGPointMake(0,0); self.borderColor = kHighlightColor; // Used when highlighting (see -setHighlighted:) + [self setBitTransform: grid.bitTransform]; } return self; } @@ -351,6 +380,18 @@ @synthesize grid=_grid, row=_row, column=_column; +- (void) setBitTransform: (CATransform3D)bitTransform +{ + // To make the bitTransform relative to my center, I need to offset the center to the origin + // first, and then back afterwards. + CGSize size = self.bounds.size; + CATransform3D x = CATransform3DMakeTranslation(-size.width/2, -size.height/2,0); + x = CATransform3DConcat(x, bitTransform); + x = CATransform3DConcat(x, CATransform3DMakeTranslation(size.width/2, size.height/2,0)); + self.sublayerTransform = x; +} + + - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill { // Default implementation just fills or outlines the cell. @@ -366,12 +407,8 @@ { if( bit != self.bit ) { [super setBit: bit]; - if( bit ) { - // Center it: - CGSize size = self.bounds.size; - bit.position = CGPointMake(floor(size.width/2.0), - floor(size.height/2.0)); - } + if( bit ) + bit.position = GetCGRectCenter(self.bounds); } } diff -r 2eb229411d73 -r 4cb50131788f Source/HexchequerGame.m --- a/Source/HexchequerGame.m Mon Jul 21 17:32:21 2008 -0700 +++ b/Source/HexchequerGame.m Thu Jul 31 11:18:13 2008 -0700 @@ -39,9 +39,11 @@ spacing: CGSizeMake(s,s) position: GetCGRectCenter(tableBounds)]; board.anchorPoint = CGPointMake(0.47,0.5); // Missing half-cells on right edge perturb center pt - [board setValue: [NSNumber numberWithDouble: M_PI/6] forKeyPath: @"transform.rotation"]; + board.transform = CATransform3DMakeRotation(M_PI/6, 0,0,1); + board.bitTransform = CATransform3DMakeRotation(-M_PI/6, 0,0,1); // counteract board rotation _board = board; [_table addSublayer: _board]; + board.allowsMoves = YES; board.allowsCaptures = NO; // no land-on captures, that is board.cellColor = CreateGray(1.0, 0.25);