Skip to content

Instantly share code, notes, and snippets.

@ecoopnet
Last active May 1, 2017 11:09
Show Gist options
  • Save ecoopnet/00d81f2e9b52b695e6cf6ac1b1a2d8ad to your computer and use it in GitHub Desktop.
Save ecoopnet/00d81f2e9b52b695e6cf6ac1b1a2d8ad to your computer and use it in GitHub Desktop.
interval秒の間、時間をかけてfrom から toに移動するサンプル。①0から遷移 ②0に遷移 ③それ以外の遷移で遷移パターンを変えています。
//: Playground - noun: a place where people can play
import Foundation
///
class ValueCalculator{
let INCREASE_ZERO_INTERVAL:TimeInterval = 12.0
let DECREASE_ZERO_INTERVAL:TimeInterval = 13.0
let DEFAULT_INTERVAL:TimeInterval = 3.0
let interval:TimeInterval
var increasing:Bool {
return from < to
}
let DECREASE_TIME_THRESHOLD:TimeInterval = 1.5
let DECREASE_SPEED_THRESHOLD:Int = 39
let initialSeconds:TimeInterval
var maxSeconds:TimeInterval {
return interval + initialSeconds
}
let from:Int
let to:Int
let timer:()->TimeInterval
convenience init(from:Int, to:Int) {
// debuggable にしないなら、こちらのNSTime()依存バージョンだけでOK。
self.init(from:from, to:to, timer:{
return NSDate().timeIntervalSince1970
})
}
init(from:Int, to:Int, timer:@escaping ()->TimeInterval) {
self.timer = timer
let initialSeconds = self.timer()
self.initialSeconds = initialSeconds
self.from = from
self.to = to
if(from == 0 || to == 0){
self.interval = from < to ? INCREASE_ZERO_INTERVAL : DECREASE_ZERO_INTERVAL
}else{
self.interval = DEFAULT_INTERVAL
}
}
func currentValue() -> Int {
if from == to { return to }
if from != 0 && to != 0 {
return currentValueImpl()
}
if increasing { return increasingCurrentValueFromZero() }
return decreasingCurrentValueFromZero()
}
/// A -> B への増減の遷移の実装(AもBもゼロ付近ではない)
private func currentValueImpl() -> Int {
// 1秒に 10~15くらい変動するのを目安に。
// 2~3秒くらいで移動完了する(40,70,100として、最大60なので、3秒で実現するには20ずつの遷移)
// 最大何秒かけて移動するか
let x = Double(min(self.timer() - initialSeconds, interval))
// 1秒あたりの最低移動速度(0なら最低速度制限なし)
let minSpeedPerSec:Double = 10
// 速度は -min >= (from - to), または (from - to) <= max を満たす。
var d = Double(to - from) / interval
if d < 0 && -minSpeedPerSec < d { // if -min < d < 0 { d = -min }
d = -minSpeedPerSec
}else if 0 < d && d < minSpeedPerSec { // if 0 < d < min { d = min }
d = minSpeedPerSec
}
let result = from + Int(d * x)
// print("result=\(result), d=\(d), x=\(x), from,to=\(from),\(to) ")
if increasing { return min(to, result) }
return max(to, result)
}
/// 0(付近)から増加する動きの実装
private func increasingCurrentValueFromZero() -> Int {
let x = Double(min(self.timer() - initialSeconds, interval))
// ratioの計算がメイン。
// 計算式は改善の余地あり。動きをみてみたい。
/*
定数:
n = 12.0 // 終了までの秒数
a = 0 // 開始値
m = 70 // 終了値
x: 0 -> n // 経過秒数
y: a -> m // 経過秒ごとの結果
// 参考:線形(まっすぐふえる場合)
y = a + (m - a) * x / n
// 対数(ゆっくりふえる)
y = a + (m - a) * log (x + 1) / log (n + 1)
// 汎化。f(x)が変化する関数
y = a + (m - a) * f(x)
f(x) = log(x + 1) / log(n + 1)
// sin(0) ~ sin( PI / 2) : ややゆっくり増える
f(x) = sin(x / n * 3.1415926546 / 2.0)
*/
let ratio:Double = log(Double(x + 1)) / log(interval + 1)
// print(self.timer(), "ratio:\(ratio), ",round(Float(to - from) * ratio), Int(round(Float(to - from) * ratio)))
return Int(Double(from) + round(Double(to - from) * ratio))
}
/// 0(付近)減少する動きの実装
private func decreasingCurrentValueFromZero() -> Int{
let x = Double(min(self.timer() - initialSeconds, interval))
let ratio = sin(Double.pi / 2.0 * x / interval)
var to2 = to
var valueThreshold:Double = 0
if to < DECREASE_SPEED_THRESHOLD {
let thresholdDiff = DECREASE_SPEED_THRESHOLD - to
// 0 に近づくときは、interval - N 秒 かけて DECREASE_SPEED_THRESHOLD まで動いたあと、残りN秒で to に移動する
to2 = DECREASE_SPEED_THRESHOLD
// 残り時間がtimeThreshold ~ 0 のとき、1.0 ~ 0.0 で推移する
let restRatio = min(1.0, (interval - x) / DECREASE_TIME_THRESHOLD)
// thresholdDiff ~ 0 で推移する
valueThreshold = Double(thresholdDiff) * pow(1.0 - restRatio, 2)
// print("diff:\(thresholdDiff), rest:\(restRatio), vThre:\(valueThreshold)")
}
let result = Double(from) + round(Double(to2 - from) * ratio) - valueThreshold
return Int(result)
}
func isFinished() -> Bool {
return self.timer() - initialSeconds > interval
}
}
// 利用例
// var calc:ValueCalculator?
// func start(){
// if (culc != nil){
// // TODO: 既存更新タイマーを止める
// }
// calc = ValueCalculator(from: 0, to: 70)
// // TODO: 更新タイマーをスタート
// }
// func onTimer(){ // タイマー処理。Threadかなにかで定期的に実行する
// setMabeeValue(calc.currentValue())
// if(calc.isFinished()){
// calc = nil
// // TODO: 更新タイマーを止める
// }
// }
// シミュレーション用
var mockTime:TimeInterval = 0
func mockTimer() -> TimeInterval {
return mockTime
}
// let calc = ValueCalculator(from: 0, to: 70, timer:mockTimer)
// let calc = ValueCalculator(from: 40, to: 70, timer:mockTimer)
// let calc = ValueCalculator(from: 70, to: 0, timer:mockTimer)
// let calc = ValueCalculator(from: 70, to: 20, timer:mockTimer)
// let calc = ValueCalculator(from: 70, to: 100, timer:mockTimer)
// let calc = ValueCalculator(from: 90, to: 100, timer:mockTimer)
let calc = ValueCalculator(from: 100, to: 90, timer:mockTimer)
let T = 13 // 計測秒数
let TScale = 2 // 1秒あたりの分割数。2なら0.5刻み、5なら0.2刻み、10なら0.1刻み。
(0...(T * TScale)).forEach{ i in
let x:TimeInterval = TimeInterval(Double(i) / Double(TScale))
mockTime = x
print(String(format:"%2.3f %2d",x, calc.currentValue()))
}
/* 例:コマンドラインでのシミュレーション実行結果:
swift logarithms-value-interpolation.swift
0.000 0
0.500 11
1.000 19
1.500 25
2.000 30
2.500 34
3.000 38
3.500 41
4.000 44
4.500 47
5.000 49
5.500 51
6.000 53
6.500 55
7.000 57
7.500 58
8.000 60
8.500 61
9.000 63
9.500 64
10.00 65
10.50 67
11.00 68
11.500 69
12.000 70
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment