Source: animation/KFLinearCurve.js

import Curve from "./Curve";
import Type from "./Type";
import Time from "./Time";
import Interval from "./Interval";
import Invariance from "./Invariance";
import AnimUtil from "./AnimUtil";
import AnimationError from "./AnimationError";


/**
 * @summary キーフレームによる線形関数
 *
 * @classdesc
 * <p>キーフレーム間を数値またはベクトルを線形に補間する関数である。</p>
 * <p>関数値の型は構築子のパラメータにより number, vector2, vector3 または vector4 を指定する。</p>
 *
 * @memberof mapray.animation
 * @extends mapray.animation.Curve
 */
class KFLinearCurve extends Curve
{

    /**
     * <p>type 型の関数を keyframes により生成する。</p>
     * <p>type は number, vector2, vector3 または vector4 を指定することができる。</p>
     * <p>keyframes を省略したときは type 型の既定値を返す定数関数と同等になる。keyframes の形式に関しては
     *    {@link mapray.animation.KFLinearCurve#setKeyFrames setKeyFrames()} を参照のこと。</p>
     *
     * @param {mapray.animation.Type} type  関数値の型
     * @param {object[]}       [keyframes]  初期キーフレーム
     */
    constructor( type, keyframes )
    {
        super();

        const dimension = AnimUtil.getDimension( type );
        if ( dimension == 0 ) {
            throw AnimationError( "unsupported type" );
        }

        this._value_type    = type;       // number | vector2 | vector3 | vector4
        this._dimension     = dimension;  // 1〜4
        this._num_keyframes = undefined;  // >= 2
        this._key_times     = undefined;  // Time[]
        this._key_values    = undefined;  // Float64Array

        if ( keyframes !== undefined ) {
            // 初期のキーフレームを設定
            this.setKeyFrames( keyframes );
        }
        else {
            // 既定のキーフレームを設定
            const t0 = Time.fromNumber( 0 );
            const t1 = Time.fromNumber( 1 );
            const dv = type.getDefaultValue();
            this.setKeyFrames( [t0, dv, t1, dv] );
        }
    }


    /**
     * @summary キーフレーム設定
     *
     * @desc
     * <p>keyframes により、すべてのキーフレームを指定する。</p>
     *
     * <p>
     * 条件1: keyframes.length >= 4 (キーフレーム数 >= 2)<br>
     * 条件2: すべての i, j において、i < j ⇔ 時刻i < 時刻j<br>
     * 条件3: すべての i において、値i は構築子の type 引数で指定した型のインスタンス
     * </p>
     *
     * @param {object[]} keyframes  [時刻0, 値0, 時刻1, 値1, ...]
     */
    setKeyFrames( keyframes )
    {
        const dimension = this._dimension;

        this._num_keyframes = keyframes.length / 2;
        this._key_times     = new Array( this._num_keyframes );
        this._key_values    = new Float64Array( this._num_keyframes * dimension );

        // キーフレームを設定
        for ( let ti = 0, vi = 0; ti < this._num_keyframes; ++ti, vi += dimension ) {
            const  time = keyframes[2*ti    ];
            const value = keyframes[2*ti + 1];

            // 時刻を配列に設定
            this._key_times[ti] = time;

            // 値を配列に設定
            if ( dimension == 1 ) {
                // スカラー
                this._key_values[vi] = value;
            }
            else {
                // ベクトル
                for ( let j = 0; j < dimension; ++j ) {
                    this._key_values[vi + j] = value[j];
                }
            }
        }

        // 全時刻の値が変化
        this.notifyValueChange( Interval.UNIVERSAL );
    }


    /**
     * @override
     */
    isTypeSupported( type )
    {
        let from_type = this._value_type;
        return type.isConvertible( from_type );
    }


    /**
     * @override
     */
    getValue( time, type )
    {
        let from_type  = this._value_type;
        let from_value = this._getInterpolatedValue( time );
        return type.convertValue( from_type, from_value );
    }


    /**
     * @override
     */
    getInvariance( interval )
    {
        const first_time = this._key_times[0];
        const  last_time = this._key_times[this._num_keyframes - 1];
        const ival_inner = new Interval( first_time, last_time, true, true );

        // 全体の不変性情報 (2区間程度なので毎回生成)
        const invr_full = new Invariance();
        invr_full.write( ival_inner.getPrecedings() );  // 最初のキーの時刻とその前の区間
        invr_full.write( ival_inner.getFollowings() );  // 最後のキーの時刻とその後の区間

        // interval 範囲に絞って返す
        return invr_full.getNarrowed( interval );
    }


    /**
     * @summary time での補間値を取得
     *
     * @param {mapray.animation.Time} time
     *
     * @return {object}  補間値 (this._value_type に適応した型)
     *
     * @private
     */
    _getInterpolatedValue( time )
    {
        // this._key_times に time より後の時刻が存在すれば、その中で最小のインデックス
        // そのような時刻が存在しなければ this._num_keyframes
        const index = AnimUtil.findKeyFrameIndex( time, this._key_times, 0, this._num_keyframes );

        if ( index == 0 ) {
            // time が最初のキー時刻と同じか、その前のときは最初のキー値で一定
            return this._createKeyFrameValue( 0 );
        }
        else if ( index == this._num_keyframes ) {
            // time が最後のキー時刻と同じか、その後のときは最後のキー値で一定
            return this._createKeyFrameValue( index - 1 );
        }
        else {
            // その他のときは前後のキー値で線形補間
            return this._createValueBy2Keys( index - 1, index, time );
        }
    }


    /**
     * @summary キーフレーム値を生成
     *
     * @param {number} index  キーフレームのインデックス
     *
     * @return {object}  キーフレーム値 (this._value_type に適応した型)
     *
     * @private
     */
    _createKeyFrameValue( index )
    {
        const dimension  = this._dimension;
        const key_values = this._key_values;

        if ( dimension == 1 ) {
            // スカラー
            return key_values[index];
        }
        else {
            // ベクトル
            let  vi = dimension * index;
            let vec = new Float64Array( dimension );
            for ( let i = 0; i < dimension; ++i ) {
                vec[i] = key_values[vi + i];
            }
            return vec;
        }
    }


    /**
     * @summary キーフレーム間の補間値を生成
     *
     * @param {number} i0  先キーフレームのインデックス
     * @param {number} i1  後キーフレームのインデックス
     * @param {mapray.animation.Time} time
     *
     * @return {object}  補間値 (this._value_type に適応した型)
     *
     * @private
     */
    _createValueBy2Keys( i0, i1, time )
    {
        const x0 = this._key_times[i0].toNumber();
        const x1 = this._key_times[i1].toNumber();
        const r1 = (time.toNumber() - x0) / (x1 - x0);
        const r0 = 1 - r1;

        const dimension  = this._dimension;
        const key_values = this._key_values;

        if ( dimension == 1 ) {
            // スカラー
            return r0*key_values[i0] + r1*key_values[i1];
        }
        else {
            // ベクトル
            let vi0 = dimension * i0;
            let vi1 = dimension * i1;
            let vec = new Float64Array( dimension );
            for ( let i = 0; i < dimension; ++i ) {
                vec[i] = r0*key_values[vi0 + i] + r1*key_values[vi1 + i];
            }
            return vec;
        }
    }

}


export default KFLinearCurve;