Created
December 5, 2014 12:11
-
-
Save sketchytech/ff01f08c48c295fa9b25 to your computer and use it in GitHub Desktop.
Swift: Spinning cube example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class ViewController: UIViewController { | |
var topOfCube:CAShapeLayer! | |
var leftOfCube:CAShapeLayer! | |
var frontOfCube:CAShapeLayer! | |
var backOfCube:CAShapeLayer! | |
var rightOfCube:CAShapeLayer! | |
var baseOfCube:CAShapeLayer! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
backOfCube = CAShapeLayer() | |
self.view.layer.addSublayer(backOfCube) | |
baseOfCube = CAShapeLayer() | |
self.view.layer.addSublayer(baseOfCube) | |
rightOfCube = CAShapeLayer() | |
self.view.layer.addSublayer(rightOfCube) | |
leftOfCube = CAShapeLayer() | |
leftOfCube.fillColor = UIColor(hue: 117/360, saturation: 29/100, brightness: 56/100, alpha: 1.0).CGColor | |
self.view.layer.addSublayer(leftOfCube) | |
frontOfCube = CAShapeLayer() | |
self.view.layer.addSublayer(frontOfCube) | |
topOfCube = CAShapeLayer() | |
topOfCube.fillColor = UIColor(hue: 117/360, saturation: 29/100, brightness: 46/100, alpha: 1.0).CGColor | |
self.view.layer.addSublayer(topOfCube) | |
} | |
override func didReceiveMemoryWarning() { | |
super.didReceiveMemoryWarning() | |
// Dispose of any resources that can be recreated. | |
} | |
@IBAction func animate(sender:AnyObject) | |
{ let positionX:CGFloat = 200 | |
let positionY:CGFloat = 200 | |
let radius:CGFloat = 100 | |
let repeat = Float.infinity | |
let duration = 2.5 | |
// roll animation | |
var newPos = positionNextCubeRotated(x: positionX, y: positionY, radius: radius, sides: 6, adjustment: 90, spaces: 6, position: CubeRelativePosition.RightFront, 180) | |
// new angle = 60 diagonally down, 120 straight down, 30 almost straight across, 15 flying up through air slightly, 0 flying up more, 270 lift up into the air as if weightless, 180 falling backwards | |
var caPos = CABasicAnimation(keyPath: "position") | |
caPos.toValue = NSValue(CGPoint: newPos) | |
caPos.duration = 12.0 | |
// colorChange | |
var colorChangeRight = CABasicAnimation(keyPath: "fillColor") | |
colorChangeRight.delegate = self | |
colorChangeRight.fromValue = UIColor(hue: 117/360, saturation: 29/100, brightness: 76/100, alpha: 1.0).CGColor | |
colorChangeRight.toValue = UIColor(hue: 117/360, saturation: 29/100, brightness: 96/100, alpha: 1.0).CGColor | |
colorChangeRight.duration = duration | |
colorChangeRight.repeatCount = repeat | |
// colorChange front | |
var colorChangeFront = CABasicAnimation(keyPath: "fillColor") | |
colorChangeFront.delegate = self | |
colorChangeFront.fromValue = UIColor(hue: 117/360, saturation: 29/100, brightness: 96/100, alpha: 1.0).CGColor | |
colorChangeFront.toValue = UIColor(hue: 117/360, saturation: 29/100, brightness: 56/100, alpha: 1.0).CGColor | |
colorChangeFront.duration = duration | |
colorChangeFront.repeatCount = repeat | |
// cube base | |
let animationb = CAKeyframeAnimation(keyPath:"path") | |
animationb.duration = duration | |
// animationb.calculationMode = kCAAnimationCubic | |
animationb.values = [] | |
// animation for all angles from 0 to 60 | |
for n in 0...60 { | |
let path = cubeBaseRotated(x: positionX, y: positionY, radius: radius, sides: 6, adjustment: 90, newangle: CGFloat(n)) | |
animationb.values.append(path) | |
} | |
animationb.repeatCount = repeat | |
animationb.autoreverses = false | |
animationb.calculationMode = kCAAnimationLinear | |
// cube top | |
let animation = CAKeyframeAnimation(keyPath:"path") | |
animation.duration = duration | |
animation.values = [] | |
// animation for all angles from 0 to 60 | |
for n in 0...60 { | |
let path = cubeTopRotatedOntoRightFrontFace(x: positionX, y: positionY, radius: radius, sides: 6, adjustment: 90, newangle: CGFloat(n)) | |
animation.values.append(path) | |
} | |
animation.repeatCount = repeat | |
animation.autoreverses = false | |
animation.calculationMode = kCAAnimationLinear | |
// cube right | |
let rightanimation1 = CAKeyframeAnimation(keyPath:"path") | |
rightanimation1.values = [] | |
for n in 0...60 { | |
let path = cubeRightRotated(x: positionX, y: positionY, radius: radius, sides: 6, adjustment: 90, newangle: CGFloat(n)) | |
rightanimation1.values.append(path) | |
} | |
rightanimation1.duration = duration | |
rightanimation1.fillMode = kCAFillModeRemoved | |
rightanimation1.repeatCount = repeat | |
rightanimation1.autoreverses = false | |
rightanimation1.calculationMode = kCAAnimationLinear | |
// cube left | |
let animation1 = CAKeyframeAnimation(keyPath:"path") | |
animation1.values = [] | |
for n in 0...60 { | |
let path = cubeLeftRotated(x: positionX, y: positionY, radius: radius, sides: 6, adjustment: 90, newangle: CGFloat(n)) | |
animation1.values.append(path) | |
} | |
animation1.duration = duration | |
animation1.fillMode = kCAFillModeRemoved | |
animation1.repeatCount = repeat | |
animation1.autoreverses = false | |
animation1.calculationMode = kCAAnimationLinear | |
// front of cube | |
let animation11 = CAKeyframeAnimation(keyPath:"path") | |
animation11.values = [] | |
for n in 0...60 { | |
var path = cubeFrontRotated(x: positionX, y: positionY, radius: radius, sides: 6, adjustment: 90, newangle: CGFloat(n)) | |
animation11.values.append(path)} | |
animation11.duration = duration | |
animation11.calculationMode = kCAAnimationLinear | |
animation11.autoreverses = false | |
animation11.repeatCount = repeat | |
// back of cube | |
let animation111 = CAKeyframeAnimation(keyPath:"path") | |
animation111.values = [] | |
for n in 0...60 { | |
let path = cubeBackRotated(x: positionX, y: positionY, radius: radius, sides: 6, adjustment: 90, newangle: CGFloat(n)) | |
animation111.values.append(path)} | |
animation111.duration = duration | |
animation111.calculationMode = kCAAnimationLinear | |
animation111.repeatCount = repeat | |
animation111.autoreverses = false | |
backOfCube.addAnimation(animation111, forKey: nil) | |
backOfCube.addAnimation(colorChangeFront, forKey: nil) | |
//backOfCube.addAnimation(caPos, forKey: nil) | |
frontOfCube.addAnimation(animation11, forKey:nil) | |
frontOfCube.addAnimation(colorChangeFront, forKey: nil) | |
//frontOfCube.addAnimation(caPos, forKey: nil) | |
leftOfCube.addAnimation(animation1, forKey:nil) | |
//leftOfCube.addAnimation(caPos, forKey: nil) | |
rightOfCube.addAnimation(rightanimation1, forKey: nil) | |
topOfCube.addAnimation(animation, forKey:nil) | |
rightOfCube.addAnimation(colorChangeRight, forKey: nil) | |
//topOfCube.addAnimation(caPos, forKey: nil) | |
} | |
} | |
func cubeTopRotatedOntoRightFrontFace(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat, #newangle:CGFloat) -> CGPathRef { | |
let path = CGPathCreateMutable() | |
// HexagonRotatedPoints class takes details and returns new points at rotation angle | |
let points = HexagonRotatedPoints(sides: sides,x: x,y: y,radius: radius,adjustment: adjustment, direction:.RightFront,newangle:newangle) | |
CGPathMoveToPoint(path, nil, points.origin.x, points.origin.y) | |
CGPathAddLineToPoint(path, nil, points.index1.x, points.index1.y) | |
CGPathAddLineToPoint(path, nil, points.index0.x, points.index0.y) | |
CGPathAddLineToPoint(path, nil, points.index5.x, points.index5.y) | |
CGPathCloseSubpath(path) | |
return path | |
} | |
func cubeLeftRotated(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat, #newangle:CGFloat) -> CGPathRef { | |
let path = CGPathCreateMutable() | |
var points = HexagonRotatedPoints(sides: sides,x: x,y: y,radius: radius,adjustment: adjustment,direction: .RightFront,newangle:newangle) | |
CGPathMoveToPoint(path, nil, points.index1.x, points.index1.y) | |
CGPathAddLineToPoint(path, nil, points.origin.x, points.origin.y) | |
CGPathAddLineToPoint(path, nil, points.index3.x, points.index3.y) | |
CGPathAddLineToPoint(path, nil, points.index2.x, points.index2.y) | |
// CGPathAddLineToPoint(path, nil, points[3].x, points[3].y) | |
CGPathCloseSubpath(path) | |
return path | |
} | |
func cubeRightRotated(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat, #newangle:CGFloat) -> CGPathRef { | |
let path = CGPathCreateMutable() | |
var points = HexagonRotatedPoints(sides: sides,x: x,y: y,radius: radius,adjustment: adjustment,direction: .RightFront,newangle:newangle) | |
CGPathMoveToPoint(path, nil, points.index0.x, points.index0.y) | |
CGPathAddLineToPoint(path, nil, points.index5.x, points.index5.y) | |
CGPathAddLineToPoint(path, nil, points.index4.x, points.index4.y) | |
CGPathAddLineToPoint(path, nil, points.originrear.x, points.originrear.y) | |
// CGPathAddLineToPoint(path, nil, points[3].x, points[3].y) | |
CGPathCloseSubpath(path) | |
return path | |
} | |
func cubeBaseRotated(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat, #newangle:CGFloat) -> CGPathRef { | |
let path = CGPathCreateMutable() | |
var points = HexagonRotatedPoints(sides: sides,x: x,y: y,radius: radius,adjustment: adjustment,direction: .RightFront,newangle:newangle) | |
CGPathMoveToPoint(path, nil, points.index4.x, points.index1.y) | |
CGPathAddLineToPoint(path, nil, points.originrear.x, points.originrear.y) | |
CGPathAddLineToPoint(path, nil, points.index2.x, points.index2.y) | |
CGPathAddLineToPoint(path, nil, points.index3.x, points.index3.y) | |
CGPathCloseSubpath(path) | |
return path | |
} | |
func cubeBackRotated(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat, #newangle:CGFloat) -> CGPathRef { | |
let path = CGPathCreateMutable() | |
var points = HexagonRotatedPoints(sides: sides,x: x,y: y,radius: radius,adjustment: adjustment,direction: .RightFront,newangle:newangle) | |
CGPathMoveToPoint(path, nil, points.originrear.x, points.originrear.y) | |
CGPathAddLineToPoint(path, nil, points.index0.x, points.index0.y) | |
CGPathAddLineToPoint(path, nil, points.index1.x, points.index1.y) | |
CGPathAddLineToPoint(path, nil, points.index2.x, points.index2.y) | |
CGPathCloseSubpath(path) | |
return path | |
} | |
func cubeFrontRotated(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat, #newangle:CGFloat) -> CGPathRef { | |
let path = CGPathCreateMutable() | |
let points = HexagonRotatedPoints(sides: sides,x: x,y: y,radius: radius,adjustment: adjustment, direction:.RightFront,newangle:newangle) | |
CGPathMoveToPoint(path, nil, points.origin.x, points.origin.y) | |
CGPathAddLineToPoint(path, nil, points.index5.x, points.index5.y) | |
CGPathAddLineToPoint(path, nil, points.index4.x, points.index4.y) | |
CGPathAddLineToPoint(path, nil, points.index3.x, points.index3.y) | |
// CGPathAddLineToPoint(path, nil, points[3].x, points[3].y) | |
CGPathCloseSubpath(path) | |
return path | |
} | |
enum HexagonIndex:Int { | |
case Zero, One, Two, Three, Four, Five | |
} | |
func degree2radian(a:CGFloat)->CGFloat { | |
let b = CGFloat(M_PI) * a/180 | |
return b | |
} | |
enum HexagonRollOntoDirection { | |
case RightFront, LeftFront, RightRear, LeftRear, ClockwiseSpin, AntiClockwiseSpin | |
} | |
func polygonPointArray(sides:Int,x:CGFloat,y:CGFloat,radius:CGFloat,adjustment:CGFloat=0)->[CGPoint] { | |
let angle = degree2radian(360/CGFloat(sides)) | |
let cx = x // x origin | |
let cy = y // y origin | |
let r = radius // radius of circle | |
var i = sides | |
var points = [CGPoint]() | |
while points.count <= sides { | |
let xpo = cx - r * cos(angle * CGFloat(i)+degree2radian(adjustment)) | |
let ypo = cy - r * sin(angle * CGFloat(i)+degree2radian(adjustment)) | |
points.append(CGPoint(x: xpo, y: ypo)) | |
i--; | |
} | |
return points | |
} | |
class HexagonRotatedPoints { | |
let origin:CGPoint, index0:CGPoint, index1:CGPoint, index2:CGPoint, index3:CGPoint, index4:CGPoint, index5:CGPoint, originrear:CGPoint | |
init (sides:Int,x:CGFloat,y:CGFloat,radius:CGFloat,adjustment:CGFloat, direction:HexagonRollOntoDirection, newangle:CGFloat) { | |
// Currently these points supply the points for a HexagonRollOntoDirection.RightFront | |
let points = polygonPointArray(sides,x,y,radius,adjustment: adjustment) | |
// cube is pivoting on index 3, so the x position never changes | |
let originX = points[0].x - radius * cos(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(180)) | |
// origin is at a point 60 degrees from the 0 line of index 2, it pivots on the arc of the circle from index 2 (to index 3) | |
let originY = points[0].y - radius * sin(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(180)) | |
origin = CGPoint(x: originX, y: originY) | |
// index 0 on the hexagon | |
let index0X = x - radius * cos(degree2radian(newangle) + degree2radian(adjustment)) | |
let index0Y = y - radius * sin(degree2radian(newangle) + degree2radian(adjustment)) | |
index0 = CGPoint(x: index0X, y: index0Y) | |
// index 1 is at the position 90 degrees (the adjustment) to index 2 and follows the arc of the index 2 circle (to the origin) | |
let index1X = x - radius * cos(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(300)) | |
let index1Y = y - radius * sin(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(300)) | |
index1 = CGPoint(x: index1X, y: index1Y) | |
// index 2 at 240 degrees to the 12 o'clock position of a circle about index 3 | |
let index2x = points[3].x - radius * cos(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(300)) | |
let index2y = points[3].y - radius * sin(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(300)) | |
index2 = CGPoint(x: index2x, y: index2y) | |
// index 3 on hexagon (moves to index 2) [AJL: not entirely confident index 3 is correct) | |
let index3X = x - radius * cos(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(180)) | |
let index3Y = y - radius * sin(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(180)) | |
index3 = CGPoint(x: index3X, y: index3Y) | |
// index 4 on hexagon (moves to index 3) | |
let index4X = x - radius * cos(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(120)) | |
let index4Y = y - radius * sin(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(120)) | |
index4 = CGPoint(x: index4X, y: index4Y) | |
// index 5 at 60 degrees to the top position | |
let index5X = points[0].x - radius * cos(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(120)) | |
let index5Y = points[0].y - radius * sin(degree2radian(newangle) + degree2radian(adjustment) + degree2radian(120)) | |
index5 = CGPoint(x: index5X, y: index5Y) | |
// rear origin | |
let originrearX = points[3].x - radius * cos(degree2radian(newangle) + degree2radian(adjustment)) | |
let originrearY = points[3].y - radius * sin(degree2radian(newangle) + degree2radian(adjustment)) | |
originrear = CGPoint(x: originrearX, y: originrearY) | |
} | |
} | |
enum CubeRelativePosition:Int { | |
case Above = 0, LeftBehind, LeftFront, Underneath, RightFront, RightBehind | |
} | |
func positionNextCubeRotated(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat, #spaces:Int, #position:CubeRelativePosition, newangle:CGFloat) -> CGPoint { | |
// block rolls along slope, if HexagonRotatedPoints.origin taken would look like steps at 60 degrees | |
var points = HexagonRotatedPoints(sides: sides,x: x,y: y,radius: radius*CGFloat(spaces),adjustment: adjustment,direction:HexagonRollOntoDirection.RightFront,newangle:newangle).index5 | |
// var newpoints = polygonPointArray(sides, points.x, points.y, radius, adjustment: adjustment) | |
return points | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note: you'll need to add a button to your initial view in the Storyboard file and hook this up to the IBOutlet