Skip to content

Instantly share code, notes, and snippets.

@sketchytech
Created December 5, 2014 12:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sketchytech/ff01f08c48c295fa9b25 to your computer and use it in GitHub Desktop.
Save sketchytech/ff01f08c48c295fa9b25 to your computer and use it in GitHub Desktop.
Swift: Spinning cube example
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
}
@sketchytech
Copy link
Author

Note: you'll need to add a button to your initial view in the Storyboard file and hook this up to the IBOutlet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment