text: var TransformPropertyFactory = (function() {
  
      function applyToMatrix(mat) {
          var _mdf = this._mdf;
          this.iterateDynamicProperties();
          this._mdf = this._mdf || _mdf;
          if (this.a) {
              mat.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]);
          }
          if (this.s) {
              mat.scale(this.s.v[0], this.s.v[1], this.s.v[2]);
          }
          if (this.sk) {
              mat.skewFromAxis(-this.sk.v, this.sa.v);
          }
          if (this.r) {
              mat.rotate(-this.r.v);
          } else {
              mat.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]);
          }
          if (this.data.p.s) {
              if (this.data.p.z) {
                  mat.translate(this.px.v, this.py.v, -this.pz.v);
              } else {
                  mat.translate(this.px.v, this.py.v, 0);
              }
          } else {
              mat.translate(this.p.v[0], this.p.v[1], -this.p.v[2]);
          }
      }
      function processKeys(forceRender){
          if (this.elem.globalData.frameId === this.frameId) {
              return;
          }
          if(this._isDirty) {
              this.precalculateMatrix();
              this._isDirty = false;
          }
  
          this.iterateDynamicProperties();
  
          if (this._mdf || forceRender) {
              this.v.cloneFromProps(this.pre.props);
              if (this.appliedTransformations < 1) {
                  this.v.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]);
              }
              if(this.appliedTransformations < 2) {
                  this.v.scale(this.s.v[0], this.s.v[1], this.s.v[2]);
              }
              if (this.sk && this.appliedTransformations < 3) {
                  this.v.skewFromAxis(-this.sk.v, this.sa.v);
              }
              if (this.r && this.appliedTransformations < 4) {
                  this.v.rotate(-this.r.v);
              } else if (!this.r && this.appliedTransformations < 4){
                  this.v.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]);
              }
              if (this.autoOriented) {
                  var v1,v2, frameRate = this.elem.globalData.frameRate;
                  if(this.p && this.p.keyframes && this.p.getValueAtTime) {
                      if (this.p._caching.lastFrame+this.p.offsetTime <= this.p.keyframes[0].t) {
                          v1 = this.p.getValueAtTime((this.p.keyframes[0].t + 0.01) / frameRate,0);
                          v2 = this.p.getValueAtTime(this.p.keyframes[0].t / frameRate, 0);
                      } else if(this.p._caching.lastFrame+this.p.offsetTime >= this.p.keyframes[this.p.keyframes.length - 1].t) {
                          v1 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t / frameRate), 0);
                          v2 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t - 0.01) / frameRate, 0);
                      } else {
                          v1 = this.p.pv;
                          v2 = this.p.getValueAtTime((this.p._caching.lastFrame+this.p.offsetTime - 0.01) / frameRate, this.p.offsetTime);
                      }
                  } else if(this.px && this.px.keyframes && this.py.keyframes && this.px.getValueAtTime && this.py.getValueAtTime) {
                      v1 = [];
                      v2 = [];
                      var px = this.px, py = this.py, frameRate;
                      if (px._caching.lastFrame+px.offsetTime <= px.keyframes[0].t) {
                          v1[0] = px.getValueAtTime((px.keyframes[0].t + 0.01) / frameRate,0);
                          v1[1] = py.getValueAtTime((py.keyframes[0].t + 0.01) / frameRate,0);
                          v2[0] = px.getValueAtTime((px.keyframes[0].t) / frameRate,0);
                          v2[1] = py.getValueAtTime((py.keyframes[0].t) / frameRate,0);
                      } else if(px._caching.lastFrame+px.offsetTime >= px.keyframes[px.keyframes.length - 1].t) {
                          v1[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t / frameRate),0);
                          v1[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t / frameRate),0);
                          v2[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t - 0.01) / frameRate,0);
                          v2[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t - 0.01) / frameRate,0);
                      } else {
                          v1 = [px.pv, py.pv];
                          v2[0] = px.getValueAtTime((px._caching.lastFrame+px.offsetTime - 0.01) / frameRate,px.offsetTime);
                          v2[1] = py.getValueAtTime((py._caching.lastFrame+py.offsetTime - 0.01) / frameRate,py.offsetTime);
                      }
                  }
                  this.v.rotate(-Math.atan2(v1[1] - v2[1], v1[0] - v2[0]));
              }
              if(this.data.p && this.data.p.s){
                  if(this.data.p.z) {
                      this.v.translate(this.px.v, this.py.v, -this.pz.v);
                  } else {
                      this.v.translate(this.px.v, this.py.v, 0);
                  }
              }else{
                  this.v.translate(this.p.v[0],this.p.v[1],-this.p.v[2]);
              }
          }
          this.frameId = this.elem.globalData.frameId;
      }
  
      function precalculateMatrix() {
          if(!this.a.k) {
              this.pre.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]);
              this.appliedTransformations = 1;
          } else {
              return;
          }
          if(!this.s.effectsSequence.length) {
              this.pre.scale(this.s.v[0], this.s.v[1], this.s.v[2]);
              this.appliedTransformations = 2;
          } else {
              return;
          }
          if(this.sk) {
              if(!this.sk.effectsSequence.length && !this.sa.effectsSequence.length) {
                  this.pre.skewFromAxis(-this.sk.v, this.sa.v);
              this.appliedTransformations = 3;
              } else {
                  return;
              }
          }
          if (this.r) {
              if(!this.r.effectsSequence.length) {
                  this.pre.rotate(-this.r.v);
                  this.appliedTransformations = 4;
              } else {
                  return;
              }
          } else if(!this.rz.effectsSequence.length && !this.ry.effectsSequence.length && !this.rx.effectsSequence.length && !this.or.effectsSequence.length) {
              this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]);
              this.appliedTransformations = 4;
          }
      }
  
      function autoOrient(){
          //
          //var prevP = this.getValueAtTime();
      }
  
      function addDynamicProperty(prop) {
          this._addDynamicProperty(prop);
          this.elem.addDynamicProperty(prop);
          this._isDirty = true;
      }
  
      function TransformProperty(elem,data,container){
          this.elem = elem;
          this.frameId = -1;
          this.propType = 'transform';
          this.data = data;
          this.v = new Matrix();
          //Precalculated matrix with non animated properties
          this.pre = new Matrix();
          this.appliedTransformations = 0;
          this.initDynamicPropertyContainer(container || elem);
          if(data.p && data.p.s){
              this.px = PropertyFactory.getProp(elem,data.p.x,0,0,this);
              this.py = PropertyFactory.getProp(elem,data.p.y,0,0,this);
              if(data.p.z){
                  this.pz = PropertyFactory.getProp(elem,data.p.z,0,0,this);
              }
          }else{
              this.p = PropertyFactory.getProp(elem,data.p || {k:[0,0,0]},1,0,this);
          }
          if(data.rx) {
              this.rx = PropertyFactory.getProp(elem, data.rx, 0, degToRads, this);
              this.ry = PropertyFactory.getProp(elem, data.ry, 0, degToRads, this);
              this.rz = PropertyFactory.getProp(elem, data.rz, 0, degToRads, this);
              if(data.or.k[0].ti) {
                  var i, len = data.or.k.length;
                  for(i=0;i<len;i+=1) {
                      data.or.k[i].to = data.or.k[i].ti = null;
                  }
              }
              this.or = PropertyFactory.getProp(elem, data.or, 1, degToRads, this);
              //sh Indicates it needs to be capped between -180 and 180
              this.or.sh = true;
          } else {
              this.r = PropertyFactory.getProp(elem, data.r || {k: 0}, 0, degToRads, this);
          }
          if(data.sk){
              this.sk = PropertyFactory.getProp(elem, data.sk, 0, degToRads, this);
              this.sa = PropertyFactory.getProp(elem, data.sa, 0, degToRads, this);
          }
          this.a = PropertyFactory.getProp(elem,data.a || {k:[0,0,0]},1,0,this);
          this.s = PropertyFactory.getProp(elem,data.s || {k:[100,100,100]},1,0.01,this);
          // Opacity is not part of the transform properties, that's why it won't use this.dynamicProperties. That way transforms won't get updated if opacity changes.
          if(data.o){
              this.o = PropertyFactory.getProp(elem,data.o,0,0.01,elem);
          } else {
              this.o = {_mdf:false,v:1};
          }
          this._isDirty = true;
          if(!this.dynamicProperties.length){
              this.getValue(true);
          }
      }
  
      TransformProperty.prototype = {
          applyToMatrix: applyToMatrix,
          getValue: processKeys,
          precalculateMatrix: precalculateMatrix,
          autoOrient: autoOrient
      }
  
      extendPrototype([DynamicPropertyContainer], TransformProperty);
      TransformProperty.prototype.addDynamicProperty = addDynamicProperty;
      TransformProperty.prototype._addDynamicProperty = DynamicPropertyContainer.prototype.addDynamicProperty;
  
      function getTransformProperty(elem,data,container){
          return new TransformProperty(elem,data,container);
      }
  
      return {
          getTransformProperty: getTransformProperty
      };
  
  }());