-
Notifications
You must be signed in to change notification settings - Fork 34
/
RUIAnimations.swift
130 lines (124 loc) · 5 KB
/
RUIAnimations.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//
// RUIAnimations.swift
//
//
// Created by Max Cobb on 21/12/2020.
//
import Foundation
import Combine
import RealityKit
public extension Entity {
/// Spin an Entity on an axis.
/// - Parameters:
/// - axis: Axis on which to rotate around.
/// - period: TIme interval for one revolution.
/// - times: Number of revolutions. Default is -1, meaning spin forever.
/// - completion: Action to take place once the last spin has completed.
/// This will not execute if the animation is interrupted.
func ruiSpin(
by axis: SIMD3<Float>, period: TimeInterval,
times: Int = -1, completion: (() -> Void)? = nil
) {
self.spinPrivate(by: axis, period: period, times: max(-1, times * 3 - 1), completion: completion)
}
/// Shake an Entity by a quaternion angle.
/// - Parameters:
/// - quat: Quaternion to add and take away from the entity's starting orientation. Do not use an angle greater than 180º
/// - period: Time interval to go from + quat to - quat
/// - times: Number of times the entity should go from one side to the other. Adding 0 just goes start -> + quat -> end.
/// - completion: Action to take place once the last spin has completed.
/// This will not execute if the animation is interrupted.
func ruiShake(by quat: simd_quatf, period: TimeInterval, times: Int, completion: (() -> Void)? = nil) {
let rockBit = matrix_multiply(
self.transform.matrix,
Transform(scale: .one, rotation: quat, translation: .zero).matrix
)
self.move(to: rockBit, relativeTo: self.parent, duration: period / 2, timingFunction: .easeIn)
var shakeCancellable: Cancellable!
shakeCancellable = self.scene?.subscribe(to: AnimationEvents.PlaybackCompleted.self, on: self, { _ in
shakeCancellable.cancel()
self.shakePrivate(
by: simd_quatf(angle: -quat.angle * 2, axis: quat.axis),
period: period,
remaining: times == -1 ? times : max(times * 2 - 1, 0),
completion: completion
)
})
}
/// Stop all animations on an object, not letting any slip through the net.
/// - Parameters:
/// - tryfor: How long (in nanoseconds) to keep calling `stopAllAnimations()`. Default is 1e8 (0.1s)
/// - completion: Action to take place once the last call to stopAllAnimations has run.
///
/// Because these animations aren't completely native, there are a few tricks to get them to work,
/// I've found that sometimes the call to stop has been made on the same frame a new animation is starting.
/// Therefor it's sometimes necessary to use this method as a last resort. **Yes, very hacky, please don't judge me.**
func ruiStopAnim(tryfor: UInt64 = UInt64(1e8), completion: ((Entity) -> Void)? = nil) {
self.stopAllAnimations()
if tryfor > 0 {
let startTime = DispatchTime.now().uptimeNanoseconds
var updCancellable: Cancellable!
updCancellable = self.scene?.subscribe(to: SceneEvents.Update.self, { _ in
self.stopAllAnimations()
if DispatchTime.now().uptimeNanoseconds - startTime > tryfor {
updCancellable.cancel()
completion?(self)
}
})
}
}
private func spinPrivate(
by axis: SIMD3<Float>, period: TimeInterval,
times: Int, completion: (() -> Void)? = nil
) {
let startPos = self.transform
let spun90 = matrix_multiply(
startPos.matrix,
Transform(scale: .one, rotation: simd_quatf(angle: 2 * .pi / 3, axis: axis), translation: .zero).matrix
)
self.move(
to: Transform(matrix: spun90),
relativeTo: self.parent,
duration: period / 3,
timingFunction: times == 0 ? .easeOut : .linear)
var spinCancellable: Cancellable!
spinCancellable = self.scene?.subscribe(to: AnimationEvents.PlaybackCompleted.self, on: self, { _ in
spinCancellable.cancel()
if times != 0 {
self.spinPrivate(by: axis, period: period, times: max(-1, times - 1), completion: completion)
} else {
completion?()
}
})
}
private func shakePrivate(
by quat: simd_quatf, period: TimeInterval,
remaining: Int, completion: (() -> Void)? = nil
) {
var applyQuat: simd_quatf!
if remaining != 0 {
applyQuat = quat
} else {
applyQuat = simd_quatf(angle: quat.angle / 2, axis: quat.axis)
}
let rockBit = matrix_multiply(
self.transform.matrix,
Transform(scale: .one, rotation: applyQuat, translation: .zero).matrix
)
self.move(
to: rockBit, relativeTo: self.parent,
duration: remaining == 0 ? period / 2 : period,
timingFunction: remaining == 0 ? .easeOut : .linear
)
var shakeCancellable: Cancellable!
shakeCancellable = self.scene?.subscribe(to: AnimationEvents.PlaybackCompleted.self, on: self, { _ in
shakeCancellable.cancel()
if remaining != 0 {
let newQuat = simd_quatf(angle: -quat.angle, axis: quat.axis)
self.shakePrivate(by: newQuat, period: period, remaining: remaining - 1, completion: completion)
} else {
completion?()
}
})
}
}