-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathtween.go
136 lines (116 loc) · 4.59 KB
/
tween.go
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
131
132
133
134
135
136
package tween
import "time"
// TransitionFunc calculates the percentage of the transition between the start
// and end values based tween (elapsed time) completion status.
// For example, a linear tween simply has a 1:1 ratio between completed
// and transition percentages and returns the completed value unchanged.
// An "ease in" transition may create a logorithmic relationship between
// the completion time and transition value for the first third, then a
// linear relationship for the remainder.
type TransitionFunc func(completed float64) float64
// Updater is the interface for updating the current value as it tweens between
// start and end values of a Tween.
type Updater interface {
// Start signals the beginning of a tween and is sent before the tweening
// begins. Start may be used to setup or pre-calculate updates.
//
// framerate is the number of frames per second in the tween
// frames is the total number of frames that be generated
// frameTime is the duration for each frame
// runningTime is the total duration for the entire tween
Start(framerate, frames int, frameTime, runningTime time.Duration)
// Update receives information about the current Tween Frame and should be
// used to update output or state.
Update(Frame Frame)
// End signals the end of the tween and is called after all updates.
// End may be used to clean up resources (e.g. update channels).
End()
}
// Frame captures information about the current "frame" of a tween transition.
type Frame struct {
Completed float64 // Completed is the percentage 0.0 - 1.0 of elapsed time.
Transitioned float64 // Transitioned is the percentage 0.0 - 1.0 of transition between start and end values of the tween.
Index int // Index is the current frame index
Elapsed time.Duration // Elapsed is the current elapsed time in the tween.
}
// NewEngine creates a basic tween Engine with a framerate of 60fps.
func NewEngine(duration time.Duration, transition TransitionFunc, updater Updater) *Engine {
return &Engine{
Duration: duration,
Transition: transition,
Updater: updater,
Framerate: 60,
}
}
// Engine runs a tween relying on transitioner and updater.
type Engine struct {
Duration time.Duration // The total duration of the tween.
Framerate int // The number of tween data points per second (defaults to 60 fps - like the real gamers use).
Transition TransitionFunc // Transition calculates the transition curve for the tween.
Updater Updater // Updater updates the tween values for each frame.
running bool // True if the tween is running
done chan int // Internal channel used to terminate the tween early
}
// Start begins the tween running.
func (e *Engine) Start() {
e.done = make(chan int)
// can't stop this thread unless you call Stop() or let the timer
// run out
go func() {
// Setup internal done channel
e.done = make(chan int)
// Based on fps we can calculate how long a frame is:
frameDuration := time.Second / time.Duration(e.Framerate) // The duration in a frame
cutoff := e.Duration - frameDuration // The cutoff point where elapsed time is considered "done"
frames := int(e.Duration / frameDuration) // The number of frames in the duration
// start ticker
e.running = true
e.Updater.Start(e.Framerate, frames, frameDuration, e.Duration)
// Send initial frame
frame := Frame{}
e.Updater.Update(frame)
// set start time
ticker := time.NewTicker(frameDuration)
timeChan := ticker.C
started := time.Now()
for e.running {
select {
case <-timeChan:
frame.Elapsed = time.Since(started)
// Calculate the frame index - some frames can be skipped so
// must find correct time slot for this elapsed time
frame.Index = int(frame.Elapsed / frameDuration)
// Calculate the completed percentage of time
frame.Completed = ((float64(frame.Index) * float64(frameDuration)) / float64(e.Duration))
if frame.Completed > 1 {
go e.Stop()
break
}
// Calulate the completed percentage of the transition
frame.Transitioned = e.Transition(frame.Completed)
// Update the value
e.Updater.Update(frame)
// see if we should keep going
if frame.Elapsed > cutoff {
go e.Stop() // terminate ourself
}
case <-e.done:
ticker.Stop()
e.running = false
}
}
// cleanup
frame.Elapsed = e.Duration
frame.Completed = 1
frame.Transitioned = 1
frame.Index = frames
e.Updater.Update(frame)
e.Updater.End()
}()
}
// Stop terminates the tween immediately.
func (e *Engine) Stop() {
if e.running == true {
close(e.done)
}
}