Home | History | Annotate | Download | only in docs
      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 }
    315