diff --git a/src/Animator.js b/src/Animator.js index 631db7cba..8431a48ec 100644 --- a/src/Animator.js +++ b/src/Animator.js @@ -8,6 +8,7 @@ require('enyo'); var kind = require('./kind'), utils = require('./utils'), + easing = require('./easing'), animation = require('./animation'); var @@ -145,7 +146,7 @@ module.exports = kind( * @default module:enyo/easing~easing.cubicOut * @public */ - easingFunction: animation.easing.cubicOut + easingFunction: easing.cubicOut }, /* diff --git a/src/SceneSupport.js b/src/SceneSupport.js new file mode 100644 index 000000000..870bb7905 --- /dev/null +++ b/src/SceneSupport.js @@ -0,0 +1,22 @@ +var + kind = require('./kind'), + utils = require('./utils'), + scene = require('./scene'); + +var SceneSupport = { + + create: kind.inherit(function(sup) { + var sctor; + return function() { + sup.apply(this, arguments); + sctor = this.scene; + if (sctor) { + sctor = scene(this, sctor); + utils.mixin(sctor, this.sceneOptions); + this.scene = sctor; + } + }; + }) +}; + +module.exports = SceneSupport; diff --git a/src/animation.js b/src/animation.js index 2176e080e..791414e48 100644 --- a/src/animation.js +++ b/src/animation.js @@ -15,7 +15,9 @@ var ms = Math.round(1000/60), cRAF = 'cancelRequestAnimationFrame', cAF = 'cancelAnimationFrame', i, pl, p, wcRAF, wrAF, wcAF, - _requestFrame, _cancelFrame, cancelFrame; + _requestFrame, _cancelFrame, cancelFrame, + core = { ts: 0, obs: {}}; + /* * Fallback on setTimeout @@ -98,63 +100,36 @@ exports.cancelRequestAnimationFrame = function(id) { exports.cancelAnimationFrame = function(id) { return _cancelFrame(id); }; - /** -* A set of interpolation functions for animations, similar in function to CSS3 -* transitions. +* Subcribes for animation frame ticks. * -* These are intended for use with {@link module:enyo/animation#easedLerp}. Each easing function -* accepts one (1) [Number]{@glossary Number} parameter and returns one (1) -* [Number]{@glossary Number} value. +* @param {Object} ctx - The context on which callback is registered. +* @param {Function} callback - A [callback]{@glossary callback} to be executed on tick. +* @public +*/ +exports.subscribe = function(ctx,callback) { + var id = utils.uid("rAF"); + core.obs[id] = utils.bindSafely(ctx, callback); + return id; +}; +/** +* Unsubcribes for animation frame ticks. * +* @param {Object} node - The context on which callback is registered. +* @param {Function} callback - A [callback]{@glossary callback} to be executed on tick. * @public */ -exports.easing = /** @lends module:enyo/animation~easing.prototype */ { - /** - * cubicIn - * - * @public - */ - cubicIn: function(n) { - return Math.pow(n, 3); - }, - /** - * cubicOut - * - * @public - */ - cubicOut: function(n) { - return Math.pow(n - 1, 3) + 1; - }, - /** - * expoOut - * - * @public - */ - expoOut: function(n) { - return (n == 1) ? 1 : (-1 * Math.pow(2, -10 * n) + 1); - }, - /** - * quadInOut - * - * @public - */ - quadInOut: function(n) { - n = n * 2; - if (n < 1) { - return Math.pow(n, 2) / 2; - } - return -1 * ((--n) * (n - 2) - 1) / 2; - }, - /** - * linear - * - * @public - */ - linear: function(n) { - return n; - } +exports.unsubscribe = function(id) { + delete core.obs[id]; +}; + +var startrAF = function(){ + _requestFrame(function (time) { + startrAF(); + core.ts = time; + }.bind(this)); }; +startrAF(); /** * Gives an interpolation of an animated transition's distance from 0 to 1. @@ -206,3 +181,19 @@ exports.easedComplexLerp = function(t0, duration, easing, reverse, time, startVa return easing(lerp, time, startValue, valueChange, duration); } }; + + +//TODO: A temporary implementation for rAF with observers. +Object.defineProperty(core, 'ts', { + + get: function() { + return this.value; + }, + + set: function(newValue) { + for(var i in this.obs){ + this.obs[i](this.value, newValue); + } + this.value = newValue; + } +}); \ No newline at end of file diff --git a/src/easing.js b/src/easing.js new file mode 100644 index 000000000..080decc92 --- /dev/null +++ b/src/easing.js @@ -0,0 +1,281 @@ +/** +* Contains set of interpolation functions for animations, similar in function to CSS3 transitions. +* @module enyo/easing +*/ + +var easing = module.exports = { + /** + * Linear ease with no acceleration + * @public + */ + linear: function(n) { + return n; + }, + /** + * Accelerating with second-degree polynomial. + * @public + */ + quadIn: function(t) { + return t * t; + }, + /** + * Deaccelerating with second-degree polynomial. + * @public + */ + quadOut: function(t) { + return -1 * t * (t - 2); + }, + /** + * Halfway accelerating and then deaccelerating with second-degree polynomial. + * @public + */ + quadInOut: function(n) { + n = n * 2; + if (n < 1) { + return Math.pow(n, 2) / 2; + } + return -1 * ((--n) * (n - 2) - 1) / 2; + }, + /** + * Accelerating with third-degree polynomial. + * @public + */ + cubicIn: function(n) { + return Math.pow(n, 3); + }, + /** + * Deaccelerating with third-degree polynomial. + * @public + */ + cubicOut: function(n) { + return Math.pow(n - 1, 3) + 1; + }, + /** + * Halfway accelerating and then deaccelerating with third-degree polynomial. + * @public + */ + cubicInOut: function(t) { + if ((t *= 2) < 1) return 0.5 * t * t * t; + return 0.5 * ((t -= 2) * t * t + 2); + }, + /** + * Accelerating with fourth-degree polynomial + * @public + */ + quartIn: function(t) { + return t * t * t * t; + }, + /** + * Deaccelerating with fourth-degree polynomial + * @public + */ + quartOut: function(t) { + return -1 * (--t * t * t * t - 1); + }, + /** + * Halfway accelerating and then deaccelerating with fourth-degree polynomial + * @public + */ + quartInOut: function(t) { + if ((t *= 2) < 1) return 0.5 * t * t * t * t; + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + /** + * Accelerating with fifth-degree polynomial + * @public + */ + quintIn: function(t) { + return t * t * t * t * t; + }, + /** + * Deaccelerating with fifth-degree polynomial + * @public + */ + quintOut: function(t) { + return --t * t * t * t * t + 1; + }, + /** + * Halfway accelerating and then deaccelerating with fifth-degree polynomial + * @public + */ + quintInOut: function(t, d) { + if ((t *= 2) < 1) return 0.5 * t * t * t * t * t; + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + /** + * Accelerating using a sine formula + * @public + */ + sineIn: function(t) { + return -1 * Math.cos(t * (Math.PI / 2)) + 1; + }, + /** + * Deaccelerating using a sine formula + * @public + */ + sineOut: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + /** + * Halfway accelerating and then deaccelerating using a sine formula + * @public + */ + sineInOut: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + /** + * Accelerating using an exponential formula + * @public + */ + expoIn: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + /** + * Deaccelerating using an exponential formula + * @public + */ + expoOut: function(n) { + return (n == 1) ? 1 : (-1 * Math.pow(2, -10 * n) + 1); + }, + /** + * Halfway accelerating and then deaccelerating using an exponential formula + * @public + */ + expoInOut: function(t) { + if (t === 0) return 0; + if (t === 1) return 1; + if ((t *= 2) < 1) return 0.5 * Math.pow(2, 10 * (t - 1)); + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + /** + * Accelerating using a circular function + * @public + */ + circIn: function(t) { + return -1 * (Math.sqrt(1 - t * t) - 1); + }, + /** + * Deaccelerating using a circular function + * @public + */ + circOut: function(t) { + return Math.sqrt(1 - (--t * t)); + }, + /** + * Halfway accelerating and then deaccelerating using a circular function + * @public + */ + circInOut: function(t) { + if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - t * t) - 1); + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + /** + * Accelerating with a bouncing effect + * @public + */ + bounceIn: function(t) { + return 1 - easing.bounceOut(1 - t); + }, + /** + * Deaccelerating with a bouncing effect + * @public + */ + bounceOut: function(t) { + if (t < 0.363636) { + return 7.5625 * t * t; + } else if (t < 0.727272) { + return 7.5625 * (t -= 0.545454) * t + 0.75; + } else if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= 0.818182) * t + 0.9375; + } else { + return 7.5625 * (t -= 0.954545) * t + 0.984375; + } + }, + /** + * Halfway accelerating and then deaccelerating with a bouncing effect + * @public + */ + bounceInOut: function(t) { + if (t < 0.5) return easing.bounceIn(t * 2) * 0.5; + return easing.bounceOut(t * 2 - 1) * 0.5 + 0.5; + }, + /** + * Accelerating as a spring oscillating back and forth until it comes to rest + * @public + */ + elasticIn: function(t, d) { + var a = 1, + p = 0, + s = 1.70158; + if (t === 0) return 0; + if (t === 1) return 1; + if (!p) p = d * 0.3; + if (a < 1) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)); + }, + /** + * Deaccelerating as a spring oscillating back and forth until it comes to rest + * @public + */ + elasticOut: function(t, d) { + var a = 1, + p = 0, + s = 1.70158; + if (t === 0) return 0; + if (t === 1) return 1; + if (!p) p = d * 0.3; + if (a < 1) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + 1; + }, + /** + * Halfway accelerating and then deaccelerating as a spring + * oscillating back and forth until it comes to rest + * @public + */ + elasticInOut: function(t, d) { + var a = 1, + p = 0, + s = 1.70158; + if (t === 0) return 0; + if ((t *= 2) === 2) return 1; + if (!p) p = d * (0.3 * 1.5); + if (a < 1) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)); + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + /** + * Accelerating while retracting slightly before it begins to animate in the path indicated + * @public + */ + backIn: function(t, d, s) { + if (!s) s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + /** + * Deaccelerating while retracting slightly before it begins to animate in the path indicated + * @public + */ + backOut: function(t, d, s) { + if (!s) s = 1.70158; + return --t * t * ((s + 1) * t + s) + 1; + }, + /** + * Halfway accelerating and then deaccelerating while retracting + * slightly before it begins to animate in the path indicated + * @public + */ + backInOut: function(t, d, s) { + if (!s) s = 1.70158; + if ((t *= 2) < 1) return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + } +}; \ No newline at end of file diff --git a/src/kind.js b/src/kind.js index 7a479c75c..e8f3ea66a 100644 --- a/src/kind.js +++ b/src/kind.js @@ -1,8 +1,9 @@ require('enyo'); var - logger = require('./logger'), - utils = require('./utils'); + logger = require('./logger'), + scene = require('./scene'), + utils = require('./utils'); var defaultCtor = null; @@ -240,30 +241,31 @@ kind.inherited = function (originals, replacements) { var target = originals.callee; var fn = target._inherited; - // regardless of how we got here, just ensure we actually - // have a function to call or else we throw a console - // warning to notify developers they are calling a - // super method that doesn't exist - if ('function' === typeof fn) { - var args = originals; - if (replacements) { - // combine the two arrays, with the replacements taking the first - // set of arguments, and originals filling up the rest. - args = []; - var i = 0, l = replacements.length; - for (; i < l; ++i) { - args[i] = replacements[i]; - } - l = originals.length; - for (; i < l; ++i) { - args[i] = originals[i]; - } - } - return fn.apply(this, args); - } else { - logger.warn('enyo.kind.inherited: unable to find requested ' + - 'super-method from -> ' + originals.callee.displayName + ' in ' + this.kindName); - } + // regardless of how we got here, just ensure we actually + // have a function to call or else we throw a console + // warning to notify developers they are calling a + // super method that doesn't exist + if ('function' === typeof fn) { + var args = originals; + if (replacements) { + // combine the two arrays, with the replacements taking the first + // set of arguments, and originals filling up the rest. + args = []; + var i = 0, + l = replacements.length; + for (; i < l; ++i) { + args[i] = replacements[i]; + } + l = originals.length; + for (; i < l; ++i) { + args[i] = originals[i]; + } + } + return fn.apply(this, args); + } else { + logger.warn('enyo.kind.inherited: unable to find requested ' + + 'super-method from -> ' + originals.callee.displayName + ' in ' + this.kindName); + } }; // dcl inspired super-inheritance @@ -350,47 +352,48 @@ kind.statics = { */ subclass: function (ctor, props) {}, - /** - * Allows for extension of the current [kind]{@glossary kind} without - * creating a new kind. This method is available on all - * [constructors]{@glossary constructor}, although calling it on a - * [deferred]{@glossary deferred} constructor will force it to be - * resolved at that time. This method does not re-run the - * {@link module:enyo/kind.features} against the constructor or instance. - * - * @name module:enyo/kind.extend - * @method - * @param {Object|Object[]} props A [hash]{@glossary Object} or [array]{@glossary Array} - * of [hashes]{@glossary Object}. Properties will override - * [prototype]{@glossary Object.prototype} properties. If a - * method that is being added already exists, the new method will - * supersede the existing one. The method may call - * `this.inherited()` or be wrapped with `kind.inherit()` to call - * the original method (this chains multiple methods tied to a - * single [kind]{@glossary kind}). - * @param {Object} [target] - The instance to be extended. If this is not specified, then the - * [constructor]{@glossary constructor} of the - * [object]{@glossary Object} this method is being called on will - * be extended. - * @returns {Object} The constructor of the class, or specific - * instance, that has been extended. - * @public - */ - extend: function (props, target) { - var ctor = this - , exts = utils.isArray(props)? props: [props] - , proto, fn; + /** + * Allows for extension of the current [kind]{@glossary kind} without + * creating a new kind. This method is available on all + * [constructors]{@glossary constructor}, although calling it on a + * [deferred]{@glossary deferred} constructor will force it to be + * resolved at that time. This method does not re-run the + * {@link module:enyo/kind.features} against the constructor or instance. + * + * @name module:enyo/kind.extend + * @method + * @param {Object|Object[]} props A [hash]{@glossary Object} or [array]{@glossary Array} + * of [hashes]{@glossary Object}. Properties will override + * [prototype]{@glossary Object.prototype} properties. If a + * method that is being added already exists, the new method will + * supersede the existing one. The method may call + * `this.inherited()` or be wrapped with `kind.inherit()` to call + * the original method (this chains multiple methods tied to a + * single [kind]{@glossary kind}). + * @param {Object} [target] - The instance to be extended. If this is not specified, then the + * [constructor]{@glossary constructor} of the + * [object]{@glossary Object} this method is being called on will + * be extended. + * @returns {Object} The constructor of the class, or specific + * instance, that has been extended. + * @public + */ + extend: function(props, target) { + var ctor = this, + exts = utils.isArray(props) ? props : [props], + proto, fn; fn = function (key, value) { return !(typeof value == 'function' || isInherited(value)) && concatenated.indexOf(key) === -1; }; - proto = target || ctor.prototype; - for (var i=0, ext; (ext=exts[i]); ++i) { - kind.concatHandler(proto, ext, true); - kind.extendMethods(proto, ext, true); - utils.mixin(proto, ext, {filter: fn}); - } + proto = target || ctor.prototype; + for (var i = 0, ext; + (ext = exts[i]); ++i) { + kind.concatHandler(proto, ext, true); + kind.extendMethods(proto, ext, true); + utils.mixin(proto, ext, { filter: fn }); + } return target || ctor; }, @@ -416,17 +419,18 @@ kind.statics = { }; /** -* @method -* @private -*/ -exports.concatHandler = function (ctor, props, instance) { - var proto = ctor.prototype || ctor - , base = proto.ctor; - - while (base) { - if (base.concat) base.concat(ctor, props, instance); - base = base.prototype.base; - } + * @method + * @private + */ +exports.concatHandler = function(ctor, props, instance) { + + var proto = ctor.prototype || ctor, + base = proto.ctor; + + while (base) { + if (base.concat) base.concat(ctor, props, instance); + base = base.prototype.base; + } }; var kindCtors = @@ -440,33 +444,32 @@ var kindCtors = exports._kindCtors = {}; /** -* @method -* @private -*/ -var constructorForKind = exports.constructorForKind = function (kind) { - if (kind === null) { - return kind; - } else if (kind === undefined) { - return getDefaultCtor(); - } - else if (utils.isFunction(kind)) { - return kind; - } - logger.warn('Creating instances by name is deprecated. Name used:', kind); - // use memoized constructor if available... - var ctor = kindCtors[kind]; - if (ctor) { - return ctor; - } - // otherwise look it up and memoize what we find - // - // if kind is an object in enyo, say "Control", then ctor = enyo["Control"] - // if kind is a path under enyo, say "Heritage.Button", then ctor = enyo["Heritage.Button"] || enyo.Heritage.Button - // if kind is a fully qualified path, say "enyo.Heritage.Button", then ctor = enyo["enyo.Heritage.Button"] || enyo.enyo.Heritage.Button || enyo.Heritage.Button - // - // Note that kind "Foo" will resolve to enyo.Foo before resolving to global "Foo". - // This is important so "Image" will map to built-in Image object, instead of enyo.Image control. - ctor = Theme[kind] || (global.enyo && global.enyo[kind]) || utils.getPath.call(global, 'enyo.' + kind) || global[kind] || utils.getPath.call(global, kind); + * @method + * @private + */ +var constructorForKind = exports.constructorForKind = function(kind) { + if (kind === null) { + return kind; + } else if (kind === undefined) { + return getDefaultCtor(); + } else if (utils.isFunction(kind)) { + return kind; + } + logger.warn('Creating instances by name is deprecated. Name used:', kind); + // use memoized constructor if available... + var ctor = kindCtors[kind]; + if (ctor) { + return ctor; + } + // otherwise look it up and memoize what we find + // + // if kind is an object in enyo, say "Control", then ctor = enyo["Control"] + // if kind is a path under enyo, say "Heritage.Button", then ctor = enyo["Heritage.Button"] || enyo.Heritage.Button + // if kind is a fully qualified path, say "enyo.Heritage.Button", then ctor = enyo["enyo.Heritage.Button"] || enyo.enyo.Heritage.Button || enyo.Heritage.Button + // + // Note that kind "Foo" will resolve to enyo.Foo before resolving to global "Foo". + // This is important so "Image" will map to built-in Image object, instead of enyo.Image control. + ctor = Theme[kind] || (global.enyo && global.enyo[kind]) || utils.getPath.call(global, 'enyo.' + kind) || global[kind] || utils.getPath.call(global, kind); // If what we found at this namespace isn't a function, it's definitely not a kind constructor if (!utils.isFunction(ctor)) { diff --git a/src/scene.js b/src/scene.js new file mode 100644 index 000000000..5ec9270d8 --- /dev/null +++ b/src/scene.js @@ -0,0 +1,555 @@ +var + tween = require('./tween'), + utils = require('./utils'), + animation = require('./animation'); +/** + * Contains the declaration for the {@link module:enyo/scene~scene} of an animation. + * @module enyo/scene + */ + +var _ts, _framerate = 16.6; + +var AnimationSupport = { + /** + * Reiterates the animation applied to the component. + * It could be; + * true, for infinite iteration + * [Number], for how many times animation should iterate. + * false, for no repetition + * @public + * @memberOf module:enyo/scene + */ + repeat: false, + /** + * Reduces GPU layers when the animation is completed. + * As most of the transform animations happens on + * GPU layer, ans stays there even after the animations + * is completed. However, need to be carefull while using this + * feature as if the component tends to animate regularly, this + * feature would be an overhead. + * when true, GPU memory is freed + * false, layer remain intact. + * @public + * @memberOf module:enyo/scene + */ + handleLayers: false, + /** + * Indentifies whether the animation is in progress or not. + * when true, animation is in progress + * false, otherwise. + * @public + * @memberOf module:enyo/scene + */ + animating: false, + /** + * Specifies the rate at which animation should be played. + * When 0, animation is still + * 1, animation plays with normal speed + * 2, animation plays with 2X fast speed + * 0.5, animation plays with slow speed at half of normal speed. + * @public + * @memberOf module:enyo/scene + */ + direction: 0, + /** + * Specifies the rate at which animation should be played. + * When 0, animation is still + * 1, animation plays with normal speed + * 2, animation plays with 2X fast speed + * 0.5, animation plays with slow speed at half of normal speed. + * @public + * @memberOf module:enyo/scene + */ + speed: 0, + /** + * Moves animation to a particular point within the span of + * an animation. Its value could lie between 0 and total duration + * of the animation. + * @public + * @memberOf module:enyo/scene + */ + seekInterval: 0, + /** + * Plays animation in sequence when set to true else + * its a parallal animation. This could be applied for + * animation properties as well as for scenes within a + * scene. + * @public + * @memberOf module:enyo/scene + */ + isSequence: true, + /** + * Starts animation when scene is initialized, + * when this property is set to true. When false scene instance has + * to be explicity played using 'play' api. + * @public + * @memberOf module:enyo/scene + */ + autoPlay: true, + /** + * The limit for an animation, which could be an instance + * of time as well as distance. + * @public + * @memberOf module:enyo/scene + */ + span: 0, + /** + * The current time state of animation. This represents the + * time at which animation is progressed upon. As this property + * directly impacts the state of animation, updating this value + * have direct effect on animation unless its animation is halted. + * The range lies between 0 to overall span of animation. + * @public + * @memberOf module:enyo/scene + */ + timeline: 0, + /** + * Starts the animation of scene. + * @public + * @memberOf module:enyo/scene + */ + play: function() { + this.direction = this.speed = 1; + if (isNaN(this.timeline) || !this.timeline) { + this.timeline = 0; + } + this.animating = true; + return this; + }, + + /** + * Resumes the paused animation of scene. + * @memberOf module:enyo/scene + * @public + */ + resume: function() { + this.direction = 1; + return this; + }, + + /** + * Pauses the animation of scene. + * @memberOf module:enyo/scene + * @public + */ + pause: function() { + this.direction = 0; + return this; + }, + + /** + * Reverses the animation of scene. + * @memberOf module:enyo/scene + * @public + */ + reverse: function() { + this.direction = -1; + }, + + /** + * Stops the animation of scene. + * @memberOf module:enyo/scene + * @public + */ + stop: function() { + this.speed = 0; + this.timeline = 0; + }, + + /** + * Seeks the animation to the position provided in seek + * The value of seek should be between 0 to duration of the animation. + * @param {Number} seek where the animation has to be seeked + * @memberOf module:enyo/scene + * @public + */ + seek: function(seek) { + this.timeline = seek; + }, + + /** + * Seeks the animation to the position provided in seek with animation + * The value of seek should be between 0 to duration of the animation. + * @param {Number} seek where the animation has to be seeked + * @memberOf module:enyo/scene + * @public + */ + seekAnimate: function(seek) { + if (seek >= 0) { + if (!this.animating) + this.play(); + this.speed = 1; + } else { + this.speed = -1; + } + this.seekInterval = this.timeline + seek; + if (this.seekInterval < 0) { + this.speed = 0; + this.seekInterval = 0; + } + }, + + //TODO: Move these events to Event Delegator + /** + * Event to identify when the scene has done animating. + * @memberOf module:enyo/scene + * @public + */ + completed: function() {}, + + /** + * Event to identify when the scene has done a step(rAF updatation of time) in the animation. + * @memberOf module:enyo/scene + * @public + */ + step: function() {} +}; + +/** + * Interface which accepts the animation details and returns a scene object + * @param {Object} actor component which has to be animated + * @param {Object} props properties of the component + * @param {Object} opts additional options + * @return {Object} A scene object + */ +module.exports = function (proto, properties, opts) { + var i, ctor, ps, s; + + if (!utils.isArray(proto)) { + ps = new scene(proto, properties, opts); + } else { + ps = new scene(); + if (opts) utils.mixin(ps, opts); + for (i = 0; + (ctor = proto[i]); i++) { + s = new scene(ctor, properties); + ps.addScene(s); + } + } + + ps.autoPlay && ps.play(); + return ps; +}; + +/** + * {@link module:enyo/Scene~Scene} + * + * @class Scene + * @extends module:enyo/Scene~Scene + * @param {Object} actor component which has to be animated + * @param {Object} props properties of the component + * @param {Object} opts additional options + * @public + */ +function scene(actor, props, opts) { + this.id = utils.uid("@"); + this.poses = []; + this.rolePlays = []; + this.actor = actor; + + this.rAFId = animation.subscribe(this, loop); + utils.mixin(this, AnimationSupport); + if (opts) utils.mixin(this, opts); + + if (props) { + var anims = utils.isArray(props) ? props : [props]; + for (var i = 0, anim; + (anim = anims[i]); i++) { + if (anim.delay) this.addAnimation({}, anim.delay, actor.duration); + this.addAnimation(anim, anim.duration || 0, actor.duration); + } + } +} + +/** + * Checks whether the scene is in a active state, which then can be animated + * @return {Boolean} generated value in true or false. true in case of the parent scene + * @memberOf module:enyo/scene + * @public + */ +scene.prototype.isActive = function() { + if (this.actor) + return this.actor.generated && !this.actor.destroyed; + + // making sure parent scenes are always active. + return true; +}; + +/** + * Sets the new animations with the properties passed. + * @param {Object} properties Animation properties + * @memberOf module:enyo/scene + * @public + */ +scene.prototype.setAnimation = function(properties) { + var currentPose = findScene(this.poses, "poses"); + setScene(currentPose, this.timeline, properties); +}; + +/** + * Gets the current animation pose. + * @param {Number} index animation index + * @return {Object} pose pose with the passed index + * @memberOf module:enyo/scene + * @public + */ +scene.prototype.getAnimation = function(index) { + return index < 0 || this.poses[index]; +}; + +/** + * addAnimation is used for adding new animation with the passed properties at run time. + * @param {Object} newProp animation properties for new animation + * @param {Number} span span between the animation + * @param {Number} dur duration for the new animation + * @memberOf module:enyo/scene + * @public + */ +scene.prototype.addAnimation = function(newProp, span, dur) { + dur = dur || this.span; + var l = this.poses.length, + old = 0, + spanCache = span.toString().match(/%$/) ? (span.replace(/%$/, '') * dur / 100) : span, + newSpan = newProp instanceof this.constructor ? newProp.span : spanCache; + + if (l > 0 && this.isSequence) { + old = this.poses[l - 1].span; + newSpan += old; + } + this.poses.push({ + animate: newProp, + span: newSpan, + begin: old + }); + this.span = newSpan; +}; + +/** + * Add a new animation scene for the animation. + * @param {Object} sc scene which has to be added + * @memberOf module:enyo/scene + * @public + */ +scene.prototype.addScene = function(sc) { + var l = this.poses.length, + old = 0, + newSpan = sc instanceof this.constructor ? sc.span : 0; + + if (l > 0 && this.isSequence) { + old = this.poses[l - 1].span; + newSpan += old; + sc.span = newSpan; + sc.begin = old; + } + + this.poses.push(sc); + this.span = newSpan; +}; + +/** + * @private + */ +function action(ts, pose) { + var tm, i, poses, + dur = this.span, + actor = this.actor; + + if (this.isActive()) { + tm = rolePlay(ts, this); + if (isNaN(tm) || tm < 0) return pose; + + poses = posesAtTime(this.poses, tm > dur ? dur : tm); + for (i = 0, pose; + (pose = poses[i]); i++) { + if (pose instanceof this.constructor) { + pose.speed = this.speed; + pose.direction = this.direction; + pose.handleLayers = this.handleLayers; + action.call(pose, ts); + } else { + update(pose, actor, (tm - pose.begin), (pose.span - pose.begin)); + } + } + this.step && this.step(actor); + + if (tm > dur) cut.call(this, actor); + } + return pose; +} + +/** + * @private + */ +function cut(actor) { + this.repeat = REPEAT[this.repeat] || this.repeat; + this.timeline = --this.repeat ? 0 : this.span; + + if (this.repeat > 0) { + applyInitialStyle(this); + return; + } else { + if (FILLMODE[this.fillmode]) { + applyInitialStyle(this); + } + } + + if (this.handleLayers) { + this.speed = 0; + if (this.active) { + this.active = false; + tween.halt(actor); + } + } + this.animating = false; + this.completed && this.completed(actor); +} + +/** + * @private + */ +function loop(was, is) { + if (this.animating) { + _ts = is - (was || 0); + _ts = (_ts > _framerate) ? _framerate : _ts; + action.call(this, _ts); + } else if (this.actor && this.actor.destroyed) { + animation.unsubscribe(this.rAFId); + } +} + +/** + * @private + */ +function update(pose, actor, since, dur) { + var t; + if (!pose._startAnim) tween.init(actor, pose); + if (since < 0) since = 0; + if (since <= dur && dur !== 0) { + t = since / dur; + tween.step(actor, pose, t, dur); + } else { + tween.step(actor, pose, 1, dur); + } +} + +/** + * rolePlay updated the timeline of the actor which is currently animating. + * @param {Number} t Elapsed time since the animation of this pose has started (ratio in factor of 1) + * @param {@link module:enyo/Component~Component} actor The component which is animating + * @return {Number} Returns the updated timeline of the actor + * @private + */ +function rolePlay(t, actor) { + actor = actor || this; + t = t * actor.speed * actor.direction; + + actor.timeline += t; + if (actor.seekInterval !== 0) { + if ((actor.seekInterval - actor.timeline) * actor.speed < 0) { + actor.seekInterval = 0; + actor.speed = 0; + } + } + + if (actor.timeline === undefined || actor.timeline < 0) + actor.timeline = 0; + return actor.timeline; +} + +/** + * Returns animation pose index for a particular + * instance of time from the list of + * animations added to the scene. + * @param {number} span - Time span from the animation timeline + * @return {number} - index of the animation + * @private + */ +function posesAtTime(anims, span) { + function doFilter(val, idx, ar) { + return span > (val.begin || 0) && span <= val.span; + } + return anims.filter(doFilter); +} + +/** + * @private + */ +function modify(pose, currentTm) { + pose.span = currentTm; + delete pose._endAnim; + pose._endAnim = pose.currentState; + return pose; +} +/** + * @private + */ +function setScene(poseArr, tm, properties) { + var currentTime = tm; + for (var i = 0; i < poseArr.length; i++) { + + if (poseArr[i].begin <= currentTime && poseArr[i].span >= currentTime) { // check the current Pose + modify(poseArr[i], currentTime); + poseArr.splice((i + 1), poseArr.length - (i + 1)); + poseArr[(i + 1)] = { + animate: properties, + begin: currentTime, + span: currentTime + properties.duration + }; + break; + } + } +} +/** + * @private + */ +function hasScene(poseArr, propCheck) { + var bool; + if (!utils.isArray(poseArr)) { + bool = poseArr[propCheck] ? true : false; + } else { + for (var i = 0; i < poseArr.length; i++) { + bool = poseArr[i][propCheck] ? true : false; + if (bool) break; + } + } + return bool; +} +/** + * @private + */ +function findScene(poseArr, propCheck) { + var parentNode, currNode = poseArr; + if (hasScene(poseArr, propCheck)) { + if (utils.isArray(poseArr)) { + for (var i = 0; i < poseArr.length; i++) { + parentNode = currNode[i]; + currNode = findScene(currNode[i].poses, propCheck); + } + } else { + parentNode = currNode; + currNode = findScene(currNode.poses, propCheck); + } + } + return parentNode ? parentNode[propCheck] : parentNode; +} + +/** + * @private + */ +function applyInitialStyle(node) { + node = node.actor ? node : findScene(node.poses, "actor"); + node = node.actor || node; + node.addStyles(node.initialState); +} + +var + REPEAT = { + 'true': Infinity, + 'false': 0 + }, + FILLMODE = { + 'backwards': true, + 'forwards': false, + 'default': false, + 'none': false + }; \ No newline at end of file diff --git a/src/transform.js b/src/transform.js new file mode 100644 index 000000000..b9ba52ea9 --- /dev/null +++ b/src/transform.js @@ -0,0 +1,735 @@ +/** +* Contains the declaration for the {@link module:enyo/transform~transform} kind. +* @module enyo/transform +*/ +require('enyo'); + +/** + * To create a Typed_array + * @param {Number} size The size of the buffer required + * @return {Number[]} Typed_array + */ +function typedArray (size) { + return new Float32Array(new ArrayBuffer(size)); +} + +/** + * To input the specified indices with value 1 + * @param {Number[]} matrix typedArray sent + * @param {Number[]} numberMat indices where value has to be 1 + */ +function inputValues (matrix, numberMat) { + for (var i = 0; i < numberMat.length; i++) { + matrix[numberMat[i]] = 1; + } +} + + +/** + * To translate in any dimension based on co-ordinates. + * @public + * @param {Number} x Translate value in X axis + * @param {Number} y Translate value in Y axis + * @param {Number} z Translate value in Z axis + * @return {Number[]} Matrix3d + */ +exports.translate = function(x, y, z) { + var translateMat, modifiedMat; + translateMat = typedArray(64); + modifiedMat = inputValues(translateMat, new Uint8Array([0, 5, 10, 15])); + translateMat[12] = x; + translateMat[13] = y ? y : 0; + translateMat[14] = z ? z : 0; + return translateMat; +}; + +/** + * To translate in x dimension + * @public + * @param {Number} x Translate value in X axis + * @return {Number[]} Matrix3d + */ +exports.translateX = function(x) { + var translateX, modifiedMat; + translateX = typedArray(64); + modifiedMat = inputValues(translateX, new Uint8Array([0, 5, 10, 15])); + translateX[12] = x ? x : 0; + return translateX; +}; + +/** + * To translate in y dimension + * @public + * @param {Number} y Translate value in Y axis + * @return {Number[]} Matrix3d + */ +exports.translateY = function(y) { + var translateY, modifiedMat; + translateY = typedArray(64); + modifiedMat = inputValues(translateY, new Uint8Array([0, 5, 10, 15])); + translateY[13] = y ? y : 0; + return translateY; +}; + +/** + * To translate in z dimension + * @public + * @param {Number} z Translate value in Z axis + * @return {Number[]} Matrix3d + */ +exports.translateZ = function(z) { + var translateZ, modifiedMat; + translateZ = typedArray(64); + modifiedMat = inputValues(translateZ, new Uint8Array([0, 5, 10, 15])); + translateZ[14] = z ? z : 0; + return translateZ; +}; + +/** + * To scale in any dimension + * @public + * @param {Number} x Scale value in X axis + * @param {Number} y Scale value in Y axis + * @param {Number} z Scale value in Z axis + * @return {Number[]} Matrix3d + */ +exports.scale = function(x, y, z) { + var scaleMat = typedArray(64); + scaleMat[0] = x; + scaleMat[5] = y ? y : 1; + scaleMat[10] = z ? z : 1; + scaleMat[15] = 1; + return scaleMat; +}; + +/** + * To skew in any dimension (skew can only happen in 2d) + * @public + * @param {Number} a Skew value in X axis + * @param {Number} b Skew value in Y axis + * @return {Number[]} Matrix3d + */ +exports.skew =function(a, b) { + var skewMat, modifiedMat; + a = a ? Math.tan(a * Math.PI / 180) : 0; + b = b ? Math.tan(b * Math.PI / 180) : 0; + + skewMat = typedArray(64); + modifiedMat = inputValues(skewMat, new Uint8Array([0, 5, 10, 15])); + skewMat[1] = b; + skewMat[4] = a; + return skewMat; +}; + +/** + * To rotate in x-axis + * @public + * @param {Number} a Rotate value in X axis + * @return {Number[]} Matrix3d + */ +exports.rotateX = function(a) { + var cosa, sina, rotateXMat, modifiedMat; + a = a ? a * Math.PI / 180 : 0; + cosa = Math.cos(a); + sina = Math.sin(a); + + rotateXMat = typedArray(64); + modifiedMat = inputValues(rotateXMat, new Uint8Array([0, 15])); + rotateXMat[5] = cosa; + rotateXMat[6] = -sina; + rotateXMat[9] = sina; + rotateXMat[10] = cosa; + return rotateXMat; +}; + +/** + * To rotate in y-axis + * @public + * @param {Number} b Rotate value in Y axis + * @return {Number[]} Matrix3d + */ +exports.rotateY = function(b) { + var cosb, sinb, rotateYMat, modifiedMat; + b = b ? b * Math.PI / 180 : 0; + cosb = Math.cos(b); + sinb = Math.sin(b); + + rotateYMat = typedArray(64); + modifiedMat = inputValues(rotateYMat, new Uint8Array([5, 15])); + rotateYMat[0] = cosb; + rotateYMat[2] = sinb; + rotateYMat[8] = -sinb; + rotateYMat[10] = cosb; + return rotateYMat; +}; + +/** + * To rotate in z-axis + * @public + * @param {Number} g Rotate value in Z axis + * @return {Number[]} Matrix3d + */ +exports.rotateZ = function(g) { + var cosg, sing, rotateZMat; + g = g ? g * Math.PI / 180 : 0; + cosg = Math.cos(g); + sing = Math.sin(g); + + rotateZMat = typedArray(64); + rotateZMat[0] = cosg; + rotateZMat[1] = -sing; + rotateZMat[4] = sing; + rotateZMat[5] = cosg; + rotateZMat[15] = 1; + return rotateZMat; +}; + +/** + * To rotate in any dimension + * @public + * @param {Number} a Rotate value in X axis + * @param {Number} b Rotate value in Y axis + * @param {Number} g Rotate value in Z axis + * @return {Number[]} Matrix3d + */ +exports.rotate = function(a, b, g) { + var ca, sa, cb, sb, cg, sg, rotateMat; + a = a ? a * Math.PI / 180 : 0; + b = b ? b * Math.PI / 180 : 0; + g = g ? g * Math.PI / 180 : 0; + ca = Math.cos(a); + sa = Math.sin(a); + cb = Math.cos(b); + sb = Math.sin(b); + cg = Math.cos(g); + sg = Math.sin(g); + + rotateMat = typedArray(64); + rotateMat[0] = cb * cg; + rotateMat[1] = ca * sg + sa * sb * cg; + rotateMat[2] = sa * sg - ca * sb * cg; + rotateMat[4] = -cb * sg; + rotateMat[5] = ca * cg - sa * sb * sg; + rotateMat[6] = sa * cg + ca * sb * sg; + rotateMat[8] = sb; + rotateMat[9] = -sa * cb; + rotateMat[10] = ca * cb; + rotateMat[15] = 1; + return rotateMat; +}; + +exports.Matrix = { + + /** + * To create Identity Matrix3d as array. + * @public + * @return {Number[]} Identity Matrix3d + */ + identity: function() { + var identityMatrix, modifiedMat; + identityMatrix = typedArray(64); + modifiedMat = inputValues(identityMatrix, new Uint8Array([0, 5, 10, 15])); + return identityMatrix; + }, + + /** + * To create Identity Matrix2d as array. + * @public + * @return {Number[]} Identity Matrix2d as array + */ + identity2D: function() { + var identity2D, modifiedMat; + identity2D = typedArray(36); + modifiedMat = inputValues(identity2D, new Uint8Array([0, 4, 8])); + return identity2D; + }, + + + /** + * To create Identity Matrix (NXN order). + * @public + * @param {Number} N Order of Identity Matrix + * @return {Number[][]} Identity Matrix of order N + */ + identityMatrix: function(N) { + var i, j, row, result = []; + for (i = 0; i < N; i++) { + row = []; + for (j = 0; j < N; j++) { + if (i === j) { + row.push(1); + } else { + row.push(0); + } + } + result.push(row); + } + return result; + }, + /** + * To multiply 2 Martix3d (4x4 order) + * @public + * @param {Number[]} m1 1st Matrix3d + * @param {Number[]} m2 2nd Matrix3d + * @return {Number[]} Resultant Matrix3d + */ + multiply: function(m1, m2) { + if (m1.length !== 16 || m2.length !== 16) return; + var multiplyMat = typedArray(64); + multiplyMat[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2]; + multiplyMat[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2]; + multiplyMat[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2]; + multiplyMat[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6]; + multiplyMat[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6]; + multiplyMat[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6]; + multiplyMat[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10]; + multiplyMat[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10]; + multiplyMat[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10]; + multiplyMat[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12]; + multiplyMat[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13]; + multiplyMat[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14]; + multiplyMat[15] = 1; + return multiplyMat; + }, + + /** + * To multiply 2 Martix3d (n*n order) + * @param {Number[]} m1 1st Matrix3d + * @param {Number[]} m2 2nd Matrix3d + * @return {Number[]} Resultant Matrix3d + */ + multiplyN: function(m1, m2) { + var i, j, sum, + m = [], + l1 = m1.length, + l2 = m2.length; + + for (i = 0; i < l1; i++) { + sum = 0; + for (j = 0; j < l2; j++) { + sum += m1[i][j] * m2[j]; + } + m.push(sum); + } + return m; + }, + + /** + * To inverse matrix of order N + * @public + * @param {Number[]} matrix Matrix (NxN order) + * @param {Number} n Order of the matrix + * @return {Number[]} Inverted Matrix + */ + inverseN: function(matrix, n) { + var i, j, k, r, t, + result = this.identityMatrix(n); + + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + if (i != j) { + r = matrix[j][i] / matrix[i][i]; + for (k = 0; k < n; k++) { + matrix[j][k] -= r * matrix[i][k]; + result[j][k] -= r * result[i][k]; + } + } + } + } + + for (i = 0; i < n; i++) { + t = matrix[i][i]; + for (j = 0; j < n; j++) { + matrix[i][j] = matrix[i][j] / t; + result[i][j] = result[i][j] / t; + } + } + + return result; + }, + + /** + * Calculate matrix3d of a frame based on transformation vectors. + * @public + * @param {Number[]} trns Translate vector + * @param {Number[]} rot Rotate quaternion vector + * @param {Number[]} sc Scale vector + * @param {Number[]} sq Skew vector + * @param {Number[]} per Perspective vector + * @return {Number[]} Final Matrix3d for particular frame + */ + recompose: function(trns, rot, sc, sq, per) { + var i, + x = rot[0], + y = rot[1], + z = rot[2], + w = rot[3], + m = this.identity(), + sM = this.identity(), + rM = this.identity(); + + + // apply perspective + if (per) { + m[3] = per[0]; + m[7] = per[1]; + m[11] = per[2]; + m[15] = per[3]; + } + + m[12] = trns[0]; + m[13] = trns[1]; + m[14] = trns[2]; + + // apply rotate + rM[0] = 1 - 2 * (y * y + z * z); + rM[1] = 2 * (x * y - z * w); + rM[2] = 2 * (x * z + y * w); + rM[4] = 2 * (x * y + z * w); + rM[5] = 1 - 2 * (x * x + z * z); + rM[6] = 2 * (y * z - x * w); + rM[8] = 2 * (x * z - y * w); + rM[9] = 2 * (y * z + x * w); + rM[10] = 1 - 2 * (x * x + y * y); + + m = this.multiply(m, rM); + + // apply skew + if (sq[2]) { + sM[9] = sq[2]; + m = this.multiply(m, sM); + } + + if (sq[1]) { + sM[9] = 0; + sM[8] = sq[1]; + m = this.multiply(m, sM); + } + + if (sq[0]) { + sM[8] = 0; + sM[4] = sq[0]; + m = this.multiply(m, sM); + } + + // apply scale + for (i = 0; i < 12; i += 4) { + m[0 + i] *= sc[0]; + m[1 + i] *= sc[1]; + m[2 + i] *= sc[2]; + } + return m; + }, + + /** + * Decompose transformation vectors into various properties out of matrix3d. + * @public + * @param {Number[]} matrix Matrix3d + * @param {Object} ret To store various transformation properties like translate, rotate, scale, skew and perspective. + * @return {Boolean} true, if matrix exists else false. + */ + decompose: function(matrix, ret) { + if (matrix[15] === 0) return false; + var i, + tV = [], + rV = [], + pV = [], + skV = [], + scV = [], + row = [], + pdum3 = []; + + for (i = 0; i < 16; i++) + matrix[i] /= matrix[15]; + + //TODO: decompose perspective + pV = [0, 0, 0, 0]; + + for (i = 0; i < 3; i++) + tV[i] = matrix[12 + i]; + + for (i = 0; i < 12; i += 4) { + row.push([ + matrix[0 + i], + matrix[1 + i], + matrix[2 + i] + ]); + } + + scV[0] = vector.len(row[0]); + row[0] = quaternion.normalize(row[0]); + skV[0] = vector.dot(row[0], row[1]); + row[1] = vector.combine(row[1], row[0], 1.0, -skV[0]); + + scV[1] = vector.len(row[1]); + row[1] = quaternion.normalize(row[1]); + skV[0] /= scV[1]; + + // Compute XZ and YZ shears, orthogonalized 3rd row + skV[1] = vector.dot(row[0], row[2]); + row[2] = vector.combine(row[2], row[0], 1.0, -skV[1]); + skV[2] = vector.dot(row[1], row[2]); + row[2] = vector.combine(row[2], row[1], 1.0, -skV[2]); + + // Next, get Z scale and normalize 3rd row. + scV[2] = vector.len(row[2]); + row[2] = quaternion.normalize(row[2]); + skV[1] /= scV[2]; + skV[2] /= scV[2]; + + pdum3 = vector.cross(row[1], row[2]); + if (vector.dot(row[0], pdum3) < 0) { + for (i = 0; i < 3; i++) { + scV[i] *= -1; + row[i][0] *= -1; + row[i][1] *= -1; + row[i][2] *= -1; + } + } + + rV[0] = 0.5 * Math.sqrt(Math.max(1 + row[0][0] - row[1][1] - row[2][2], 0)); + rV[1] = 0.5 * Math.sqrt(Math.max(1 - row[0][0] + row[1][1] - row[2][2], 0)); + rV[2] = 0.5 * Math.sqrt(Math.max(1 - row[0][0] - row[1][1] + row[2][2], 0)); + rV[3] = 0.5 * Math.sqrt(Math.max(1 + row[0][0] + row[1][1] + row[2][2], 0)); + + if (row[2][1] > row[1][2]) rV[0] = -rV[0]; + if (row[0][2] > row[2][0]) rV[1] = -rV[1]; + if (row[1][0] > row[0][1]) rV[2] = -rV[2]; + + ret.translate = tV; + ret.rotate = rV; + ret.scale = scV; + ret.skew = skV; + ret.perspective = pV; + return true; + }, + + /** + * Decompose transformation matrix2d from matrix3d. + * @public + * @param {Number[]} matrix Matrix3d + * @param {Object} ret To store various transformation properties like translate, angle and matrix. + * @return {Boolean} ret To store various transformation properties like translate, angle and matrix. + */ + decompose2D: function(m, ret) { + var scale = [], + matrix = [], + row0x = m[0], + row0y = m[1], + row1x = m[4], + row1y = m[5], + det, angle, sn, cs, + m11, m12, m21, m22; + + ret = ret || {}; + scale = [ + Math.sqrt(row0x * row0x + row0y * row0y), + Math.sqrt(row1x * row1x + row1y * row1y) + ]; + + // If determinant is negative, one axis was flipped. + det = row0x * row1y - row0y * row1x; + if (det < 0) + // Flip axis with minimum unit vector dot product. + if (row0x < row1y) + scale[0] = -scale[0]; + else + scale[1] = -scale[1]; + + // Renormalize matrix to remove scale. + if (scale[0]) { + row0x *= 1 / scale[0]; + row0y *= 1 / scale[0]; + } + + if (scale[1]) { + row1x *= 1 / scale[1]; + row1y *= 1 / scale[1]; + } + ret.scale = scale; + + + // Compute rotation and renormalize matrix. + angle = Math.atan2(row0y, row0x); + + if (angle) { + sn = -row0y; + cs = row0x; + m11 = row0x; + m12 = row0y; + m21 = row1x; + m22 = row1y; + row0x = cs * m11 + sn * m21; + row0y = cs * m12 + sn * m22; + row1x = -sn * m11 + cs * m21; + row1y = -sn * m12 + cs * m22; + } + + // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)] + // = [row0x, -row0y, row0y, row0x] + // Thanks to the normalization above. + matrix[0] = row0x; + matrix[1] = row0y; + matrix[2] = row1x; + matrix[3] = row1y; + matrix[4] = m[12]; + matrix[5] = m[13]; + ret.matrix2D = matrix; + + // Convert into degrees because our rotation functions expect it. + ret.angle = angle * 180 / Math.PI; + + return ret; + }, + + /** + * Convert Matrix3d array to Matrix3d String + * @public + * @param {Number[]} m Matrix3d Array + * @return {String} Matrix3d String + */ + toString: function(m) { + var i, ms = m.length > 10 ? 'matrix3d(' : 'matrix('; + for (i = 0; i < m.length - 1; i++) { + ms += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ','; + } + ms += m[m.length - 1] + ')'; + return ms; + } +}; + +var vector = exports.Vector = { + /** + * Length of a vector + * @param {Number[]} v - vector + * @return {Number} resultant length + * @public + */ + len: function(v) { + return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + }, + + /** + * Divides vector with a scalar value. + * @param {Number[]} v - vector + * @param {Number} s - scalar value to divide + * @return {Number[]} resultant vector + * @public + */ + divide: function(v, s) { + var divideVector = new Float32Array([v[0] / s, v[1] / s, v[2] / s]); + return divideVector; + }, + + /** + * Dot product of 3D vectors + * @param {Number[]} v1 - vector + * @param {Number[]} v2 - vector + * @return {Number} resultant dot product + * @public + */ + dot: function(v1, v2) { + return (v1[0] * v2[0]) + (v1[1] * v2[1]) + (v1[2] * v2[2]) + (v1[3] !== undefined && v2[3] !== undefined ? (v1[3] * v2[3]) : 0); + }, + + /** + * Cross product of two vectors + * @param {Number[]} v1 - vector + * @param {Number[]} v2 - vector + * @return {Number[]} resultant cross product + * @public + */ + cross: function(v1, v2) { + var crossProdMat = new Float32Array([v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0]]); + return crossProdMat; + }, + + /** + * Combine scalar values with two vectors. + * Required during parsing scaler values matrix. + * @param {Number[]} a - first vector + * @param {Number[]} b - second vector + * @param {Number[]} ascl - first vector scalar + * @param {Number[]} bscl - second vector scalar + * @return {Number[]} resultant vector + * @public + */ + combine: function(a, b, ascl, bscl) { + var combineMat = new Float32Array([(ascl * a[0]) + (bscl * b[0]), (ascl * a[1]) + (bscl * b[1]), (ascl * a[2]) + (bscl * b[2])]); + return combineMat; + + } +}; + +var quaternion = exports.Quaternion = { + /** + * Gives the direction of motion from one vector to other. + * Returns true if moving towards positive direction. + * @param {Number[]} q1 - quant + * @param {Number[]} q2 - quant + * @return {boolean} true if positive, false otherwise. + * @public + */ + direction: function(q1, q2) { + return (q1[0] - q2[0]) < 0 || (q1[1] - q2[1]) < 0 || (q1[2] - q2[2]) < 0; + }, + + /** + * Dot product of 3D quanterion + * @param {Number[]} q1 - quanterion + * @param {Number[]} q2 - quanterion + * @return {Number} resultant dot product + * @public + */ + quantDot: function(q1, q2) { + return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] * q2[3]); + }, + + multiplication: function(q1, q2) { + return [q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2] - q2[3] * q1[3], + q2[0] * q1[1] + q2[1] * q1[0] - q2[2] * q1[3] + q2[3] * q1[2], + q2[0] * q1[2] + q2[1] * q1[3] + q2[2] * q1[0] - q2[3] * q1[1], + q2[0] * q1[3] - q2[1] * q1[2] + q2[2] * q1[1] + q2[3] * q1[0]]; + }, + + /** + * Normalizing a vector is obtaining another unit vector in the same direction. + * To normalize a vector, divide the vector by its magnitude. + * @param {Number[]} q1 - quanterion + * @return {Number[]} resultant quanterion + * @public + */ + normalize: function(q) { + return vector.divide(q, vector.len(q)); + }, + + /** + * Converts a rotation vector to a quaternion vector. + * @param {Number[]} v - vector + * @return {Number[]} resultant quaternion + * @public + */ + toQuant: function(v) { + if (!v) v = []; + var p = parseFloat(v[1] || 0) * Math.PI / 360, + y = parseFloat(v[2] || 0) * Math.PI / 360, + r = parseFloat(v[0] || 0) * Math.PI / 360, + c1 = Math.cos(p), + c2 = Math.cos(y), + c3 = Math.cos(r), + s1 = Math.sin(p), + s2 = Math.sin(y), + s3 = Math.sin(r), + q; + + q = new Float32Array([ + Math.round((s1 * s2 * c3 + c1 * c2 * s3) * 100000) / 100000, + Math.round((s1 * c2 * c3 + c1 * s2 * s3) * 100000) / 100000, + Math.round((c1 * s2 * c3 - s1 * c2 * s3) * 100000) / 100000, + Math.round((c1 * c2 * c3 - s1 * s2 * s3) * 100000) / 100000 + ]); + + return q; + } + //TODO: Acheive the same fucntionality for other 11 choices XYX, XZX, XZY, YXY, YXZ, YZX, YZY, ZXY, ZXZ, ZYX, ZYZ +}; \ No newline at end of file diff --git a/src/tween.js b/src/tween.js new file mode 100644 index 000000000..5c6d975ef --- /dev/null +++ b/src/tween.js @@ -0,0 +1,748 @@ +require('enyo'); + +var + dom = require('./dom'), + utils = require('./utils'), + transform = require('./transform'); + + +var fn, state, ease, points, path, oldState, newState, node, matrix, cState = [], + domCSS = {}; +/** + * Tween is a module responsible for creating intermediate frames for an animation. + * The responsibilities of this module is to; + * - Interpolating current state of character. + * - Update DOM based on current state, using matrix for tranform and styles for others. + * + * @module enyo/tween + */ +module.exports = { + + /** + * Indentifies initial pose of an element. + * @param {Object} actor - Element to be animated + * @param {Object} pose - Current behavior in the animation (at a given time) + * @param {Object} pose - Current behavior in the animation (at a given time) + * @memberOf module:enyo/tween + * @public + */ + init: function(actor, pose, initial) { + var k; + actor.initialState = actor.initialState || {}; + if (!(actor && pose && pose.animate)) return; + node = actor.hasNode(); + utils.mixin(pose, getAnimatedProperty(node, pose.animate, initial)); + actor.currentState = pose.currentState; + for (k in pose.initialState) { + actor.initialState[k] = actor.initialState[k] || pose.initialState[k]; + } + return pose; + }, + + /** + * Step represents state of the actor at any point of time in the animation. + * @param {Object} actor - Element to be animated + * @param {Object} pose - Current behavior in the animation (at a given time) + * @param {Number} t - Fraction which represents the animation (between 0 to 1) + * @param {Number} d - Duration of the current pose + * @memberOf module:enyo/tween + * @public + */ + step: function(actor, pose, t, d) { + if (!(actor && pose && pose.animate)) return; + t = t < 0 ? 0 : t; + t = t > 1 ? 1 : t; + + var k; + domCSS = {}; + node = actor.hasNode(); + state = utils.clone(pose.currentState || pose._startAnim); + points = pose.controlPoints = pose.controlPoints || {}; + ease = pose.animate && pose.animate.ease ? pose.animate.ease : this.ease; + path = pose.animate && pose.animate.path; + + if (pose.props) { + for (k in pose.props) { + if (!pose._endAnim[k] || k === 'duration' || k === 'ease' || k === 'path') { + continue; + } + + cState = utils.clone(state[k] || []); + newState = pose._endAnim[k].slice(); + oldState = pose._startAnim[k].slice(); + + if (ease && (typeof ease !== 'function')) { + if (k == 'rotate') { + points[k] = points[k] || + this.bezierSPoints(ease, oldState, newState, pose.props[k], points[k]); + fn = this.bezierSpline; + } else { + points[k] = points[k] || + this.bezierPoints(ease, oldState, newState, points[k]); + fn = this.bezier; + } + cState = fn.call(this, t, points[k], cState); + } else { + fn = (k === 'rotate') ? this.slerp : this.lerp; + cState = fn.call(this, oldState, newState, ease(t, d), cState); + } + + if (!isTransform(k)) { + domCSS = toPropertyValue(k, cState, domCSS); + } + state[k] = cState; + } + } else { + utils.mixin(state, oldState); + } + + //TODO: Support for properties other than translate + if (path) { + this.traversePath(t, path, state.translate); + } + + matrix = transform.Matrix.recompose( + state.translate, + state.rotate, + state.scale, + state.skew, + state.perspective + ); + state.matrix = matrix; + pose.currentState = state; + domCSS = toTransformValue(matrix, domCSS); + + actor.addStyles(domCSS); + }, + + /** + * This causes the stopped animation to be removed from GPU layer. + * @param {Object} actor - Element to be animated + * @param {Object} pose - Current behavior in the animation (at a given time) + * @memberOf module:enyo/tween + * @public + */ + halt: function(actor, pose) { + var matrix = pose.currentState && pose.currentState.matrix; + + pose = transform.Matrix.decompose2D(matrix); + domCSS = toTransformValue(pose.matrix2D); + actor.addStyles(domCSS); + }, + + /** + * Overridden function for applying the default ease. + * @param {Number} t - Fraction which represents the animation (between 0 to 1) + * @return {Number} t + * @memberOf module:enyo/tween + * @public + * @override + */ + ease: function(t) { + return t; + }, + + /** + * Draws linear interpolation between two values. + * @param {Number[]} vA - origin vector + * @param {Number[]} vB - Destination vector + * @param {Number} t - Fraction which represents the animation (between 0 to 1) + * @param {Number[]} vR - Resultant vector + * @return {Number[]} vR + * @memberOf module:enyo/tween + * @public + */ + lerp: function(vA, vB, t, vR) { + if (!vA) return; + if (!vR) vR = []; + var i, l = vA.length; + + for (i = 0; i < l; i++) { + vR[i] = (1 - t) * vA[i] + t * vB[i]; + } + return vR; + }, + + /** + * Draws sperical linear interpolation between two values. + * @param {Number[]} qA Quaternion origin + * @param {Number[]} qB - Quaternion destination + * @param {Number} t - Fraction which represents the animation (between 0 to 1) + * @param {Number[]} qR - Resultant quaternion + * @return {Number[]} qR + * @memberOf module:enyo/tween + * @public + */ + slerp: function(qA, qB, t, qR) { + if (!qA) return; + if (!qR) qR = []; + var a, + b, + theta, + dot = transform.Quaternion.quantDot(qA, qB), + l = qA.length; + + dot = Math.min(Math.max(dot, -1.0), 1.0); + if (dot == 1.0) { + qR = utils.cloneArray(qA); + return qR; + } + theta = Math.acos(dot); + for (var i = 0; i < l; i++) { + a = (Math.sin((1 - t) * theta) / Math.sin(theta)) * qA[i]; + b = (Math.sin(t * theta) / Math.sin(theta)) * qB[i]; + qR[i] = a + b; + } + return qR; + }, + + /** + * Creates bezier curve path for animation. + * @param {Number} t - Fraction which represents the animation (between 0 to 1) + * @param {Number[]} points - knot and control points + * @param {Number[]} vR - Resulting points + * @return {Number[]} vR + * @memberOf module:enyo/tween + * @public + */ + bezier: function(t, points, vR) { + if (!points) return; + if (!vR) vR = []; + + var i, j, + c = points.length, + l = points[0].length, + lastIndex = (c - 1), + startPoint = points[0], + endPoint = points[lastIndex], + values = this.getBezierValues(t, lastIndex); + + for (i = 0; i < l; i++) { + vR[i] = 0; + for (j = 0; j < c; j++) { + if ((j > 0) && (j < (c - 1))) { + vR[i] = vR[i] + ((startPoint[i] + (points[j][i] * (endPoint[i] - startPoint[i]))) * values[j]); + } else { + vR[i] = vR[i] + (points[j][i] * values[j]); + } + } + } + return vR; + }, + + /** + * Returns the control points for bezier curve. + * @param {Object} easeObj- The easing object with values. + * @param {Number[]} startPoint - Starting point of the curve + * @param {Number[]} endPoint - End point of the curve + * @param {Number[]} points - control points + * @return {Number[]} points + * @memberOf module:enyo/tween + * @public + */ + bezierPoints: function(easeObj, startPoint, endPoint, points) { + if (!easeObj) return; + var order = (easeObj && Object.keys(easeObj).length) ? (Object.keys(easeObj).length + 1) : 0; + var bValues = [], + m1 = [], + m2 = [], + m3 = [], + m4 = [], + l = 0; + points = [startPoint]; + + var t, a; + for (var key in easeObj) { + t = parseFloat(key) / 100; + a = parseFloat(easeObj[key]) / 100; + bValues = this.getBezierValues(t, order); + bValues.shift(); + m1.push(a - bValues.pop()); + m2.push(bValues); + } + + m3 = transform.Matrix.inverseN(m2, bValues.length); + m4 = transform.Matrix.multiplyN(m3, m1); + l = m4.length; + for (var i = 0; i < l; i++) { + var pValues = []; + for (var j = 0; j < endPoint.length; j++) { + pValues.push(m4[i]); + } + points.push(pValues); + } + + points.push(endPoint); + return points; + }, + + /** + * Traverses the path of the animation + * @param {Number} t - Fraction which represents the animation (between 0 to 1) + * @param {Number[]} path - Array of points + * @param {Number[]} vR Resulatant Array + * @return {Number[]} vR + * @memberOf module:enyo/tween + * @public + */ + traversePath: function(t, path, vR) { + if (!path) return; + if (!vR) vR = []; + + var i, j, + c = path.length, + l = path[0].length, + lastIndex = (c - 1), + values = this.getBezierValues(t, lastIndex); + + for (i = 0; i < l; i++) { + vR[i] = 0; + for (j = 0; j < c; j++) { + vR[i] = vR[i] + (path[j][i] * values[j]); + } + } + return vR; + }, + + /** + * Returns the control points for bezier spline. + * @param {Object} ease- The easing object with values. + * @param {Number[]} startQuat - Quaternion origin + * @param {Number[]} endQuat - Quaternion destination + * @param {Number[]} endPoint - Final Destination point + * @param {Number[]} splinePoints - spline control points + * @return {Number[]} splinePoints + * @memberOf module:enyo/tween + * @public + */ + bezierSPoints: function(ease, startQuat, endQuat, endPoint, splinePoints) { + if (!ease) return; + var time = [0], + quats = [startQuat]; + + var a, n, _a, aI, bN, eP, key, i, j, + eD = formatCSSValues(endPoint); + + splinePoints = splinePoints || {}; + if (Object.keys(ease).length > 0) { + for (key in ease) { + eP = utils.clone(eD); + a = parseFloat(ease[key]) / 100; + for (i in eP) { + eP[i] *= a; + } + quats.push(transform.Quaternion.toQuant(eP)); + time.push(parseFloat(key) / 100); + } + quats.push(endQuat); + time.push(1); + n = quats.length - 1; + aI = this.slerp(startQuat, endQuat, 0); + splinePoints[0] = [quats[0], aI, aI, quats[1]]; + for (i = 0, j = 1; i < n; i++, j++) { + if (i === 0) { + aI = this.slerp(quats[0], this.slerp(quats[2], quats[1], 2.0), 1.0 / 3); + } else { + _a = this.slerp(this.slerp(quats[i - 1], quats[i], 2.0), quats[i + 1], 0.5); + aI = this.slerp(quats[j], _a, 1.0 / 3); + } + if (j === n) { + bN = this.slerp(quats[j], this.slerp(quats[j - 2], quats[j - 1], 2.0), 1.0 / 3); + } else { + _a = this.slerp(this.slerp(quats[j - 1], quats[j], 2.0), quats[j + 1], 0.5); + bN = this.slerp(quats[j], _a, -1.0 / 3); + } + splinePoints[time[j]] = [quats[i], aI, bN, quats[i + 1]]; + } + } + return splinePoints; + }, + + /** + * Creates bezier spline path for animation. + * @param {Number} t - Fraction which represents the animation (between 0 to 1) + * @param {Number[]} points - knot and control points + * @param {Number[]} vR - Resulting points + * @return {Number[]} vR + * @memberOf module:enyo/tween + * @public + */ + bezierSpline: function(t, points, vR) { + if (!points) return; + if (!vR) vR = []; + var Q0, Q1, Q2, R0, R1, + p, key, pts; + for (p in points) { + if (p >= t) { + key = p; + break; + } + } + pts = points[key]; + + if (pts.length >= 4) { + Q0 = this.slerp(pts[0], pts[1], t); + Q1 = this.slerp(pts[1], pts[2], t); + Q2 = this.slerp(pts[2], pts[3], t); + R0 = this.slerp(Q0, Q1, t); + R1 = this.slerp(Q1, Q2, t); + vR = this.slerp(R0, R1, t); + } else + vR = this.slerp(pts[0], pts[1], t); + return vR; + }, + + /** + * Function to get the bezier coeffients based on the time and order + * @param {number} t - time + * @param {number} n - order + * @return {object} - bezier coefficients + * @memberOf module:enyo/tween + * @public + */ + getBezierValues: function(t, n) { + t = parseFloat(t, 10), + n = parseInt(n, 10); + + if (isNaN(t) || isNaN(n)) + return void 0; + if ((t < 0) || (n < 0)) + return void 0; + if (t > 1) + return void 0; + + var c, + values = [], + + x = (1 - t), + y = t; + // + // Binomial theorem to expand (x+y)^n + // + for (var k = 0; k <= n; k++) { + c = getCoeff(n, k) * Math.pow(x, (n - k)) * Math.pow(y, k); + values.push(c); + } + + return values; + } +}; + +/** + * This function returns the coefficents based on the order and the current position + * @param {number} n - order + * @param {number} k - current position + * @return {object} - coefficients + * @private + */ +function getCoeff (n, k) { + n = parseInt(n, 10); + k = parseInt(k, 10); + // Credits + // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 + if (isNaN(n) || isNaN(k)) + return void 0; + if ((n < 0) || (k < 0)) + return void 0; + if (k > n) + return void 0; + if (k === 0) + return 1; + if (k === n) + return 1; + if (k > n / 2) + return getCoeff(n, n - k); + + return n * getCoeff(n - 1, k - 1) / k; +} + +/** + * Get DOM node animation properties. + * @param {HTMLElement} node DOM node + * @param {Object} props Properties to fetch from DOM. + * @param {Object} initial Default properties to be applied. + * @return {Object} Object with various animation properties. + * @private + */ +function getAnimatedProperty(node, props, initial) { + if (!node) return; + + var + diff, + eP = {}, + sP = initial ? utils.mixin({}, initial) : {}, + tP = {}, + dP = {}, + iP = {}, + m, k, v, t, + s = initial ? undefined : dom.getComputedStyle(node); + + for (k in props) { + v = sP[k]; + if (!isTransform(k)) { + v = v || getStyleValue(s || dom.getComputedStyle(node), k); + iP[k] = v; + sP[k] = formatCSSValues(v, k); + eP[k] = formatCSSValues(props[k], k); + if (sP[k].length) { + diff = sP[k].length - eP[k].length; + if(diff > 0) { + eP[k] = eP[k].concat(Array(diff).fill(0)); + } else { + sP[k] = sP[k].concat(Array(-1 * diff).fill(0)); + } + } + } else { + v = formatTransformValues(props[k], k); + if (k.match(/rotate/)) { + v = transform.Quaternion.toQuant(v); + tP.rotate = tP.rotate ? transform.Quaternion.multiplication(tP.rotate, v) : v; + } else { + t = k.replace(/[XYZ]$/, ''); + tP[t] = tP[t] ? merge(tP[t], v) : v; + } + if (k.match(/[XYZ]$/)) { + t = k.replace(/[XYZ]$/, ''); + props[t] = tP[t].join(); + delete props[k]; + } + } + } + + if (initial) { + dP.translate = initial.translate; + dP.rotate = initial.rotate.length < 4 ? transform.Quaternion.toQuant(initial.rotate) : initial.rotate; + dP.scale = initial.scale; + dP.skew = initial.skew; + dP.perspective = initial.perspective; + } else { + m = getStyleValue(s || dom.getComputedStyle(node), dom.getCssTransformProp()); + m = formatTransformValues(m, 'matrix'); + transform.Matrix.decompose(m, dP); + iP.transform = transform.Matrix.toString(m); + } + + for (k in dP) { + sP[k] = dP[k]; + eP[k] = tP[k] || dP[k]; + } + return { + _startAnim: sP, + _endAnim: eP, + _transform: dP, + currentState: dP, + initialState: iP, + matrix: m, + props: props + }; +} + +/** +* @private +*/ +function merge (ar1, ar2) { + ar1.map(function(num, id) { + return num + ar2[id]; + }); + return ar1; +} + + +/** + * Converts comma separated values to array. + * @private + * @param {String} val Value of required animation in any property. + * @param {String} prop Property name of which value has been provided. + * @return {Number[]} Create array from val. + */ +function formatCSSValues(val, prop) { + if (typeof val === 'function') { + return val; + } + if (prop === 'duration' || prop === 'delay') { + val = 0; + } + if (SHADOW[prop] || COLOR[prop]) { + if (val === 'none') { + val = Array(7).fill(0); + } else if (val.indexOf('rgb') === 0) { + val = val.split(')')[0].replace(/^\w*\(/, '').concat(val.split(')')[1].split(' ').join()); + } else { + val = val.split('rgb(')[1].replace(')',',').concat(val.split('rgb(')[0]).replace(/, $/,''); + } + } + return stringToMatrix(val); +} + +/** +* @private +*/ +function formatTransformValues(val, prop) { + var + res, + X, Y, Z, + last = prop.match(/[XYZ]$/), + defalt = prop.match(/scale/) ? 1 : 0; + + if (prop === 'matrix') { + res = transform.Matrix.identity(); + val = stringToMatrix(val.replace(/^\w*\(/, '').replace(')', '')); + if (val.length <= 6) { + res[0] = val[0]; + res[1] = val[1]; + res[4] = val[2]; + res[5] = val[3]; + res[12] = val[4]; + res[13] = val[5]; + } + if (val.length == 16) { + res = val; + } + } else if (last) { + val = parseFloat(val, 10); + X = (last[0] === 'X') ? val : defalt; + Y = (last[0] === 'Y') ? val : defalt; + Z = (last[0] === 'Z') ? val : defalt; + res = [X, Y, Z]; + } else { + res = stringToMatrix(val); + } + return res; +} + +/** + * Validates if property is a transform property. + * @public + * @param {String} transform Any transform property, for which we want to identify whether or not the property is transform. + * @return {Number} Value of the required transform property. + */ +function isTransform(transform) { + return TRANSFORM[transform]; +} + +/** +* @private +*/ +function stringToMatrix(val) { + if (!val || val === "auto" || val === 'none') { + return 0; + } + return val.toString().split(",").map(function(v) { + return parseFloat(v, 10); + }); +} + +/** +* @private +*/ +function toPropertyValue(prop, val, ret) { + if (!val) return; + ret = ret || {}; + if (COLOR[prop]) { + val = val.map(function(v) { + return parseInt(v, 10); + }); + val = 'rgb(' + val + ')'; + } else if (INT_UNIT[prop]) { + val = parseInt(val[0], 10); + } else if (BORDER[prop]) { + val = val.map(function(v) { + return v + '%'; + }).join(' '); + } else if (OPACITY[prop]) { + val = val[0].toFixed(6); + val = (val <= 0) ? '0.000001' : val; + } else if (SHADOW[prop]) { + val = 'rgb(' + val.slice(0, 3).map(function(v) { + return parseInt(v, 10); + }) + ') ' + val.slice(3).map(function(v) { + return v + 'px'; + }).join(' '); + } else { + val = val[0] + 'px'; + } + + ret[prop] = val; + return ret; +} + + +/** +* @private +*/ +function toTransformValue (matrix, ret) { + var mat = transform.Matrix.toString(matrix), + key = dom.getStyleTransformProp(); + + ret = ret || {}; + ret[key] = mat; + return ret; +} + + +/** + * Gets a style property applied from the DOM element. + * @private + * @param {HTMLElement} style Computed style of a DOM. + * @param {String} key Property name for which style has to be fetched. + * @return {Number|HTMLElement} + */ +function getStyleValue(style, key) { + return style.getPropertyValue(key) || style[key]; +} + +var + BORDER = { + 'border-radius': 1, + 'border-image-slice': 1, + 'border-top-left-radius': 1, + 'border-top-right-radius': 1, + 'border-bottom-left-radius': 1, + 'border-bottom-right-radius': 1 + }, + COLOR = { + 'color': 1, + 'fill': 1, + 'stop-color': 1, + 'flood-color': 1, + 'border-color': 1, + 'outline-color': 1, + 'lighting-color': 1, + 'border-top-color': 1, + 'background-color': 1, + 'border-left-color': 1, + 'border-right-color': 1, + 'border-bottom-color': 1 + }, + INT_UNIT = { + 'z-index': 1 + }, + SHADOW = { + 'box-shadow': 1, + 'text-shadow': 1 + }, + OPACITY = { + 'opacity': 1, + 'flood-opacity': 1, + 'stop-opacity': 1, + 'fill-opacity': 1, + 'stroke-opacity': 1 + }, + TRANSFORM = { + translate: 1, + translateX: 1, + translateY: 1, + translateZ: 1, + rotate: 1, + rotateX: 1, + rotateY: 1, + rotateZ: 1, + skew: 1, + skewX: 1, + skewY: 1, + scale: 1, + scaleX: 1, + scaleY: 1, + scaleZ: 1, + perspective: 1 + }; \ No newline at end of file