Skip to content

Instantly share code, notes, and snippets.

@sketchytech
Last active August 29, 2015 14:10
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/b4baba0752b75759136f to your computer and use it in GitHub Desktop.
Save sketchytech/b4baba0752b75759136f to your computer and use it in GitHub Desktop.
Swift: Cube sinking animation
import UIKit
import QuartzCore
class ViewController: UIViewController {
var tilePos:CGPoint!
let adjustment:CGFloat = 90
let radius:CGFloat = 50
var toPoint:CGPoint!
var nextPoint:CGPoint!
var fromPoint:CGPoint!
var cubeLayer:CAShapeLayer!
var layerCurrent:CAShapeLayer!
var cubePoint:CGPoint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// select cube rotation
cubePoint = CGPoint(x: CGRectGetMidX(self.view.frame), y: CGRectGetMidY(self.view.frame))
// tile underneath cube
var tile = drawCubeBase(cubePoint.x, cubePoint.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
self.view.layer.addSublayer(tile)
// tile to left of cube
tilePos = positionNextCube(x: cubePoint.x, y: cubePoint.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.LeftBehind)
tile = drawCubeBase(tilePos.x, tilePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
self.view.layer.addSublayer(tile)
tilePos = positionNextCube(x: tilePos.x, y: tilePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.LeftFront)
tile = drawCubeBase(tilePos.x, tilePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
self.view.layer.addSublayer(tile)
// corner hole tiles
tilePos = positionNextCube(x: tilePos.x, y: tilePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.RightFront)
var tileCorner = cornerHoleTile(tilePos.x, tilePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 65/100, alpha: 1.0))
self.view.layer.addSublayer(tileCorner)
// front cubes
var frontCubePos = positionNextCube(x: tilePos.x, y: tilePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.RightFront)
frontCubePos = positionNextCube(x: frontCubePos.x, y: frontCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.Underneath)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = 2
var cube = drawCube(frontCubePos.x, frontCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
// cosmetic cube
var rearCubePos = positionNextCube(x: frontCubePos.x, y: frontCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.RightBehind)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = 0
cube = drawCube(rearCubePos.x, rearCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
rearCubePos = positionNextCube(x: rearCubePos.x, y: rearCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.Underneath)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = -1
cube = drawCube(rearCubePos.x, rearCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
// cosmetic cube
frontCubePos = positionNextCube(x: frontCubePos.x, y: frontCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.LeftFront)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = 3
cube = drawCube(frontCubePos.x, frontCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
// essential cube
frontCubePos = positionNextCube(x: frontCubePos.x, y: frontCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.LeftBehind)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = 2
cube = drawCube(frontCubePos.x, frontCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
// cosmetic cube
rearCubePos = positionNextCube(x: frontCubePos.x, y: frontCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.LeftBehind)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = 0
cube = drawCube(rearCubePos.x, rearCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
rearCubePos = positionNextCube(x: rearCubePos.x, y: rearCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.Underneath)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = -1
cube = drawCube(rearCubePos.x, rearCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
// essential cube
frontCubePos = positionNextCube(x: frontCubePos.x, y: frontCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.Underneath)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = 1
cube = drawCube(frontCubePos.x, frontCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
// cosmetic cube
frontCubePos = positionNextCube(x: frontCubePos.x, y: frontCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.RightFront)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = 2
cube = drawCube(frontCubePos.x, frontCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
// essential cube
frontCubePos = positionNextCube(x: frontCubePos.x, y: frontCubePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.RightBehind)
cubeLayer = CAShapeLayer()
cubeLayer.zPosition = 1
cube = drawCube(frontCubePos.x, frontCubePos.y, radius, adjustment, UIColor(hue: 117/360, saturation: 29/100, brightness: 86/100, alpha: 1.0))
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
// cubeLayer.transform = CATransform3DMakeTranslation(tilePos.x-cubePoint.x, tilePos.y-cubePoint.y, 0)
}
override func viewDidAppear(animated: Bool) {
// draw cube
}
@IBAction func animateBlock(sender: AnyObject) {
let cube = drawCube(cubePoint.x, cubePoint.y, radius, adjustment, UIColor(hue: 60/360, saturation: 80/100, brightness: 100/100, alpha: 1.0))
cubeLayer = CAShapeLayer()
// cubeLayer.anchorPoint = CGPoint(x:0,y:0)
for c in cube {
cubeLayer.addSublayer(c)
}
self.view.layer.addSublayer(cubeLayer)
if layerCurrent != nil {
layerCurrent.removeFromSuperlayer()
}
var tilePosNew = positionNextCube(x: tilePos.x, y: tilePos.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 1, position:.LeftFront)
tilePosNew = positionNextCube(x: tilePosNew.x, y: tilePosNew.y, radius: radius, sides: 6, adjustment:adjustment, spaces: 2, position:.Underneath)
blockAnimation(cubeLayer, dur: 10.0, from: cubePoint, to: tilePos, next:tilePosNew)
}
func blockAnimation(currentLayer:CAShapeLayer,dur:CFTimeInterval,from:CGPoint,to:CGPoint,next:CGPoint){
toPoint = to
nextPoint = next
fromPoint = from
layerCurrent = currentLayer
var angle = degree2radian(360)
var firstAnimation = CABasicAnimation(keyPath:"transform")
// Make this view controller the delegate so it knows when the animation starts and ends
firstAnimation.delegate = self
firstAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Use fromValue and toValue
// theAnimation.fromValue = from.x
//firstAnimation.repeatCount = Float.infinity
firstAnimation.toValue = NSValue(CATransform3D: CATransform3DMakeTranslation(to.x-from.x, to.y-from.y,0))
// Transform3DMakeTranslation(to.x, to.y, 0))
firstAnimation.fillMode = kCAFillModeForwards
firstAnimation.removedOnCompletion = false
firstAnimation.duration = dur/2
// ---- Second Animation ----//
//firstAnimation.autoreverses = true
currentLayer.addAnimation(firstAnimation, forKey:"pos1")
}
override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
// see http://stackoverflow.com/a/1390981/1694526
if anim == layerCurrent.animationForKey("pos1") { layerCurrent.transform = CATransform3DMakeTranslation(toPoint.x-fromPoint.x, toPoint.y-fromPoint.y,0)
var secondAnimation = CABasicAnimation(keyPath:"transform")
secondAnimation.toValue = NSValue(CATransform3D: CATransform3DMakeTranslation(nextPoint.x-toPoint.x, nextPoint.y-toPoint.y,0))
secondAnimation.fillMode = kCAFillModeRemoved
secondAnimation.duration = 10
secondAnimation.autoreverses = true
layerCurrent.addAnimation(secondAnimation, forKey:"pos2")
}
else {
// layerCurrent.transform = CATransform3DMakeTranslation(fromPoint.x-toPoint.x, fromPoint.x-toPoint.y,0)
}
}
}
func degree2radian(a:CGFloat)->CGFloat {
let b = CGFloat(M_PI) * a/180
return b
}
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
}
func drawCube (x:CGFloat,y:CGFloat,radius:CGFloat, adjustment:CGFloat, color:UIColor)->[CAShapeLayer] {
// --- Color is shaded with light coming from above and to the right --- //
var h:CGFloat = 0
var s:CGFloat = 0
var b:CGFloat = 0
color.getHue(&h, saturation: &s, brightness: &b, alpha: nil)
// --- Left side of cube (visible) --- //
let leftSide = CAShapeLayer()
let leftSidePath = cubeLeft(x: x, y: y, radius: radius, sides: 6, adjustment:adjustment)
leftSide.path = leftSidePath
leftSide.fillColor = UIColor(hue: h, saturation: s, brightness: b*0.7, alpha: 1.0).CGColor
// --- Front side of cube (visible) --- //
let frontSide = CAShapeLayer()
let frontSidePath = cubeFront(x: x, y: y, radius: radius, sides: 6, adjustment:adjustment)
frontSide.path = frontSidePath
frontSide.fillColor = UIColor(hue: h, saturation: s, brightness: b*0.85, alpha: 1.0).CGColor
let topSide = CAShapeLayer()
let topSidePath = cubeTop(x: x, y: y, radius: radius, sides: 6, adjustment:adjustment)
topSide.path = topSidePath
topSide.fillColor = UIColor(hue: h, saturation: s, brightness: b, alpha: 1.0).CGColor
return [leftSide,frontSide,topSide]
}
func cubeTop(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat) -> CGPathRef {
let path = CGPathCreateMutable()
let points = polygonPointArray(sides,x,y,radius,adjustment: adjustment)
var cpg = points[0]
CGPathMoveToPoint(path, nil, cpg.x, cpg.y)
CGPathAddLineToPoint(path, nil, points[1].x, points[1].y)
CGPathAddLineToPoint(path, nil, x, y)
CGPathAddLineToPoint(path, nil, points[5].x, points[5].y)
CGPathCloseSubpath(path)
return path
}
func cubeFront(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat) -> CGPathRef {
let path = CGPathCreateMutable()
let points = polygonPointArray(sides,x,y,radius,adjustment: adjustment)
var cpg = points[5]
CGPathMoveToPoint(path, nil, cpg.x, cpg.y)
CGPathAddLineToPoint(path, nil, points[4].x, points[4].y)
CGPathAddLineToPoint(path, nil, points[3].x, points[3].y)
CGPathAddLineToPoint(path, nil, x, y)
CGPathCloseSubpath(path)
return path
}
func cubeLeft(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat) -> CGPathRef {
let path = CGPathCreateMutable()
let points = polygonPointArray(sides,x,y,radius,adjustment: adjustment)
var cpg = points[5]
CGPathMoveToPoint(path, nil, x, y)
CGPathAddLineToPoint(path, nil, points[1].x, points[1].y)
CGPathAddLineToPoint(path, nil, points[2].x, points[2].y)
CGPathAddLineToPoint(path, nil, points[3].x, points[3].y)
CGPathCloseSubpath(path)
return path
}
func cubeBase(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat) -> CGPathRef {
let path = CGPathCreateMutable()
let points = polygonPointArray(sides,x,y,radius,adjustment: adjustment)
var cpg = points[2]
CGPathMoveToPoint(path, nil, cpg.x, cpg.y)
CGPathAddLineToPoint(path, nil, points[3].x, points[3].y)
CGPathAddLineToPoint(path, nil, points[4].x, points[4].y)
CGPathAddLineToPoint(path, nil, x, y)
CGPathCloseSubpath(path)
return path
}
func cornerHoleTileLeft(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat) -> CGPathRef {
let path = CGPathCreateMutable()
let points = polygonPointArray(sides,x,y,radius,adjustment: adjustment)
var cpg = points[2]
CGPathMoveToPoint(path, nil, cpg.x, cpg.y)
CGPathAddLineToPoint(path, nil, points[3].x, points[3].y)
CGPathAddLineToPoint(path, nil, x, y)
CGPathCloseSubpath(path)
return path
}
func cornerHoleTileRight(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat) -> CGPathRef {
let path = CGPathCreateMutable()
let points = polygonPointArray(sides,x,y,radius,adjustment: adjustment)
var cpg = points[3]
CGPathMoveToPoint(path, nil, cpg.x, cpg.y)
CGPathAddLineToPoint(path, nil, points[4].x, points[4].y)
CGPathAddLineToPoint(path, nil, x, y)
CGPathCloseSubpath(path)
return path
}
func drawCubeBase (x:CGFloat,y:CGFloat,radius:CGFloat, adjustment:CGFloat, color:UIColor)->CAShapeLayer {
// --- Left side of cube (visible) --- //
let cubeBaseObject = CAShapeLayer()
let cubeBasePath = cubeBase(x: x, y: y, radius: radius, sides: 6, adjustment:adjustment)
cubeBaseObject.path = cubeBasePath
cubeBaseObject.fillColor = color.CGColor
return cubeBaseObject
}
func cornerHoleTile (x:CGFloat,y:CGFloat,radius:CGFloat, adjustment:CGFloat, color:UIColor)->CAShapeLayer {
// --- Color is shaded with light coming from above and to the right --- //
var h:CGFloat = 0
var s:CGFloat = 0
var b:CGFloat = 0
color.getHue(&h, saturation: &s, brightness: &b, alpha: nil)
let corner = CAShapeLayer()
let left = CAShapeLayer()
let leftPath = cornerHoleTileLeft(x: x, y: y, radius: radius, sides: 6, adjustment:adjustment)
left.path = leftPath
left.fillColor = color.CGColor
let right = CAShapeLayer()
let rightPath = cornerHoleTileRight(x: x, y: y, radius: radius, sides: 6, adjustment:adjustment)
right.path = rightPath
right.fillColor = UIColor(hue: h, saturation: s, brightness: b*0.8, alpha: 1.0).CGColor
corner.addSublayer(left)
corner.addSublayer(right)
return corner
}
func pyramidFront(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat) -> CGPathRef {
let path = CGPathCreateMutable()
let points = polygonPointArray(sides,x,y,radius,adjustment: adjustment)
var cpg = points[4]
CGPathMoveToPoint(path, nil, cpg.x, cpg.y)
CGPathAddLineToPoint(path, nil, points[5].x/2+points[1].x/2, points[5].y/2+points[1].y/2)
CGPathAddLineToPoint(path, nil, points[3].x, points[3].y)
CGPathCloseSubpath(path)
return path
}
func pyramidLeft(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat) -> (path:CGPathRef,point:CGPoint) {
let path = CGPathCreateMutable()
let points = polygonPointArray(sides,x,y,radius,adjustment: adjustment)
var cpg = points[3]
CGPathMoveToPoint(path, nil, cpg.x, cpg.y)
CGPathAddLineToPoint(path, nil, points[5].x/2+points[1].x/2, points[5].y/2+points[1].y/2)
CGPathAddLineToPoint(path, nil, points[2].x, points[2].y)
CGPathCloseSubpath(path)
let topOfPyramid = CGPoint(x:points[5].x/2+points[1].x/2, y:points[5].y/2+points[1].y/2)
return (path,topOfPyramid)
}
func drawPyramid(x:CGFloat,y:CGFloat,radius:CGFloat,adjustment:CGFloat, color:UIColor)->[CAShapeLayer] {
// --- Color is shaded with light coming from above and to the right --- //
var h:CGFloat = 0
var s:CGFloat = 0
var b:CGFloat = 0
color.getHue(&h, saturation: &s, brightness: &b, alpha: nil)
let pyramidFrontLayer = CAShapeLayer()
let pyramidFrontPath = pyramidFront(x: x, y: y, radius: radius, sides: 6, adjustment: adjustment)
pyramidFrontLayer.path = pyramidFrontPath
pyramidFrontLayer.fillColor = UIColor(hue: h, saturation: s, brightness: b*0.85, alpha: 1.0).CGColor
let pyramidLeftLayer = CAShapeLayer()
let pyramidLeftPath = pyramidLeft(x: x, y: y, radius: radius, sides: 6, adjustment: adjustment)
pyramidLeftLayer.path = pyramidLeftPath.path
pyramidLeftLayer.fillColor = UIColor(hue: h, saturation: s, brightness: b*0.7, alpha: 1.0).CGColor
return [pyramidFrontLayer,pyramidLeftLayer]
}
func positionNextCube(#x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #adjustment:CGFloat, #spaces:Int, #position:CubeRelativePosition) -> CGPoint {
// to get the origin of a hexagon so many spaces away, we simply multiply the radius by the number of spaces since each origin is space exactly the length of the radius
var points = polygonPointArray(sides,x,y,radius*CGFloat(spaces),adjustment: adjustment)
return points[position.rawValue]
}
enum CubeRelativePosition:Int {
case Above = 0, LeftBehind, LeftFront, Underneath, RightFront, RightBehind
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment