介绍

关键帧的各种设置,是个不错的案例

说明

 

源码

/**
 * Takes a pair of keyframes and adds extra randomly stumbling, staggering keyframes between them.
 *
 * Helpful for making realistic progress bars, and probably not much else!
 *
 * Known Issues:
 *  - Sometimes, Bezier keyframes overshoot a lot causing reverse animation
 *  - The same speed/influence easing is used for all dimensions of a keyfrrame
 *
 * @author Zack Lovatt <zack@zacklovatt.com>
 * @version 0.1.3
 */
 (function keyStumbler(thisObj) {
    var NUM_KEYS = 20;
    var MIN_GAP_FRAMES = 2;
  
    // The chance to have certain types of animation (out of 100)
    var HOLD_CHANCE_PERCENT = 5;
    var BEZIER_CHANCE_PERCENT = 50;
  
    // Only if IN and OUT are both bezier will these come into play:
    var AUTOBEZIER_CHANCE_PERCENT = 33;
    var CONTINUOUS_CHANCE_PERCENT = 33;
  
    /**
     * Draws UI
     *
     * @returns {Window} Created window
     */
    function createUI() {
      var win =
        thisObj instanceof Panel
          ? thisObj
          : new Window("palette", "Key Stumbler", undefined, {
              resizeable: true,
            });
      win.alignChildren = ["fill", "top"];
      win.minimumSize = [50, 80];
  
      var pnlOptions = win.add("panel", undefined, "Options");
      pnlOptions.alignChildren = ["left", "top"];
  
      var grpNumKeys = pnlOptions.add("group");
      grpNumKeys.alignment = ["fill", "fill"];
      grpNumKeys.alignChildren = ["fill", "top"];
  
      var stNumKeys = grpNumKeys.add(
        "statictext",
        undefined,
        "Number of Keys to Create:"
      );
      var etNumKeys = grpNumKeys.add("edittext", undefined, "20");
      stNumKeys.helpTip = etNumKeys.helpTip =
        "How many keyframes should we make between your selected keys?";
  
      var grpGap = pnlOptions.add("group");
      grpGap.alignment = ["fill", "fill"];
      grpGap.alignChildren = ["fill", "top"];
  
      var stGap = grpGap.add(
        "statictext",
        undefined,
        "Minimum # Frames between Keys:"
      );
      var etGap = grpGap.add("edittext", undefined, "2");
      etGap.characters = 6;
      stGap.helpTip = etGap.helpTip =
        "What's the minimum number of frames between keyframes?";
  
      var pnlInterpolations = win.add("panel");
      pnlInterpolations.alignChildren = ["left", "top"];
      pnlInterpolations.add(
        "statictext",
        undefined,
        "Chance of keyframes being HOLD or BEZIER:"
      );
  
      var grpHoldChance = pnlInterpolations.add("group");
      grpHoldChance.alignment = ["fill", "fill"];
      grpHoldChance.alignChildren = ["fill", "top"];
  
      var stHoldChance = grpHoldChance.add(
        "statictext",
        undefined,
        "Hold Chance %:"
      );
      var etHoldChance = grpHoldChance.add("edittext", undefined, "5");
      stHoldChance.helpTip = etHoldChance.helpTip =
        "% chance for a keyframe to be HOLD";
  
      var grpBezierChance = pnlInterpolations.add("group");
      grpBezierChance.alignment = ["fill", "fill"];
      grpBezierChance.alignChildren = ["fill", "top"];
  
      var stBezierChance = grpBezierChance.add(
        "statictext",
        undefined,
        "Bezier Chance %:"
      );
      var etBezierChance = grpBezierChance.add("edittext", undefined, "50");
      stBezierChance.helpTip = etBezierChance.helpTip =
        "% chance for a keyframe to be BEZIER";
  
      var pnlBezierMode = win.add("panel");
      pnlBezierMode.alignChildren = ["left", "top"];
      pnlBezierMode.add(
        "statictext",
        undefined,
        "Chance of BEZIER keyframes being Autobezier or Continuous:"
      );
  
      var grpAutobezChance = pnlBezierMode.add("group");
      grpAutobezChance.alignment = ["fill", "fill"];
      grpAutobezChance.alignChildren = ["fill", "top"];
  
      var stAutobezChance = grpAutobezChance.add(
        "statictext",
        undefined,
        "Autobezier Chance %:"
      );
      var etAutobezChance = grpAutobezChance.add("edittext", undefined, "33");
      stAutobezChance.helpTip = etAutobezChance.helpTip =
        "% chance for a BEZIER keyframe to be AUTOBEZIER";
  
      var grpContChance = pnlBezierMode.add("group");
      grpContChance.alignment = ["fill", "fill"];
      grpContChance.alignChildren = ["fill", "top"];
  
      var stContChance = grpContChance.add(
        "statictext",
        undefined,
        "Continuous Chance %:"
      );
      var etContChance = grpContChance.add("edittext", undefined, "33");
      stContChance.helpTip = etContChance.helpTip =
        "% chance for a BEZIER keyframe to be CONTINUOUS";
  
      var grpBtns = win.add("group");
      grpBtns.orientation = "row";
      grpBtns.alignChildren = ["left", "top"];
  
      var btnStumble = grpBtns.add("button", undefined, "Stumble!");
      btnStumble.onClick = function () {
        var numKeysInput = parseInt(etNumKeys.text, 10);
        if (isNaN(numKeysInput)) {
          throw new Error("Enter a valid Number of Keys!");
        }
  
        var gapInput = parseInt(etGap.text, 10);
        if (isNaN(gapInput)) {
          throw new Error("Enter a valid Gap Frame Amount!");
        }
  
        // The chance to have certain types of animation (out of 100)
        var holdChanceInput = parseFloat(etHoldChance.text);
        if (isNaN(holdChanceInput)) {
          throw new Error("Enter a valid Hold Chance!");
        }
  
        var bezierChanceInput = parseFloat(etBezierChance.text);
        if (isNaN(bezierChanceInput)) {
          throw new Error("Enter a valid Bezier Chance!");
        }
  
        // Only if IN and OUT are both bezier will these come into play:
        var autobezChanceInput = parseFloat(etAutobezChance.text);
        if (isNaN(autobezChanceInput)) {
          throw new Error("Enter a valid Autobezier Chance!");
        }
  
        var contChanceInput = parseFloat(etContChance.text);
        if (isNaN(contChanceInput)) {
          throw new Error("Enter a valid Continuous Chance!");
        }
  
        NUM_KEYS = numKeysInput;
        MIN_GAP_FRAMES = gapInput;
  
        // The chance to have certain types of animation (out of 100)
        HOLD_CHANCE_PERCENT = holdChanceInput;
        BEZIER_CHANCE_PERCENT = bezierChanceInput;
  
        // Only if IN and OUT are both bezier will these come into play:
        AUTOBEZIER_CHANCE_PERCENT = autobezChanceInput;
        CONTINUOUS_CHANCE_PERCENT = contChanceInput;
  
        stumble();
      };
  
      win.layout.layout();
  
      win.onResizing = win.onResize = function () {
        this.layout.resize();
      };
      return win;
    }
  
    /**
     * Linear interpolation
     *
     * @param {number} t        Input
     * @param {number} tMin     Input min
     * @param {number} tMax     Input max
     * @param {number} valueMin Output min
     * @param {number} valueMax Output max
     * @returns {number}        Interpolated number
     */
    function _lerp(t, tMin, tMax, valueMin, valueMax) {
      return ((t - tMin) * (valueMax - valueMin)) / (tMax - tMin) + valueMin;
    }
  
    /**
     * Converts frame count to time.
     *
     * Pulled from aequery.
     *
     * @param  {number} frames    Frame count to convert
     * @param  {number} frameRate FPS to convert with
     * @return {number}           Frame count in time
     */
    function _framesToTime(frames, frameRate) {
      return frames / frameRate;
    }
  
    /**
     * Converts time to frame count.
     *
     * Pulled from aequery.
     *
     * @param  {number} time      Time to convert
     * @param  {number} frameRate FPS to convert with
     * @return {number}           Time in frames
     */
    function _timeToFrames(time, frameRate) {
      return time * frameRate;
    }
  
    /**
     * Checks whether a property is able to be stumbled
     *
     * @param {Property} prop Property to check
     * @returns {boolean}     Whether property is supported
     */
    function _isSupportedProperty(prop) {
      var supportedTypes = [
        PropertyValueType.COLOR.toString(),
        PropertyValueType.OneD.toString(),
        PropertyValueType.ThreeD.toString(),
        PropertyValueType.ThreeD_SPATIAL.toString(),
        PropertyValueType.TwoD.toString(),
        PropertyValueType.TwoD_SPATIAL.toString(),
      ].join("|");
  
      return supportedTypes.indexOf(prop.propertyValueType.toString()) > -1;
    }
  
    /**
     * Generates keyframe times from random sequence between existing keyframes
     *
     * @param {number[]} timeDeltas Array of time offsets
     * @param {object} timeData     Required info to generate times
     * @returns {number[]}          Randomized times
     */
    function _generateKeyTimes(timeDeltas, timeData) {
      var startTime = timeData.startTime;
      var endTime = timeData.endTime;
      var minGap = timeData.minGap;
      var frameRate = timeData.frameRate;
  
      var keyTimes = [];
  
      for (var ii = 0, il = timeDeltas.length; ii < il; ii++) {
        var timeDelta = timeDeltas[ii];
  
        var mappedTime = _lerp(
          timeDelta,
          timeDeltas[0],
          timeDeltas[timeDeltas.length - 1],
          startTime,
          endTime
        );
  
        var lastTime = startTime;
        if (keyTimes.length > 0) {
          lastTime = keyTimes[ii - 1];
        }
  
        var mappedInFrames = Math.floor(_timeToFrames(mappedTime, frameRate));
        var lastInFrames = Math.floor(_timeToFrames(lastTime, frameRate));
        var frameDelta = mappedInFrames - lastInFrames;
  
        if (frameDelta < minGap) {
          mappedInFrames = lastInFrames + minGap;
        }
  
        var mappedInSeconds = _framesToTime(mappedInFrames, frameRate);
        keyTimes.push(mappedInSeconds);
      }
  
      return keyTimes;
    }
  
    /**
     * Generates random keyframe values at provided times
     *
     * @param {number[]} valueDeltas Array of value offsets
     * @param {object} valueData     Required info to generate values
     * @returns {number[]}           Randomized values
     */
    function _generateKeyValues(valueDeltas, valueData) {
      var startValue = valueData.startValue;
      var endValue = valueData.endValue;
      var keyValues = [];
  
      for (var ii = 0, il = valueDeltas.length; ii < il; ii++) {
        var valueDelta = valueDeltas[ii];
  
        var mappedValue = _lerp(
          valueDelta,
          valueDeltas[0],
          valueDeltas[valueDeltas.length - 1],
          startValue,
          endValue
        );
  
        keyValues.push(mappedValue);
      }
  
      return keyValues;
    }
  
    /**
     * Generates X amount of incremental random numbers
     *
     * @param {number} numValues Number of values to generate
     * @returns {number[]}       Generated number sequence
     */
    function _generateIncrementalRandomNumbers(numValues) {
      var randomValues = [];
      var sum = 0;
  
      for (var ii = 0; ii <= numValues; ii++) {
        var num = generateRandomNumber();
  
        sum += num;
        randomValues.push(sum);
      }
  
      return randomValues;
    }
  
    /**
     * Randomly picks an interpolation method for keyframes
     *
     * @param {object} interpolationTypeChances Object of interpolation chance data
     * @returns {KeyframeInterplationType}      Chosen interpolation type
     */
    function _getInterpolationType(interpolationTypeChances) {
      var interpolationNumber = generateRandomNumber();
  
      var holdWeight = interpolationTypeChances.hold / 100;
      var bezierWeight = interpolationTypeChances.bezier / 100;
  
      if (interpolationNumber <= holdWeight) {
        return KeyframeInterpolationType.HOLD;
      }
  
      if (
        interpolationNumber > holdWeight &&
        interpolationNumber <= bezierWeight
      ) {
        return KeyframeInterpolationType.BEZIER;
      }
  
      return KeyframeInterpolationType.LINEAR;
    }
  
    /**
     * Generates a KeyfameEase array for a given property
     *
     * @todo Fix overshoot
     *
     * @param {Property} prop     Property to generate ease on
     * @param {object} keyIndices Start/end keyframe info
     * @returns {KeyframeEase[]}  Array of eases
     */
    function _generateBezierEase(prop, keyIndices) {
      var firstValue = prop.keyValue(keyIndices.start);
      var lastValue = prop.keyValue(keyIndices.end);
  
      var numDimensions = 1;
  
      if (prop.propertyValueType === PropertyValueType.OneD) {
        firstValue = [firstValue];
        lastValue = [lastValue];
      } else if (prop.propertyValueType === PropertyValueType.TwoD) {
        numDimensions = 2;
      } else if (prop.propertyValueType === PropertyValueType.ThreeD) {
        numDimensions = 3;
      }
  
      var ease = [];
  
      var animationDirection = firstValue[0] < lastValue[0] ? -1 : 1;
      var valueDelta =
        Math.abs(firstValue[0] - lastValue[0]) * animationDirection * -1;
  
      var speed = generateRandomNumber() * valueDelta;
      var influence = Math.max(generateRandomNumber() * 100, 0.1);
  
      for (var ii = 0, il = numDimensions; ii < il; ii++) {
        var dimensionEase = new KeyframeEase(speed, influence);
        ease.push(dimensionEase);
      }
  
      return ease;
    }
  
    /**
     * Sets eases for keyframes
     *
     * @param {Property} prop     Property to set eases on
     * @param {object} keyIndices Start/end keyframe info
     * @param {object} chances    Collection of probability values
     */
    function _setKeyEases(prop, keyIndices, chances) {
      var interpolationTypeChances = {
        hold: chances.hold,
        bezier: chances.bezier,
      };
      var autobezierChance = chances.autobezier;
      var continuousChance = chances.continuous;
  
      var startIndex = keyIndices.start;
      var endIndex = keyIndices.end;
  
      for (var ii = startIndex + 1; ii <= endIndex - 1; ii++) {
        var inType = _getInterpolationType(interpolationTypeChances);
        var outType = _getInterpolationType(interpolationTypeChances);
  
        var inEase = prop.keyInTemporalEase(ii);
        var outEase = prop.keyOutTemporalEase(ii);
  
        prop.setInterpolationTypeAtKey(ii, inType, outType);
  
        if (inType === KeyframeInterpolationType.BEZIER) {
          inEase = _generateBezierEase(prop, keyIndices);
        }
  
        if (outType === KeyframeInterpolationType.BEZIER) {
          outEase = _generateBezierEase(prop, keyIndices);
        }
  
        if (
          inType === KeyframeInterpolationType.BEZIER ||
          outType === KeyframeInterpolationType.BEZIER
        ) {
          prop.setTemporalEaseAtKey(ii, inEase, outEase);
        }
  
        if (
          inType === KeyframeInterpolationType.BEZIER &&
          outType === KeyframeInterpolationType.BEZIER
        ) {
          var temporalMode = generateRandomNumber();
  
          var autoBezierWeight = autobezierChance / 100;
          var continousWeight = continuousChance / 100;
  
          if (temporalMode <= autoBezierWeight) {
            prop.setTemporalAutoBezierAtKey(ii, true);
          } else if (
            temporalMode > autoBezierWeight &&
            temporalMode <= continousWeight
          ) {
            prop.setTemporalContinuousAtKey(ii, true);
          }
        }
      }
    }
  
    /**
     * Generates stumbled keyframes on the selected property
     *
     * @param {Property} prop  Property to generate keyframes on
     * @param {object} options Options about how to generate them
     * @param {object} chances Chances of different behaviours happening
     */
    function _generateStumbledKeys(prop, options, chances) {
      var selectedKeys = prop.selectedKeys;
  
      if (selectedKeys.length !== 2) {
        throw new Error("Select only 2 keyframes!");
      }
  
      var keyIndices = {
        start: selectedKeys[0],
        end: selectedKeys[1],
      };
  
      if (keyIndices.end - keyIndices.start !== 1) {
        throw new Error("Keyframes must be a sequence!");
      }
  
      var numKeys = options.numKeys;
      var minGap = options.minGap;
  
      var startTime = prop.keyTime(keyIndices.start);
      var endTime = prop.keyTime(keyIndices.end);
      var comp = prop.propertyGroup(prop.propertyDepth).containingComp;
      var frameRate = comp.frameRate;
  
      var spanFrameDuration = _timeToFrames(endTime - startTime, frameRate);
      var requiredFrameDuration = numKeys * minGap;
  
      if (requiredFrameDuration > spanFrameDuration) {
        throw new Error(
          "Not enough time between selected keys to create " +
            numKeys +
            " frames! Try extending span."
        );
      }
  
      var randomTimes = _generateIncrementalRandomNumbers(numKeys);
      var keyTimes = _generateKeyTimes(randomTimes, {
        startTime: startTime,
        endTime: endTime,
        minGap: minGap,
        frameRate: frameRate,
      });
  
      var whileCounter = 0;
      var whileLimit = 10;
  
      // Make sure our keyframes are only within the range
      while (
        whileCounter < whileLimit &&
        keyTimes[keyTimes.length - 1] > endTime
      ) {
        whileCounter++;
  
        randomTimes = _generateIncrementalRandomNumbers(numKeys);
        keyTimes = _generateKeyTimes(randomTimes, {
          startTime: startTime,
          endTime: endTime,
          minGap: minGap,
          frameRate: frameRate,
        });
      }
  
      if (whileCounter == whileLimit) {
        throw new Error(
          "Not enough time between selected keys to create " +
            numKeys +
            " frames! Try extending span."
        );
      }
  
      var randomValues = _generateIncrementalRandomNumbers(numKeys);
      var keyValues = _generateKeyValues(randomValues, {
        startValue: prop.keyValue(keyIndices.start),
        endValue: prop.keyValue(keyIndices.end),
      });
  
      prop.setValuesAtTimes(keyTimes, keyValues);
  
      keyIndices.end += options.numKeys;
  
      _setKeyEases(prop, keyIndices, chances);
    }
  
    function stumble() {
      var options = {
        numKeys: NUM_KEYS,
        minGap: MIN_GAP_FRAMES,
      };
  
      var interpolationChanceData = {
        hold: HOLD_CHANCE_PERCENT,
        bezier: BEZIER_CHANCE_PERCENT,
        autobezier: AUTOBEZIER_CHANCE_PERCENT,
        continuous: CONTINUOUS_CHANCE_PERCENT,
      };
  
      app.beginUndoGroup("Stumble Selected Keyframes");
  
      try {
        var comp = app.project.activeItem;
  
        if (!(comp && comp instanceof CompItem)) {
          throw new Error("Select an animated property!");
        }
  
        var selectedProps = comp.selectedProperties;
  
        for (var ii = 0, il = selectedProps.length; ii < il; ii++) {
          var prop = selectedProps[ii];
  
          if (!prop.canVaryOverTime) {
            continue;
          }
  
          if (!_isSupportedProperty(prop)) {
            throw new Error("Property " + prop.name + " is not supported!");
          }
  
          _generateStumbledKeys(prop, options, interpolationChanceData);
        }
      } catch (e) {
        alert(e);
      } finally {
        app.endUndoGroup();
      }
    }
  
    var ui = createUI();
  
    if (ui instanceof Window) {
      ui.show();
    } else {
      ui.layout.layout(true);
    }
  })(this);

 

您的电子邮箱地址不会被公开。 必填项已用*标注