UNPKG

lottie-web

Version:

After Effects plugin for exporting animations to SVG + JavaScript or canvas + JavaScript

464 lines (445 loc) 17.1 kB
import { extendPrototype, } from '../functionExtensions'; import { createSizedArray, createTypedArray, } from '../helpers/arrays'; import ShapePropertyFactory from '../shapes/ShapeProperty'; import PropertyFactory from '../PropertyFactory'; import shapePool from '../pooling/shape_pool'; import { initialDefaultFrame, } from '../../main'; import bez from '../bez'; import Matrix from '../../3rd_party/transformation-matrix'; import TransformPropertyFactory from '../TransformProperty'; import expressionHelpers from './expressionHelpers'; import ExpressionManager from './ExpressionManager'; function addPropertyDecorator() { function loopOut(type, duration, durationFlag) { if (!this.k || !this.keyframes) { return this.pv; } type = type ? type.toLowerCase() : ''; var currentFrame = this.comp.renderedFrame; var keyframes = this.keyframes; var lastKeyFrame = keyframes[keyframes.length - 1].t; if (currentFrame <= lastKeyFrame) { return this.pv; } var cycleDuration; var firstKeyFrame; if (!durationFlag) { if (!duration || duration > keyframes.length - 1) { duration = keyframes.length - 1; } firstKeyFrame = keyframes[keyframes.length - 1 - duration].t; cycleDuration = lastKeyFrame - firstKeyFrame; } else { if (!duration) { cycleDuration = Math.max(0, lastKeyFrame - this.elem.data.ip); } else { cycleDuration = Math.abs(lastKeyFrame - this.elem.comp.globalData.frameRate * duration); } firstKeyFrame = lastKeyFrame - cycleDuration; } var i; var len; var ret; if (type === 'pingpong') { var iterations = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); if (iterations % 2 !== 0) { return this.getValueAtTime(((cycleDuration - (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line } } else if (type === 'offset') { var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); var current = this.getValueAtTime(((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame) / this.comp.globalData.frameRate, 0); // eslint-disable-line var repeats = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); if (this.pv.length) { ret = new Array(initV.length); len = ret.length; for (i = 0; i < len; i += 1) { ret[i] = (endV[i] - initV[i]) * repeats + current[i]; } return ret; } return (endV - initV) * repeats + current; } else if (type === 'continue') { var lastValue = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); var nextLastValue = this.getValueAtTime((lastKeyFrame - 0.001) / this.comp.globalData.frameRate, 0); if (this.pv.length) { ret = new Array(lastValue.length); len = ret.length; for (i = 0; i < len; i += 1) { ret[i] = lastValue[i] + (lastValue[i] - nextLastValue[i]) * ((currentFrame - lastKeyFrame) / this.comp.globalData.frameRate) / 0.0005; // eslint-disable-line } return ret; } return lastValue + (lastValue - nextLastValue) * (((currentFrame - lastKeyFrame)) / 0.001); } return this.getValueAtTime((((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line } function loopIn(type, duration, durationFlag) { if (!this.k) { return this.pv; } type = type ? type.toLowerCase() : ''; var currentFrame = this.comp.renderedFrame; var keyframes = this.keyframes; var firstKeyFrame = keyframes[0].t; if (currentFrame >= firstKeyFrame) { return this.pv; } var cycleDuration; var lastKeyFrame; if (!durationFlag) { if (!duration || duration > keyframes.length - 1) { duration = keyframes.length - 1; } lastKeyFrame = keyframes[duration].t; cycleDuration = lastKeyFrame - firstKeyFrame; } else { if (!duration) { cycleDuration = Math.max(0, this.elem.data.op - firstKeyFrame); } else { cycleDuration = Math.abs(this.elem.comp.globalData.frameRate * duration); } lastKeyFrame = firstKeyFrame + cycleDuration; } var i; var len; var ret; if (type === 'pingpong') { var iterations = Math.floor((firstKeyFrame - currentFrame) / cycleDuration); if (iterations % 2 === 0) { return this.getValueAtTime((((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line } } else if (type === 'offset') { var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); var current = this.getValueAtTime((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration) + firstKeyFrame) / this.comp.globalData.frameRate, 0); var repeats = Math.floor((firstKeyFrame - currentFrame) / cycleDuration) + 1; if (this.pv.length) { ret = new Array(initV.length); len = ret.length; for (i = 0; i < len; i += 1) { ret[i] = current[i] - (endV[i] - initV[i]) * repeats; } return ret; } return current - (endV - initV) * repeats; } else if (type === 'continue') { var firstValue = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); var nextFirstValue = this.getValueAtTime((firstKeyFrame + 0.001) / this.comp.globalData.frameRate, 0); if (this.pv.length) { ret = new Array(firstValue.length); len = ret.length; for (i = 0; i < len; i += 1) { ret[i] = firstValue[i] + ((firstValue[i] - nextFirstValue[i]) * (firstKeyFrame - currentFrame)) / 0.001; } return ret; } return firstValue + ((firstValue - nextFirstValue) * (firstKeyFrame - currentFrame)) / 0.001; } return this.getValueAtTime(((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame))) / this.comp.globalData.frameRate, 0); // eslint-disable-line } function smooth(width, samples) { if (!this.k) { return this.pv; } width = (width || 0.4) * 0.5; samples = Math.floor(samples || 5); if (samples <= 1) { return this.pv; } var currentTime = this.comp.renderedFrame / this.comp.globalData.frameRate; var initFrame = currentTime - width; var endFrame = currentTime + width; var sampleFrequency = samples > 1 ? (endFrame - initFrame) / (samples - 1) : 1; var i = 0; var j = 0; var value; if (this.pv.length) { value = createTypedArray('float32', this.pv.length); } else { value = 0; } var sampleValue; while (i < samples) { sampleValue = this.getValueAtTime(initFrame + i * sampleFrequency); if (this.pv.length) { for (j = 0; j < this.pv.length; j += 1) { value[j] += sampleValue[j]; } } else { value += sampleValue; } i += 1; } if (this.pv.length) { for (j = 0; j < this.pv.length; j += 1) { value[j] /= samples; } } else { value /= samples; } return value; } function getTransformValueAtTime(time) { if (!this._transformCachingAtTime) { this._transformCachingAtTime = { v: new Matrix(), }; } /// / var matrix = this._transformCachingAtTime.v; matrix.cloneFromProps(this.pre.props); if (this.appliedTransformations < 1) { var anchor = this.a.getValueAtTime(time); matrix.translate( -anchor[0] * this.a.mult, -anchor[1] * this.a.mult, anchor[2] * this.a.mult ); } if (this.appliedTransformations < 2) { var scale = this.s.getValueAtTime(time); matrix.scale( scale[0] * this.s.mult, scale[1] * this.s.mult, scale[2] * this.s.mult ); } if (this.sk && this.appliedTransformations < 3) { var skew = this.sk.getValueAtTime(time); var skewAxis = this.sa.getValueAtTime(time); matrix.skewFromAxis(-skew * this.sk.mult, skewAxis * this.sa.mult); } if (this.r && this.appliedTransformations < 4) { var rotation = this.r.getValueAtTime(time); matrix.rotate(-rotation * this.r.mult); } else if (!this.r && this.appliedTransformations < 4) { var rotationZ = this.rz.getValueAtTime(time); var rotationY = this.ry.getValueAtTime(time); var rotationX = this.rx.getValueAtTime(time); var orientation = this.or.getValueAtTime(time); matrix.rotateZ(-rotationZ * this.rz.mult) .rotateY(rotationY * this.ry.mult) .rotateX(rotationX * this.rx.mult) .rotateZ(-orientation[2] * this.or.mult) .rotateY(orientation[1] * this.or.mult) .rotateX(orientation[0] * this.or.mult); } if (this.data.p && this.data.p.s) { var positionX = this.px.getValueAtTime(time); var positionY = this.py.getValueAtTime(time); if (this.data.p.z) { var positionZ = this.pz.getValueAtTime(time); matrix.translate( positionX * this.px.mult, positionY * this.py.mult, -positionZ * this.pz.mult ); } else { matrix.translate(positionX * this.px.mult, positionY * this.py.mult, 0); } } else { var position = this.p.getValueAtTime(time); matrix.translate( position[0] * this.p.mult, position[1] * this.p.mult, -position[2] * this.p.mult ); } return matrix; /// / } function getTransformStaticValueAtTime() { return this.v.clone(new Matrix()); } var getTransformProperty = TransformPropertyFactory.getTransformProperty; TransformPropertyFactory.getTransformProperty = function (elem, data, container) { var prop = getTransformProperty(elem, data, container); if (prop.dynamicProperties.length) { prop.getValueAtTime = getTransformValueAtTime.bind(prop); } else { prop.getValueAtTime = getTransformStaticValueAtTime.bind(prop); } prop.setGroupProperty = expressionHelpers.setGroupProperty; return prop; }; var propertyGetProp = PropertyFactory.getProp; PropertyFactory.getProp = function (elem, data, type, mult, container) { var prop = propertyGetProp(elem, data, type, mult, container); // prop.getVelocityAtTime = getVelocityAtTime; // prop.loopOut = loopOut; // prop.loopIn = loopIn; if (prop.kf) { prop.getValueAtTime = expressionHelpers.getValueAtTime.bind(prop); } else { prop.getValueAtTime = expressionHelpers.getStaticValueAtTime.bind(prop); } prop.setGroupProperty = expressionHelpers.setGroupProperty; prop.loopOut = loopOut; prop.loopIn = loopIn; prop.smooth = smooth; prop.getVelocityAtTime = expressionHelpers.getVelocityAtTime.bind(prop); prop.getSpeedAtTime = expressionHelpers.getSpeedAtTime.bind(prop); prop.numKeys = data.a === 1 ? data.k.length : 0; prop.propertyIndex = data.ix; var value = 0; if (type !== 0) { value = createTypedArray('float32', data.a === 1 ? data.k[0].s.length : data.k.length); } prop._cachingAtTime = { lastFrame: initialDefaultFrame, lastIndex: 0, value: value, }; expressionHelpers.searchExpressions(elem, data, prop); if (prop.k) { container.addDynamicProperty(prop); } return prop; }; function getShapeValueAtTime(frameNum) { // For now this caching object is created only when needed instead of creating it when the shape is initialized. if (!this._cachingAtTime) { this._cachingAtTime = { shapeValue: shapePool.clone(this.pv), lastIndex: 0, lastTime: initialDefaultFrame, }; } frameNum *= this.elem.globalData.frameRate; frameNum -= this.offsetTime; if (frameNum !== this._cachingAtTime.lastTime) { this._cachingAtTime.lastIndex = this._cachingAtTime.lastTime < frameNum ? this._caching.lastIndex : 0; this._cachingAtTime.lastTime = frameNum; this.interpolateShape(frameNum, this._cachingAtTime.shapeValue, this._cachingAtTime); } return this._cachingAtTime.shapeValue; } var ShapePropertyConstructorFunction = ShapePropertyFactory.getConstructorFunction(); var KeyframedShapePropertyConstructorFunction = ShapePropertyFactory.getKeyframedConstructorFunction(); function ShapeExpressions() {} ShapeExpressions.prototype = { vertices: function (prop, time) { if (this.k) { this.getValue(); } var shapePath = this.v; if (time !== undefined) { shapePath = this.getValueAtTime(time, 0); } var i; var len = shapePath._length; var vertices = shapePath[prop]; var points = shapePath.v; var arr = createSizedArray(len); for (i = 0; i < len; i += 1) { if (prop === 'i' || prop === 'o') { arr[i] = [vertices[i][0] - points[i][0], vertices[i][1] - points[i][1]]; } else { arr[i] = [vertices[i][0], vertices[i][1]]; } } return arr; }, points: function (time) { return this.vertices('v', time); }, inTangents: function (time) { return this.vertices('i', time); }, outTangents: function (time) { return this.vertices('o', time); }, isClosed: function () { return this.v.c; }, pointOnPath: function (perc, time) { var shapePath = this.v; if (time !== undefined) { shapePath = this.getValueAtTime(time, 0); } if (!this._segmentsLength) { this._segmentsLength = bez.getSegmentsLength(shapePath); } var segmentsLength = this._segmentsLength; var lengths = segmentsLength.lengths; var lengthPos = segmentsLength.totalLength * perc; var i = 0; var len = lengths.length; var accumulatedLength = 0; var pt; while (i < len) { if (accumulatedLength + lengths[i].addedLength > lengthPos) { var initIndex = i; var endIndex = (shapePath.c && i === len - 1) ? 0 : i + 1; var segmentPerc = (lengthPos - accumulatedLength) / lengths[i].addedLength; pt = bez.getPointInSegment(shapePath.v[initIndex], shapePath.v[endIndex], shapePath.o[initIndex], shapePath.i[endIndex], segmentPerc, lengths[i]); break; } else { accumulatedLength += lengths[i].addedLength; } i += 1; } if (!pt) { pt = shapePath.c ? [shapePath.v[0][0], shapePath.v[0][1]] : [shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1]]; } return pt; }, vectorOnPath: function (perc, time, vectorType) { // perc doesn't use triple equality because it can be a Number object as well as a primitive. if (perc == 1) { // eslint-disable-line eqeqeq perc = this.v.c; } else if (perc == 0) { // eslint-disable-line eqeqeq perc = 0.999; } var pt1 = this.pointOnPath(perc, time); var pt2 = this.pointOnPath(perc + 0.001, time); var xLength = pt2[0] - pt1[0]; var yLength = pt2[1] - pt1[1]; var magnitude = Math.sqrt(Math.pow(xLength, 2) + Math.pow(yLength, 2)); if (magnitude === 0) { return [0, 0]; } var unitVector = vectorType === 'tangent' ? [xLength / magnitude, yLength / magnitude] : [-yLength / magnitude, xLength / magnitude]; return unitVector; }, tangentOnPath: function (perc, time) { return this.vectorOnPath(perc, time, 'tangent'); }, normalOnPath: function (perc, time) { return this.vectorOnPath(perc, time, 'normal'); }, setGroupProperty: expressionHelpers.setGroupProperty, getValueAtTime: expressionHelpers.getStaticValueAtTime, }; extendPrototype([ShapeExpressions], ShapePropertyConstructorFunction); extendPrototype([ShapeExpressions], KeyframedShapePropertyConstructorFunction); KeyframedShapePropertyConstructorFunction.prototype.getValueAtTime = getShapeValueAtTime; KeyframedShapePropertyConstructorFunction.prototype.initiateExpression = ExpressionManager.initiateExpression; var propertyGetShapeProp = ShapePropertyFactory.getShapeProp; ShapePropertyFactory.getShapeProp = function (elem, data, type, arr, trims) { var prop = propertyGetShapeProp(elem, data, type, arr, trims); prop.propertyIndex = data.ix; prop.lock = false; if (type === 3) { expressionHelpers.searchExpressions(elem, data.pt, prop); } else if (type === 4) { expressionHelpers.searchExpressions(elem, data.ks, prop); } if (prop.k) { elem.addDynamicProperty(prop); } return prop; }; } function initialize() { addPropertyDecorator(); } export default initialize;