caryclark | 3257c12 | 2015-11-16 13:36:08 -0800 | [diff] [blame] | 1 | var animationState = {}; |
| 2 | animationState.reset = function (engine) { |
| 3 | if ('string' === typeof engine) { |
| 4 | this.defaultEngine = engine; |
| 5 | } |
| 6 | this.defaults = {}; |
| 7 | this.displayList = []; |
| 8 | this.displayDict = {}; |
| 9 | this.start = null; |
| 10 | this.time = 0; |
| 11 | this.timeline = []; |
| 12 | this.timelineIndex = 0; |
| 13 | this.requestID = null; |
| 14 | this.paused = false; |
| 15 | this.displayEngine = 'undefined' === typeof engine ? this.defaultEngine : engine; |
| 16 | } |
| 17 | |
| 18 | function addActions(frame, timeline) { |
| 19 | var keyframe = keyframes[frame]; |
| 20 | var len = keyframe.length; |
| 21 | for (var i = 0; i < len; ++i) { |
| 22 | var action = keyframe[i]; |
| 23 | loopOver(action, timeline); |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | function animateList(now) { |
| 28 | if (animationState.paused) { |
| 29 | return; |
| 30 | } |
| 31 | if (animationState.start == null) { |
| 32 | animationState.start = now - animationState.time; |
| 33 | } |
| 34 | animationState.time = now - animationState.start; |
| 35 | var stillAnimating = false; |
| 36 | for (var index = animationState.timelineIndex; index < animationState.timeline.length; ++index) { |
| 37 | var animation = animationState.timeline[index]; |
| 38 | if (animation.time > animationState.time) { |
| 39 | stillAnimating = true; |
| 40 | break; |
| 41 | } |
| 42 | if (animation.time + animation.duration < animationState.time) { |
| 43 | if (animation.finalized) { |
| 44 | continue; |
| 45 | } |
| 46 | animation.finalized = true; |
| 47 | } |
| 48 | stillAnimating = true; |
| 49 | var actions = animation.actions; |
| 50 | for (var aIndex = 0; aIndex < actions.length; ++aIndex) { |
| 51 | var action = actions[aIndex]; |
| 52 | var hasDraw = 'draw' in action; |
| 53 | var hasRef = 'ref' in action; |
| 54 | var displayIndex; |
| 55 | if (hasDraw) { |
| 56 | var ref = hasRef ? action.ref : "anonymous_" + index + "_" + aIndex; |
| 57 | assert('string' == typeof(ref)); |
| 58 | if (ref in animationState.displayDict) { |
| 59 | displayIndex = animationState.displayDict[ref]; |
| 60 | } else { |
| 61 | assert('string' == typeof(action.draw)); |
| 62 | var draw = (new Function("return " + action.draw))(); |
| 63 | assert('object' == typeof(draw)); |
| 64 | var paint; |
| 65 | if ('paint' in action) { |
| 66 | assert('string' == typeof(action.paint)); |
| 67 | paint = (new Function("return " + action.paint))(); |
| 68 | assert('object' == typeof(paint) && !isArray(paint)); |
| 69 | } else { |
| 70 | paint = animationState.defaults.paint; |
| 71 | } |
| 72 | displayIndex = animationState.displayList.length; |
| 73 | animationState.displayList.push( { "ref":ref, "draw":draw, "paint":paint, |
| 74 | "drawSpec":action.draw, "paintSpec":action.paint, |
| 75 | "drawCopied":false, "paintCopied":false, |
| 76 | "drawDirty":true, "paintDirty":true, "once":false } ); |
| 77 | animationState.displayDict[ref] = displayIndex; |
| 78 | } |
| 79 | } else if (hasRef) { |
| 80 | assert('string' == typeof(action.ref)); |
| 81 | displayIndex = animationState.displayDict[action.ref]; |
| 82 | } else { |
| 83 | assert(actions.length == 1); |
| 84 | for (var prop in action) { |
| 85 | if ('paint' == prop) { |
| 86 | assert('string' == typeof(action[prop])); |
| 87 | var obj = (new Function("return " + action[prop]))(); |
| 88 | assert('object' == typeof(obj) && !isArray(obj)); |
| 89 | animationState.defaults[prop] = obj; |
| 90 | } else { |
| 91 | animationState.defaults[prop] = action[prop]; |
| 92 | } |
| 93 | } |
| 94 | continue; |
| 95 | } |
| 96 | var targetSpec = 'target' in action ? action.target : animationState.defaults.target; |
| 97 | assert(targetSpec); |
| 98 | assert('string' == typeof(targetSpec)); |
| 99 | assert(displayIndex < animationState.displayList.length); |
| 100 | var display = animationState.displayList[displayIndex]; |
| 101 | var modDraw = targetSpec.startsWith('draw'); |
| 102 | assert(modDraw || targetSpec.startsWith('paint')); |
| 103 | var modType = modDraw ? "draw" : "paint"; |
| 104 | var copied = modDraw ? display.drawCopied : action.paintCopied; |
| 105 | if (!copied) { |
| 106 | var copy; |
| 107 | if (!modDraw || display.drawSpec.startsWith("text")) { |
| 108 | copy = {}; |
| 109 | var original = modDraw ? display.draw : display.paint; |
| 110 | for (var p in original) { |
| 111 | copy[p] = original[p]; |
| 112 | } |
| 113 | } else if (display.drawSpec.startsWith("paths")) { |
| 114 | copy = []; |
| 115 | for (var i = 0; i < display.draw.length; ++i) { |
| 116 | var curves = display.draw[i]; |
| 117 | var curve = Object.keys(curves)[0]; |
| 118 | copy[i] = {}; |
| 119 | copy[i][curve] = curves[curve].slice(0); // clone the array of curves |
| 120 | } |
| 121 | } else { |
| 122 | assert(display.drawSpec.startsWith("pictures")); |
| 123 | copy = []; |
| 124 | for (var i = 0; i < display.draw.length; ++i) { |
| 125 | var entry = display.draw[i]; |
| 126 | copy[i] = { "draw":entry.draw, "paint":entry.paint }; |
| 127 | } |
| 128 | } |
| 129 | display[modType] = copy; |
| 130 | display[modType + "Copied"] = true; |
| 131 | } |
| 132 | var targetField, targetObject, fieldOffset; |
| 133 | if (targetSpec.endsWith("]")) { |
| 134 | fieldOffset = targetSpec.lastIndexOf("["); |
| 135 | assert(fieldOffset >= 0); |
| 136 | targetField = targetSpec.substring(fieldOffset + 1, targetSpec.length - 1); |
| 137 | var arrayIndex = +targetField; |
| 138 | if (!isNaN(arrayIndex) && targetField.length > 0) { |
| 139 | targetField = arrayIndex; |
| 140 | } |
| 141 | |
| 142 | } else { |
| 143 | fieldOffset = targetSpec.lastIndexOf("."); |
| 144 | if (fieldOffset >= 0) { |
| 145 | targetField = targetSpec.substring(fieldOffset + 1, targetSpec.length); |
| 146 | } else { |
| 147 | targetObject = display; |
| 148 | targetField = targetSpec; |
| 149 | } |
| 150 | } |
| 151 | if (fieldOffset >= 0) { |
| 152 | var sub = targetSpec.substring(0, fieldOffset); |
| 153 | targetObject = (new Function('display', "return display." + sub))(display); |
| 154 | } |
| 155 | assert(null != targetObject[targetField]); |
| 156 | if (!('start' in action) || action.start < animation.time) { |
| 157 | for (var p in animationState.defaults) { |
| 158 | if ('draw' == p || 'paint' == p || 'ref' == p) { |
| 159 | continue; |
| 160 | } |
| 161 | assert('range' == p || 'target' == p || 'formula' == p || 'params' == p); |
| 162 | if (!(p in action)) { |
| 163 | action[p] = animationState.defaults[p]; |
| 164 | } |
| 165 | } |
| 166 | if ('number' == typeof(action.formula)) { |
| 167 | targetObject[targetField] = action.formula; |
| 168 | action.once = true; |
| 169 | } |
| 170 | action.start = animation.time; |
| 171 | } |
| 172 | if (action.once) { |
| 173 | continue; |
| 174 | } |
| 175 | var value = Math.min(1, (animationState.time - animation.time) / animation.duration); |
| 176 | var scaled = action.range[0] + (action.range[1] - action.range[0]) * value; |
| 177 | if ('params' in action) { |
| 178 | if (!('func' in action)) { |
| 179 | if (isArray(action.params)) { |
| 180 | action.funcParams = []; |
| 181 | var len = action.params.length; |
| 182 | for (var i = 0; i < len; ++i) { |
| 183 | action.funcParams[i] = 'target' == action.params[i] |
| 184 | ? targetObject[targetField] |
| 185 | : (new Function("return " + action.params[i]))(); |
| 186 | } |
| 187 | } else { |
| 188 | action.funcParams = 'target' == action.params |
| 189 | ? targetObject[targetField] |
| 190 | : (new Function("return " + action.params))(); |
| 191 | } |
| 192 | assert('formula' in action && 'string' == typeof(action.formula)); |
| 193 | // evaluate inline function to get value |
| 194 | action.func = new Function('value', 'params', "return " + action.formula); |
| 195 | } |
| 196 | scaled = action.func(scaled, action.funcParams); |
| 197 | } |
| 198 | if (targetObject[targetField] != scaled) { |
| 199 | if (modDraw) { |
| 200 | display.drawDirty = true; |
| 201 | } else { |
| 202 | display.paintDirty = true; |
| 203 | } |
| 204 | targetObject[targetField] = scaled; |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | displayBackend(animationState.displayEngine, animationState.displayList); |
| 209 | |
| 210 | if (stillAnimating) { |
| 211 | animationState.requestID = requestAnimationFrame(animateList); |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | function flattenPaint(paint) { |
| 216 | if (!paint.paint) { |
| 217 | return; |
| 218 | } |
| 219 | var parent = paints[paint.paint]; |
| 220 | flattenPaint(parent); |
| 221 | for (var prop in parent) { |
| 222 | if (!(prop in paint)) { |
| 223 | paint[prop] = parent[prop]; |
| 224 | } |
| 225 | } |
| 226 | paint.paint = null; |
| 227 | } |
| 228 | |
| 229 | function init(engine, keyframe) { |
| 230 | animationState.reset(engine); |
| 231 | setupPaint(); |
| 232 | setupBackend(animationState.displayEngine); |
| 233 | keyframeInit(keyframe); |
| 234 | } |
| 235 | |
| 236 | function keyframeInit(frame) { |
| 237 | animationState.reset(); |
| 238 | addActions("_default", animationState.timeline); |
| 239 | addActions(frame, animationState.timeline); |
| 240 | for (var index = 0; index < animationState.timeline.length; ++index) { |
| 241 | animationState.timeline[index].position = index; |
| 242 | } |
| 243 | animationState.timeline.sort(function(a, b) { |
| 244 | if (a.time == b.time) { |
| 245 | return a.position - b.position; |
| 246 | } |
| 247 | return a.time - b.time; |
| 248 | }); |
| 249 | keyframeBackendInit(animationState.displayEngine, animationState.displayList, |
| 250 | keyframes[frame][0]); |
| 251 | animationState.requestID = requestAnimationFrame(animateList); |
| 252 | } |
| 253 | |
| 254 | function loopAddProp(action, propName) { |
| 255 | var funcStr = ""; |
| 256 | var prop = action[propName]; |
| 257 | if ('draw' != propName && isArray(prop)) { |
| 258 | funcStr += '['; |
| 259 | for (var index = 0; index < prop.length; ++index) { |
| 260 | funcStr += loopAddProp(prop, index); |
| 261 | if (index + 1 < prop.length) { |
| 262 | funcStr += ", "; |
| 263 | } |
| 264 | } |
| 265 | funcStr += ']'; |
| 266 | return funcStr; |
| 267 | } |
| 268 | assert("object" != typeof(prop)); |
| 269 | var useString = "string" == typeof(prop) && isAlpha(prop.charCodeAt(0)); |
| 270 | if (useString) { |
| 271 | funcStr += "'"; |
| 272 | } |
| 273 | funcStr += prop; |
| 274 | if (useString) { |
| 275 | funcStr += "'"; |
| 276 | } |
| 277 | return funcStr; |
| 278 | } |
| 279 | |
| 280 | function loopOver(rec, timeline) { |
| 281 | var funcStr = ""; |
| 282 | if (rec.for) { |
| 283 | funcStr += "for (" + rec.for[0] + "; " + rec.for[1] + "; " + rec.for[2] + ") {\n"; |
| 284 | } |
| 285 | funcStr += " var time = " + ('time' in rec ? rec.time : 0) + ";\n"; |
| 286 | funcStr += " var duration = " + ('duration' in rec ? rec.duration : 0) + ";\n"; |
| 287 | funcStr += " var actions = [];\n"; |
| 288 | var len = rec.actions.length; |
| 289 | for (var i = 0; i < len; ++i) { |
| 290 | funcStr += " var action" + i + " = {\n"; |
| 291 | var action = rec.actions[i]; |
| 292 | for (var p in action) { |
| 293 | funcStr += " '" + p + "':"; |
| 294 | funcStr += loopAddProp(action, p); |
| 295 | funcStr += ",\n"; |
| 296 | } |
| 297 | funcStr = funcStr.substring(0, funcStr.length - 2); |
| 298 | funcStr += "\n };\n"; |
| 299 | funcStr += " actions.push(action" + i + ");\n"; |
| 300 | } |
| 301 | funcStr += " timeline.push( { 'time':time, 'duration':duration, 'actions':actions," |
| 302 | + "'finalized':false } );\n"; |
| 303 | if (rec.for) { |
| 304 | funcStr += "}\n"; |
| 305 | } |
| 306 | var func = new Function('rec', 'timeline', funcStr); |
| 307 | func(rec, timeline); |
| 308 | } |
| 309 | |
| 310 | function setupPaint() { |
| 311 | for (var prop in paints) { |
| 312 | flattenPaint(paints[prop]); |
| 313 | } |
| 314 | } |