/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt 3D Studio.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef Q3DSBEZIEREVAL_P_H
#define Q3DSBEZIEREVAL_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include "q3dscubicroots_p.h"

#include <QtCore/qglobal.h>

QT_BEGIN_NAMESPACE

namespace Q3DSAnimationUtils {

struct KeyFrame
{
    float time;
    float value;
    struct ControlPoint
    {
        float time;
        float value;
    } c1, c2;
};

//==============================================================================
/**
 * Generic Bezier parametric curve evaluation at a given parametric value.
 * @param p0 - control point p0
 * @param p1 - control point P1
 * @param p2 - control point P2
 * @param p3 - control point P3
 * @param s  - the variable
 * @return the evaluated value on the bezier curve
 */
inline float evaluateBezierCurve(float p0, float p1, float p2, float p3, const float s)
{
    // Using:
    //    Q(s) = Sum i=0 to 3 ( Pi * Bi,3(s))
    // where:
    //    Pi is a control point and
    //    Bi,3 is a basis function such that:
    //
    //    B0,3(s) = (1 - s)^3
    //    B1,3(s) = (3 * s) * (1 - s)^2
    //    B2,3(s) = (3 * s^2) * (1 - s)
    //    B3,3(s) = s^3

    //    float sSquared = s * s;                                    => t^2
    //    float sCubed = sSquared * s;                               => t^3
    //    float sDifference = 1 - s;                                 => (1 - t)
    //    float sDifferenceSquared = sDifference * sDifference;      => (1 - t)^2
    //    float sDifferenceCubed = sDifferenceSquared * sDifference; => (1 - t)^3
    //    float firstTerm = sDifferenceCubed;                        => (1 - t)^3
    //    float secondTerm = ( 3 * s ) * sDifferenceSquared;         => (3 * t) * (1 - t)^2
    //    float thirdTerm = ( 3 * sSquared ) * sDifference;          => (3 * t^2) * (1 - t)
    //    float fourthTerm = sCubed;                                 => t^3
    //    => Q(t) = ( p0 * (1 - t)^3 ) + ( p1 * (3 * t) * (1 - t)^2 ) + ( p2 * (3 * t^2) * (1 - t) ) + ( p3 * t^3 )
    //    return ( p0 * firstTerm ) + ( p1 * secondTerm ) + ( p2 * thirdTerm ) + (p3 * fourthTerm );

    float factor = s * s;
    p1 *= 3.0f * s;
    p2 *= 3.0f * factor;
    factor *= s;
    p3 *= factor;

    factor = 1.0f - s;
    p2 *= factor;
    factor *= 1.0f - s;
    p1 *= factor;
    factor *= 1.0f - s;
    p0 *= factor;

    return p0 + p1 + p2 + p3;
}

//==============================================================================
/**
 * Inverse Bezier parametric curve evaluation to get parametric value for a given output.
 * This is equal to finding the root(s) of the Bezier cubic equation.
 * @param p0 - control point p0
 * @param p1 - control point P1
 * @param p2 - control point P2
 * @param p3 - control point P3
 * @param x  - the variable
 * @return the evaluated value
 */
inline float evaluateInverseBezierCurve(const float p0,
                                        const float p1,
                                        const float p2,
                                        const float p3,
                                        const float x)
{
    float result;
    // Using:
    //     Q(s) = Sum i=0 to 3 ( Pi * Bi,3(s))
    // where:
    //    Pi is a control point and
    //    Bi,3 is a basis function such that:
    //
    //    B0,3(s) = (1 - s)^3
    //    B1,3(s) = (3 * s) * (1 - s)^2
    //    B2,3(s) = (3 * s^2) * (1 - s)
    //    B3,3(s) = s^3

    // The Bezier cubic equation:
    // x = p0*(1-s)^3 + p1*(3*s)*(1-s)^2 + p2*(3*s^2)*(1-s) + p3*s^3
    //   = s^3*( -p0 + 3*p1 - 3*p2 +p3 ) + s^2*( 3*p0 - 6*p1 + 3*p2 ) + s*( -3*p0 + 3*p1 ) + p0
    // For cubic eqn of the form: c[0] + c[1]*x + c[2]*x^2 + c[3]*x^3 = 0
    const float constants[4] {(p0 - x),
                              (-3.0f * p0 + 3.0f * p1),
                              (3.0f * p0 - 6.0f * p1 + 3.0f * p2),
                              (-p0 + 3.0f * p1 - 3.0f * p2 + p3)};

    float solution[3];
    qint32 numRoots = 0;
    if (almostZero(constants[3])) {
        if (almostZero(constants[2])) {
            if (almostZero(constants[1])) // Invalid
                result = 0.0f;
            else
                result = -constants[0] / constants[1]; // linear
        } else { // quadratic
            numRoots = solveQuadric(constants, solution);
            result = solution[numRoots / 2];
        }
    } else { // Cubic
        numRoots = solveCubic(constants, solution);
        Q_ASSERT(numRoots > 0);
        result = solution[numRoots / 3];
    }

    // If the initially selected solution is outside the range, we need to iterate all of them
    if (result < 0.0f || result > 1.0f) {
        for (int i = 0; i < numRoots; ++i) {
            if (solution[i] >= -0.01f && solution[i] <= 1.01f)
                result = qMin(qMax(solution[i], 0.0f), 1.0f);
        }
    }

    return result;
}

inline float evaluateBezierKeyframe(float time, const KeyFrame &kfA, const KeyFrame &kfB)
{
    // The special case of C1Time=0 and C2Time=0 is used to indicate Studio-native animation.
    // Studio uses a simplified version of the bezier animation where the time control points
    // are equally spaced between the starting and ending times. This avoids calling the expensive
    // InverseBezierCurve function to find the right 's' given 't'.
    float parameter;
    if (kfA.c1.time == 0.0f && kfB.c2.time == 0.0f) {
        // Special case signaling that it's ok to treat time as "s"
        // This is done by assuming that Key1Val,Key1C1,Key1C2,Key2Val (aka p0,P1,P2,P3)
        // are evenly distributed over time.
        parameter = (time - kfA.time) / (kfB.time - kfA.time);
    } else {
        // Compute the "s" parameter on the Bezier given the time
        parameter = evaluateInverseBezierCurve(kfA.time, kfA.c1.time, kfB.c2.time, kfB.time, time);
        if (parameter < 0.0f || almostZero(parameter))
            return kfA.value;
        if (parameter > 1.0f || almostZero(parameter - 1.0f))
            return kfB.value;
    }

    return evaluateBezierCurve(kfA.value, kfA.c1.value, kfB.c2.value, kfB.value, parameter);
}

}

QT_END_NAMESPACE

#endif // Q3DSBEZIEREVAL_P_H
