github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/internal/fling/animation.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package fling 4 5 import ( 6 "math" 7 "runtime" 8 "time" 9 10 "github.com/cybriq/giocore/unit" 11 ) 12 13 type Animation struct { 14 // Current offset in pixels. 15 x float32 16 // Initial time. 17 t0 time.Time 18 // Initial velocity in pixels pr second. 19 v0 float32 20 } 21 22 var ( 23 // Pixels/second. 24 minFlingVelocity = unit.Dp(50) 25 maxFlingVelocity = unit.Dp(8000) 26 ) 27 28 const ( 29 thresholdVelocity = 1 30 ) 31 32 // Start a fling given a starting velocity. Returns whether a 33 // fling was started. 34 func (f *Animation) Start(c unit.Metric, now time.Time, velocity float32) bool { 35 min := float32(c.Px(minFlingVelocity)) 36 v := velocity 37 if -min <= v && v <= min { 38 return false 39 } 40 max := float32(c.Px(maxFlingVelocity)) 41 if v > max { 42 v = max 43 } else if v < -max { 44 v = -max 45 } 46 f.init(now, v) 47 return true 48 } 49 50 func (f *Animation) init(now time.Time, v0 float32) { 51 f.t0 = now 52 f.v0 = v0 53 f.x = 0 54 } 55 56 func (f *Animation) Active() bool { 57 return f.v0 != 0 58 } 59 60 // Tick computes and returns a fling distance since 61 // the last time Tick was called. 62 func (f *Animation) Tick(now time.Time) int { 63 if !f.Active() { 64 return 0 65 } 66 var k float32 67 if runtime.GOOS == "darwin" { 68 k = -2 // iOS 69 } else { 70 k = -4.2 // Android and default 71 } 72 t := now.Sub(f.t0) 73 // The acceleration x''(t) of a point mass with a drag 74 // force, f, proportional with velocity, x'(t), is 75 // governed by the equation 76 // 77 // x''(t) = kx'(t) 78 // 79 // Given the starting position x(0) = 0, the starting 80 // velocity x'(0) = v0, the position is then 81 // given by 82 // 83 // x(t) = v0*e^(k*t)/k - v0/k 84 // 85 ekt := float32(math.Exp(float64(k) * t.Seconds())) 86 x := f.v0*ekt/k - f.v0/k 87 dist := x - f.x 88 idist := int(dist) 89 f.x += float32(idist) 90 // Solving for the velocity x'(t) gives us 91 // 92 // x'(t) = v0*e^(k*t) 93 v := f.v0 * ekt 94 if -thresholdVelocity < v && v < thresholdVelocity { 95 f.v0 = 0 96 } 97 return idist 98 }