text: /**
   * Copyright (c) 2015-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @providesModule SpringAnimation
   * @flow
   * @format
   */
  'use strict';
  
  const AnimatedValue = require('../nodes/AnimatedValue');
  const AnimatedValueXY = require('../nodes/AnimatedValueXY');
  const Animation = require('./Animation');
  const SpringConfig = require('../SpringConfig');
  
  const invariant = require('fbjs/lib/invariant');
  const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
  
  import type {AnimationConfig, EndCallback} from './Animation';
  
  export type SpringAnimationConfig = AnimationConfig & {
    toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY,
    overshootClamping?: boolean,
    restDisplacementThreshold?: number,
    restSpeedThreshold?: number,
    velocity?: number | {x: number, y: number},
    bounciness?: number,
    speed?: number,
    tension?: number,
    friction?: number,
    stiffness?: number,
    damping?: number,
    mass?: number,
    delay?: number,
  };
  
  export type SpringAnimationConfigSingle = AnimationConfig & {
    toValue: number | AnimatedValue,
    overshootClamping?: boolean,
    restDisplacementThreshold?: number,
    restSpeedThreshold?: number,
    velocity?: number,
    bounciness?: number,
    speed?: number,
    tension?: number,
    friction?: number,
    stiffness?: number,
    damping?: number,
    mass?: number,
    delay?: number,
  };
  
  function withDefault<T>(value: ?T, defaultValue: T): T {
    if (value === undefined || value === null) {
      return defaultValue;
    }
    return value;
  }
  
  class SpringAnimation extends Animation {
    _overshootClamping: boolean;
    _restDisplacementThreshold: number;
    _restSpeedThreshold: number;
    _lastVelocity: number;
    _startPosition: number;
    _lastPosition: number;
    _fromValue: number;
    _toValue: any;
    _stiffness: number;
    _damping: number;
    _mass: number;
    _initialVelocity: number;
    _delay: number;
    _timeout: any;
    _startTime: number;
    _lastTime: number;
    _frameTime: number;
    _onUpdate: (value: number) => void;
    _animationFrame: any;
    _useNativeDriver: boolean;
  
    constructor(config: SpringAnimationConfigSingle) {
      super();
  
      this._overshootClamping = withDefault(config.overshootClamping, false);
      this._restDisplacementThreshold = withDefault(
        config.restDisplacementThreshold,
        0.001,
      );
      this._restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001);
      this._initialVelocity = withDefault(config.velocity, 0);
      this._lastVelocity = withDefault(config.velocity, 0);
      this._toValue = config.toValue;
      this._delay = withDefault(config.delay, 0);
      this._useNativeDriver = shouldUseNativeDriver(config);
      this.__isInteraction =
        config.isInteraction !== undefined ? config.isInteraction : true;
      this.__iterations = config.iterations !== undefined ? config.iterations : 1;
  
      if (
        config.stiffness !== undefined ||
        config.damping !== undefined ||
        config.mass !== undefined
      ) {
        invariant(
          config.bounciness === undefined &&
            config.speed === undefined &&
            config.tension === undefined &&
            config.friction === undefined,
          'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one',
        );
        this._stiffness = withDefault(config.stiffness, 100);
        this._damping = withDefault(config.damping, 10);
        this._mass = withDefault(config.mass, 1);
      } else if (config.bounciness !== undefined || config.speed !== undefined) {
        // Convert the origami bounciness/speed values to stiffness/damping
        // We assume mass is 1.
        invariant(
          config.tension === undefined &&
            config.friction === undefined &&
            config.stiffness === undefined &&
            config.damping === undefined &&
            config.mass === undefined,
          'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one',
        );
        const springConfig = SpringConfig.fromBouncinessAndSpeed(
          withDefault(config.bounciness, 8),
          withDefault(config.speed, 12),
        );
        this._stiffness = springConfig.stiffness;
        this._damping = springConfig.damping;
        this._mass = 1;
      } else {
        // Convert the origami tension/friction values to stiffness/damping
        // We assume mass is 1.
        const springConfig = SpringConfig.fromOrigamiTensionAndFriction(
          withDefault(config.tension, 40),
          withDefault(config.friction, 7),
        );
        this._stiffness = springConfig.stiffness;
        this._damping = springConfig.damping;
        this._mass = 1;
      }
  
      invariant(this._stiffness > 0, 'Stiffness value must be greater than 0');
      invariant(this._damping > 0, 'Damping value must be greater than 0');
      invariant(this._mass > 0, 'Mass value must be greater than 0');
    }
  
    __getNativeAnimationConfig() {
      return {
        type: 'spring',
        overshootClamping: this._overshootClamping,
        restDisplacementThreshold: this._restDisplacementThreshold,
        restSpeedThreshold: this._restSpeedThreshold,
        stiffness: this._stiffness,
        damping: this._damping,
        mass: this._mass,
        initialVelocity: withDefault(this._initialVelocity, this._lastVelocity),
        toValue: this._toValue,
        iterations: this.__iterations,
      };
    }
  
    start(
      fromValue: number,
      onUpdate: (value: number) => void,
      onEnd: ?EndCallback,
      previousAnimation: ?Animation,
      animatedValue: AnimatedValue,
    ): void {
      this.__active = true;
      this._startPosition = fromValue;
      this._lastPosition = this._startPosition;
  
      this._onUpdate = onUpdate;
      this.__onEnd = onEnd;
      this._lastTime = Date.now();
      this._frameTime = 0.0;
  
      if (previousAnimation instanceof SpringAnimation) {
        const internalState = previousAnimation.getInternalState();
        this._lastPosition = internalState.lastPosition;
        this._lastVelocity = internalState.lastVelocity;
        // Set the initial velocity to the last velocity
        this._initialVelocity = this._lastVelocity;
        this._lastTime = internalState.lastTime;
      }
  
      const start = () => {
        if (this._useNativeDriver) {
          this.__startNativeAnimation(animatedValue);
        } else {
          this.onUpdate();
        }
      };
  
      //  If this._delay is more than 0, we start after the timeout.
      if (this._delay) {
        this._timeout = setTimeout(start, this._delay);
      } else {
        start();
      }
    }
  
    getInternalState(): Object {
      return {
        lastPosition: this._lastPosition,
        lastVelocity: this._lastVelocity,
        lastTime: this._lastTime,
      };
    }
  
    /**
     * This spring model is based off of a damped harmonic oscillator
     * (https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator).
     *
     * We use the closed form of the second order differential equation:
     *
     * x'' + (2ζ⍵_0)x' + ⍵^2x = 0
     *
     * where
     *    ⍵_0 = √(k / m) (undamped angular frequency of the oscillator),
     *    ζ = c / 2√mk (damping ratio),
     *    c = damping constant
     *    k = stiffness
     *    m = mass
     *
     * The derivation of the closed form is described in detail here:
     * http://planetmath.org/sites/default/files/texpdf/39745.pdf
     *
     * This algorithm happens to match the algorithm used by CASpringAnimation,
     * a QuartzCore (iOS) API that creates spring animations.
     */
    onUpdate(): void {
      // If for some reason we lost a lot of frames (e.g. process large payload or
      // stopped in the debugger), we only advance by 4 frames worth of
      // computation and will continue on the next frame. It's better to have it
      // running at faster speed than jumping to the end.
      const MAX_STEPS = 64;
      let now = Date.now();
      if (now > this._lastTime + MAX_STEPS) {
        now = this._lastTime + MAX_STEPS;
      }
  
      const deltaTime = (now - this._lastTime) / 1000;
      this._frameTime += deltaTime;
  
      const c: number = this._damping;
      const m: number = this._mass;
      const k: number = this._stiffness;
      const v0: number = -this._initialVelocity;
  
      const zeta = c / (2 * Math.sqrt(k * m)); // damping ratio
      const omega0 = Math.sqrt(k / m); // undamped angular frequency of the oscillator (rad/ms)
      const omega1 = omega0 * Math.sqrt(1.0 - zeta * zeta); // exponential decay
      const x0 = this._toValue - this._startPosition; // calculate the oscillation from x0 = 1 to x = 0
  
      let position = 0.0;
      let velocity = 0.0;
      const t = this._frameTime;
      if (zeta < 1) {
        // Under damped
        const envelope = Math.exp(-zeta * omega0 * t);
        position =
          this._toValue -
          envelope *
            ((v0 + zeta * omega0 * x0) / omega1 * Math.sin(omega1 * t) +
              x0 * Math.cos(omega1 * t));
        // This looks crazy -- it's actually just the derivative of the
        // oscillation function
        velocity =
          zeta *
            omega0 *
            envelope *
            (Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0) / omega1 +
              x0 * Math.cos(omega1 * t)) -
          envelope *
            (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) -
              omega1 * x0 * Math.sin(omega1 * t));
      } else {
        // Critically damped
        const envelope = Math.exp(-omega0 * t);
        position = this._toValue - envelope * (x0 + (v0 + omega0 * x0) * t);
        velocity =
          envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0));
      }
  
      this._lastTime = now;
      this._lastPosition = position;
      this._lastVelocity = velocity;
  
      this._onUpdate(position);
      if (!this.__active) {
        // a listener might have stopped us in _onUpdate
        return;
      }
  
      // Conditions for stopping the spring animation
      let isOvershooting = false;
      if (this._overshootClamping && this._stiffness !== 0) {
        if (this._startPosition < this._toValue) {
          isOvershooting = position > this._toValue;
        } else {
          isOvershooting = position < this._toValue;
        }
      }
      const isVelocity = Math.abs(velocity) <= this._restSpeedThreshold;
      let isDisplacement = true;
      if (this._stiffness !== 0) {
        isDisplacement =
          Math.abs(this._toValue - position) <= this._restDisplacementThreshold;
      }
  
      if (isOvershooting || (isVelocity && isDisplacement)) {
        if (this._stiffness !== 0) {
          // Ensure that we end up with a round value
          this._lastPosition = this._toValue;
          this._lastVelocity = 0;
          this._onUpdate(this._toValue);
        }
  
        this.__debouncedOnEnd({finished: true});
        return;
      }
      this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
    }
  
    stop(): void {
      super.stop();
      this.__active = false;
      clearTimeout(this._timeout);
      global.cancelAnimationFrame(this._animationFrame);
      this.__debouncedOnEnd({finished: false});
    }
  }
  
  module.exports = SpringAnimation;
  