Skip to content

Instantly share code, notes, and snippets.

@Orangetronic
Created November 21, 2020 23:35
Show Gist options
  • Save Orangetronic/a028602a7336ba465625a19399225d41 to your computer and use it in GitHub Desktop.
Save Orangetronic/a028602a7336ba465625a19399225d41 to your computer and use it in GitHub Desktop.
Dump this in an iOS playground, and click around to simulate what would be index finger point tracking
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
import CoreGraphics
import simd
/// # Some Convencience Extensions
extension simd_float2 {
func clampLength (_ f: Float) -> simd_float2 {
if simd_length(self) < f { return self }
if f == 0 { return simd_make_float2(0, 0) }
return simd_normalize(self) * f
}
}
extension CGPoint {
init (_ vector: simd_float2) {
self = CGPoint(
x: CGFloat(vector.x),
y: CGFloat(vector.y)
)
}
func to_simd_float2() -> simd_float2 {
return simd_make_float2(
Float(self.x),
Float(self.y)
)
}
}
/// # Laws of the universe
fileprivate let TERMINAL_VELOCITY: Float = 2
fileprivate let MAX_G: Float = 0.2
/// # A lil circle class with a colour, radius, and position
class UICircle {
var shapeLayer = CAShapeLayer()
var position: simd_float2
var radius: CGFloat
init (center: CGPoint, radius: CGFloat) {
self.position = center.to_simd_float2()
self.radius = radius
setPath()
self.shapeLayer.fillColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1).cgColor
}
@discardableResult
func setColor(_ col: CGColor) -> Self {
self.shapeLayer.fillColor = col
self.shapeLayer.strokeColor = col
return self
}
func setPath () {
let circle = UIBezierPath(
arcCenter: CGPoint(position),
radius: radius,
startAngle: 0,
endAngle: CGFloat.pi * 2,
clockwise: true
)
self.shapeLayer.path = circle.cgPath
}
}
/// # A circle whom is a cursor
class UICircleCursor: UICircle {
var targetSize: CGFloat
var V: simd_float2
var target: simd_float2
override init (center: CGPoint, radius: CGFloat) {
targetSize = radius
self.V = simd_make_float2(0, 0)
self.target = center.to_simd_float2()
super.init(center: center, radius: radius)
}
func setTarget(_ pos: CGPoint) {
self.target = pos.to_simd_float2()
}
func tick() {
self.position = self.position + self.V
if simd_distance_squared(self.target, self.position) < 20 {
self.position = self.target
}
self.setPath()
let toTarget = self.target - self.position
let dV = toTarget.clampLength(MAX_G) // acceleration towards target
let distanceToTarget = simd_length(toTarget)
var TV = distanceToTarget < 2 * TERMINAL_VELOCITY
? distanceToTarget / 2
: TERMINAL_VELOCITY
if TV < 1 { TV = 1 }
self.V = (self.V + dV).clampLength(TV)
}
}
class UICircleTarget: UICircle {
enum TargetStatus {
case inactive
case active(Int)
}
var width: CGFloat = 10.0
var action: (() -> ())? = nil
var threshold: Int = 60
var status: TargetStatus = .inactive
var indicator: UICircle
var maskLayer = CAShapeLayer()
override init (center: CGPoint, radius: CGFloat) {
self.indicator = UICircle(center: center, radius: 10).setColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 0).cgColor)
super.init(center: center, radius: radius)
self.shapeLayer.fillColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0).cgColor
self.shapeLayer.strokeColor = #colorLiteral(red: 0.8549019694, green: 0.250980407, blue: 0.4784313738, alpha: 1).cgColor
self.shapeLayer.lineWidth = width
self.shapeLayer.addSublayer(self.indicator.shapeLayer)
self.maskLayer.path = self.shapeLayer.path!.copy()
self.indicator.shapeLayer.mask = self.maskLayer
}
override func setColor(_ col: CGColor) -> Self {
super.setColor(col)
self.shapeLayer.fillColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0).cgColor
return self
}
func contains (_ cursor: UICircleCursor) -> Bool {
let distance_squared = simd_distance_squared(self.position, cursor.position)
let size_difference = Float(self.radius - cursor.radius)
let size_diff_squared = size_difference * size_difference
let isOutside = (size_diff_squared < distance_squared) && (self.radius > cursor.radius)
let contains = !isOutside
return contains
}
func tick (_ cursor: UICircleCursor) {
if !self.contains(cursor) {
self.status = .inactive
self.indicator.setColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 0).cgColor)
} else {
var nextCount = 1
switch self.status {
case .inactive:
self.indicator.setColor(self.shapeLayer.strokeColor!)
self.status = .active(nextCount)
case .active(let currentCount):
nextCount = currentCount + 1
}
self.status = .active(nextCount)
self.indicator.position = cursor.position
self.indicator.radius = CGFloat(nextCount + 10)
self.indicator.setPath()
if nextCount == threshold {
if let action = self.action {
action()
}
}
}
}
func setAction (_ closure: @escaping () -> ()) {
self.action = closure
}
}
class MyViewController : UIViewController {
var cursor: UICircleCursor = UICircleCursor(
center: CGPoint(x: 100, y: 100),
radius: 15)
.setColor(#colorLiteral(red: 0.8549019694, green: 0.250980407, blue: 0.4784313738, alpha: 1).cgColor)
var target: UICircleTarget = UICircleTarget(
center: CGPoint(x: 200, y: 200),
radius: 50)
.setColor(#colorLiteral(red: 0.8549019694, green: 0.250980407, blue: 0.4784313738, alpha: 1).cgColor)
var ticker: Timer? = nil
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
view.layer.addSublayer(cursor.shapeLayer)
view.layer.addSublayer(target.shapeLayer)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped(sender:)))
view.addGestureRecognizer(tap)
ticker = Timer.scheduledTimer(withTimeInterval: 0.015, repeats: true, block: { (t) in
self.cursor.tick()
self.target.tick(self.cursor)
})
}
override func viewWillDisappear(_ animated: Bool) {
self.ticker?.invalidate()
self.ticker = nil
}
@objc func tapped (sender: UITapGestureRecognizer) {
let pos = sender.location(in: self.view)
self.moved(pos)
}
func moved (_ pos: CGPoint) {
self.cursor.setTarget(pos)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment