-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathindex.js
166 lines (139 loc) · 4.32 KB
/
index.js
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//Generic list of keyframes with timestamps and values
var lerp = require('lerp-array')
var range = require('unlerp')
var vec3 = require('gl-vec3/set')
var temp = [0, 0, 0]
function sort(a, b) {
return a.time - b.time
}
function Keyframes(frames, sorted) {
if (!(this instanceof Keyframes))
return new Keyframes(frames, sorted)
this.frames = frames||[]
if (!sorted)
this.sort()
}
//Finds the index of the nearest keyframe to the given time stamp.
//If radius is specified, it will return the nearest only within that radius
Keyframes.prototype.nearestIndex = function(time, radius) {
radius = typeof radius === 'number' ? radius : Number.MAX_VALUE
var minDist = Number.MAX_VALUE
var nearest = -1
for (var i=0; i<this.frames.length; i++) {
var dist = Math.abs(this.frames[i].time - time)
if (dist < minDist && dist <= radius) {
minDist = dist
nearest = i
}
}
return nearest
}
//Gets the keyframe at the index
Keyframes.prototype.nearest = function(time, radius) {
var idx = this.nearestIndex(time, radius)
return idx === -1 ? null : this.frames[idx]
}
//Gets the keyframe at time
Keyframes.prototype.get = function(time) {
return this.nearest(time, 0)
}
//Gets the keyframe index at time
Keyframes.prototype.getIndex = function(time) {
return this.nearestIndex(time, 0)
}
//lerps the value at the specified time stamp
//returns null if no keyframes exist
Keyframes.prototype.value = function(time, interpolator, out) {
var v = this.interpolation(time)
if (v[0] === -1 || v[1] === -1)
return null
var startFrame = this.frames[ v[0] ]
var endFrame = this.frames[ v[1] ]
var t = v[2]
//We interpolator from left keyframe to right, with a custom easing
//equation if specified
if (typeof interpolator === 'function')
return interpolator(startFrame, endFrame, t, out)
//Otherwise we assume the values are simple numbers and lerp them
return lerp(startFrame.value, endFrame.value, t, out)
}
Keyframes.prototype.interpolation = function(time) {
if (this.frames.length === 0)
return vec3(temp, -1, -1, 0)
var prev = -1
//get last keyframe to time
for (var i=this.frames.length-1; i>=0; i--) {
if (time >= this.frames[i].time) {
prev = i
break
}
}
//start or end keyframes
if (prev === -1 || prev === this.frames.length-1) {
if (prev < 0)
prev = 0
return vec3(temp, prev, prev, 0)
}
else {
var startFrame = this.frames[prev]
var endFrame = this.frames[prev+1]
//clamp and get range
time = Math.max(startFrame.time, Math.min(time, endFrame.time))
var t = range(startFrame.time, endFrame.time, time)
//provide interpolation factor
return vec3(temp, prev, prev+1, t)
}
}
Keyframes.prototype.next = function(time) {
if (this.frames.length < 1)
return null
var cur = -1
//get last keyframe to time
for (var i=0; i<this.frames.length; i++) {
if (time < this.frames[i].time) {
cur = i
break
}
}
return cur===-1 ? null : this.frames[cur]
}
Keyframes.prototype.previous = function(time) {
if (this.frames.length < 1)
return null
var cur = -1
//get last keyframe to time
for (var i=this.frames.length-1; i>=0; i--) {
if (time > this.frames[i].time) {
cur = i
break
}
}
return cur===-1 ? null : this.frames[cur]
}
//Adds a frame at the given time stamp
Keyframes.prototype.add = function(frame) {
this.frames.push(frame)
this.sort()
}
//convenience for .frames.splice
//if items are inserted, a sort will be applied after insertion
Keyframes.prototype.splice = function(index, howmany, itemsN) {
this.frames.splice.apply(this.frames, arguments)
if (arguments.length > 2)
this.sort()
}
//sorts the keyframes. you should do this after
//adding new keyframes that are not in linear time
Keyframes.prototype.sort = function() {
this.frames.sort(sort)
}
//Clears the keyframe list
Keyframes.prototype.clear = function() {
this.frames.length = 0
}
Object.defineProperty(Keyframes.prototype, "count", {
get: function() {
return this.frames.length
}
})
module.exports = Keyframes