Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marianbreitmeyer/7ca3734faecc4bc10b4d4ca594748432 to your computer and use it in GitHub Desktop.
Save marianbreitmeyer/7ca3734faecc4bc10b4d4ca594748432 to your computer and use it in GitHub Desktop.
Mixing custom AnimatableVector in AnimatablePair
//
// Mixing custom AnimatableVector in AnimatablePair
//
// Created by Pavel Zak on 26/05/2020.
// Copyright © 2020 Pavel Zak. All rights reserved.
//
import SwiftUI
struct AnimatableVector2D: VectorArithmetic {
var values: [CGPoint]
init(count: Int = 1) {
self.values = [CGPoint](repeating: CGPoint(), count: count)
self.magnitudeSquared = 0.0
}
init(with values: [CGPoint]) {
self.values = values
self.magnitudeSquared = 0
self.recomputeMagnitude()
}
func computeMagnitude()->Double {
// compute square magnitued of the vector
// = sum of all squared values
var sum: Double = 0.0
for index in 0..<self.values.count {
sum += Double(self.values[index].x*self.values[index].x)
sum += Double(self.values[index].y*self.values[index].y)
}
return Double(sum)
}
mutating func recomputeMagnitude(){
self.magnitudeSquared = self.computeMagnitude()
}
// MARK: VectorArithmetic
var magnitudeSquared: Double // squared magnitude of the vector
mutating func scale(by rhs: Double) {
// scale vector with a scalar
// = each value is multiplied by rhs
for index in 0..<values.count {
values[index].x *= CGFloat(rhs)
values[index].y *= CGFloat(rhs)
}
self.magnitudeSquared = self.computeMagnitude()
}
// MARK: AdditiveArithmetic
// zero is identity element for aditions
// = all values are zero
static var zero: AnimatableVector2D = AnimatableVector2D()
static func + (lhs: AnimatableVector2D, rhs: AnimatableVector2D) -> AnimatableVector2D {
var retValues = [CGPoint]()
for index in 0..<min(lhs.values.count, rhs.values.count) {
retValues.append(CGPoint(x: lhs.values[index].x + rhs.values[index].x, y: lhs.values[index].y + rhs.values[index].y))
}
return AnimatableVector2D(with: retValues)
}
static func += (lhs: inout AnimatableVector2D, rhs: AnimatableVector2D) {
for index in 0..<min(lhs.values.count,rhs.values.count) {
lhs.values[index].x += rhs.values[index].x
lhs.values[index].y += rhs.values[index].y
}
lhs.recomputeMagnitude()
}
static func - (lhs: AnimatableVector2D, rhs: AnimatableVector2D) -> AnimatableVector2D {
var retValues = [CGPoint]()
for index in 0..<min(lhs.values.count, rhs.values.count) {
retValues.append(CGPoint(x: lhs.values[index].x - rhs.values[index].x, y: lhs.values[index].y - rhs.values[index].y))
}
return AnimatableVector2D(with: retValues)
}
static func -= (lhs: inout AnimatableVector2D, rhs: AnimatableVector2D) {
for index in 0..<min(lhs.values.count,rhs.values.count) {
lhs.values[index].x -= rhs.values[index].x
lhs.values[index].y -= rhs.values[index].y
}
lhs.recomputeMagnitude()
}
}
// example of usage
struct ExampleShape: Shape {
var controlPoints: AnimatableVector2D
var limit: Double
var animatableData: AnimatablePair<Double, AnimatableVector2D> {
set {
self.controlPoints = newValue.second
self.limit = newValue.first
}
get {
return AnimatablePair(self.limit, self.controlPoints)
}
}
func path(in rect: CGRect) -> Path {
return Path { path in
path.move(to: self.controlPoints.values[0])
var i = 0;
let roundedLimit = (Int)(limit.rounded(.up))
while (i < (self.controlPoints.values.count-1)) && (i < roundedLimit) {
let delta = CGFloat(limit - Double(i))
if delta <= 1.0 { // we need to be able to draw a path even for limits ike 4.234 - the part behind decimal point needs to be interpolated like this
let dx = (self.controlPoints.values[i+1].x-self.controlPoints.values[i].x)*delta
let dy = (self.controlPoints.values[i+1].y-self.controlPoints.values[i].y)*delta
let px = self.controlPoints.values[i].x + dx
let py = self.controlPoints.values[i].y + dy
path.addLine(to: CGPoint(x: px, y: py))
}
else {
path.addLine(to: self.controlPoints.values[i+1])
}
i += 1;
}
//path.addLine(to: self.controlPoints.values[0])
}
}
}
func randomVector(reset: Bool = false, validPoints: Int, size: Double = 300, maxPoints: Int = 20) -> AnimatableVector2D {
let xStep = size/Double(maxPoints)
var points = [CGPoint]()
for index in (0..<maxPoints) {
let xCoord:Double = Double(index)*xStep
let yCoord: Double = (index < validPoints) ? Double.random(in: 100...size) : 0.0
points.append(CGPoint(x: xCoord, y: yCoord))
}
//print("validPoints: \(validPoints)")
print("Reset: \(reset)")
print("maxPoints: \(maxPoints)")
print("-------------------")
return AnimatableVector2D(with: points)
}
func clearVector(maxPoints: Int = 20) ->AnimatableVector2D {
return randomVector(reset: true, validPoints: 0, maxPoints: maxPoints)
}
struct ContentView: View {
@State var points: AnimatableVector2D = randomVector(validPoints: 7)
@State var limit = 6
@State var maxPoints = 20
var body: some View {
VStack {
ExampleShape(controlPoints: self.points, limit: Double(self.limit-1))
.stroke(Color.orange, lineWidth: 2)
.frame(width: 300, height: 300)
.border(Color.gray)
.scaleEffect(x: 1, y: -1, anchor: UnitPoint(x: 0.5, y: 0.5))
Button(action: {
withAnimation(.easeInOut(duration: 0.5)) {
self.points = clearVector(maxPoints: self.maxPoints)
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
self.maxPoints = Int.random(in: 6..<31)
self.limit = self.maxPoints
self.points = clearVector(maxPoints: self.maxPoints)
withAnimation(.easeInOut(duration: 0.5)) {
self.points = randomVector(validPoints: self.limit, maxPoints: self.maxPoints)
}
}
}) {
Text("randomize")
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment