Last active
August 29, 2015 14:10
-
-
Save sketchytech/b4baba0752b75759136f to your computer and use it in GitHub Desktop.
Swift: Cube sinking animation
This file contains 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 | |
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