/*
 * Data structures for Quaternions
 * See http://www-groups.dcs.st-and.ac.uk/~history/Mathematicians/Hamilton.html
 *
 * Copyright (c) Criterion Software Limited
 */

#ifndef RTQUAT_H
#define RTQUAT_H

/**
 * \defgroup rtquat RtQuat
 * \ingroup rttool
 *
 * Quaternion Toolkit for Renderware.
 *
 * See also http://www.gamasutra.com/features/19980703/quaternions_01.htm
 */

/*
 * See http://www-groups.dcs.st-and.ac.uk/~history/Mathematicians/Hamilton.html
 * On 16 October 1843 (a Monday) Hamilton was walking in along the Royal
 * Canal with his wife to preside at a Council meeting of the Royal Irish
 * Academy.
 * 
 * Although his wife talked to him now and again Hamilton hardly
 * heard, for the discovery of the quaternions, the first noncommutative
 * algebra to be studied, was taking shape in his mind:-
 * 
 *    "And here there dawned on me the notion that we must admit, in
 *     some sense, a fourth dimension of space for the purpose of calculating
 *     with triples ...  An electric circuit seemed to close, and a spark
 *     flashed forth."
 */


/****************************************************************************
 Includes
 */

#include <math.h>
/* renderware */
#include "rwcore.h"

#include "rtquat.rpe"          /* automatically generated header file */

#define RW_TOL_ORTHONORMAL  ((RwReal)0.01)

/****************************************************************************
 Global Types
 */


typedef struct RtQuat RtQuat;
/**
 * \ingroup rtquat
 * \struct RtQuat 
 * A structure describing a Quaternion
 *
*/ 
struct RtQuat
{
    RwV3d               imag;   /**< The imaginary part(s) */
    RwReal              real;   /**< The real part */
};


/****************************************************************************
 Defines
 */

#define RtQuatInitMacro( result, _x, _y, _z, _w)                           \
MACRO_START                                                                \
{                                                                          \
    (result)->real = (_w);                                                 \
    (result)->imag.x = (_x);                                               \
    (result)->imag.y = (_y);                                               \
    (result)->imag.z = (_z);                                               \
}                                                                          \
MACRO_STOP

#if (!defined(RtQuatAssignMacro))
#define RtQuatAssignMacro(_target, _source)            \
    ( *(_target) = *(_source) )
#endif /* (!defined(RtQuatAssignMacro)) */

#define RtQuatAddMacro( result, q1, q2 )                                   \
MACRO_START                                                                \
{                                                                          \
    (result)->real = (q1)->real + (q2)->real;                              \
    RwV3dAddMacro(&(result)->imag, &(q1)->imag, &(q2)->imag);              \
}                                                                          \
MACRO_STOP

#define RtQuatIncrementRealPartMacro(result, s, q)                         \
MACRO_START                                                                \
{                                                                          \
    (result)->real = (q)->real + s;                                        \
    (result)->imag.x = (q)->imag.x;                                        \
    (result)->imag.y = (q)->imag.y;                                        \
    (result)->imag.z = (q)->imag.z;                                        \
}                                                                          \
MACRO_STOP

#define RtQuatDecrementRealPartMacro(result, s, q)                         \
MACRO_START                                                                \
{                                                                          \
    (result)->real = (q)->real - s;                                        \
    (result)->imag.x = (q)->imag.x;                                        \
    (result)->imag.y = (q)->imag.y;                                        \
    (result)->imag.z = (q)->imag.z;                                        \
}                                                                          \
MACRO_STOP

#define RtQuatIncrementMacro( result, dq )                                 \
MACRO_START                                                                \
{                                                                          \
    (result)->real = (result)->real + (dq)->real;                          \
    RwV3dAddMacro(&(result)->imag, &(result)->imag, &(dq)->imag);          \
}                                                                          \
MACRO_STOP

#define RtQuatSubMacro( result, q1, q2 )                                   \
MACRO_START                                                                \
{                                                                          \
    (result)->real = (q1)->real - (q2)->real;                              \
    RwV3dSubMacro(&(result)->imag, &(q1)->imag, &(q2)->imag);              \
}                                                                          \
MACRO_STOP

#define RtQuatNegateMacro( result, q )                                     \
MACRO_START                                                                \
{                                                                          \
    (result)->real = -(q)->real;                                           \
    (result)->imag.x = -(q)->imag.x;                                       \
    (result)->imag.y = -(q)->imag.y;                                       \
    (result)->imag.z = -(q)->imag.z;                                       \
}                                                                          \
MACRO_STOP

#define RtQuatConjugateMacro( result, q)                                   \
MACRO_START                                                                \
{                                                                          \
    (result)->real = (q)->real;                                            \
    (result)->imag.x = -(q)->imag.x;                                       \
    (result)->imag.y = -(q)->imag.y;                                       \
    (result)->imag.z = -(q)->imag.z;                                       \
}                                                                          \
MACRO_STOP

#define RtQuatScaleMacro(result, q, scale )                                \
MACRO_START                                                                \
{                                                                          \
    (result)->real = (q)->real * scale;                                    \
    RwV3dScaleMacro(&(result)->imag, &(q)->imag, scale);                   \
}                                                                          \
MACRO_STOP

#define RtQuatModulusSquaredMacro( q )                                     \
    ((q)->real * (q)->real +                                               \
     RwV3dDotProductMacro(&(q)->imag, &(q)->imag))

#define RtQuatModulusMacro( result, q )         \
MACRO_START                                     \
{                                               \
    (result) = RtQuatModulusSquaredMacro(q);    \
    rwSqrtMacro(result, result);                \
}                                               \
MACRO_STOP

#define RtQuatMultiplyMacro( result, q1, q2)                               \
MACRO_START                                                                \
{                                                                          \
    /*                                                                     \
     * Assumes q1 != result != q2                                          \
     */                                                                    \
    (result)->real =                                                       \
        (q1)->real * (q2)->real -                                          \
        RwV3dDotProductMacro(&(q1)->imag,&(q2)->imag);                     \
    RwV3dCrossProductMacro(&(result)->imag, &(q1)->imag, &(q2)->imag);     \
    RwV3dIncrementScaledMacro(&(result)->imag, &(q2)->imag, (q1)->real);   \
    RwV3dIncrementScaledMacro(&(result)->imag, &(q1)->imag, (q2)->real);   \
}                                                                          \
MACRO_STOP

#define RtQuatReciprocalMacro( result, q)                                  \
MACRO_START                                                                \
{                                                                          \
    /*                                                                     \
     * Assumes result != q                                                 \
     */                                                                    \
    RwReal              val = RtQuatModulusSquaredMacro(q);                \
                                                                           \
    if (val > (RwReal) 0)                                                  \
    {                                                                      \
        val = ((RwReal)1) / val;                                           \
        (result)->real = (q)->real * val;                                  \
        val = -val;                                                        \
        RwV3dScaleMacro(&(result)->imag, &(q)->imag, val);                 \
    }                                                                      \
}                                                                          \
MACRO_STOP

#define RtQuatSquareMacro( result, q )                                     \
MACRO_START                                                                \
{                                                                          \
    /*                                                                     \
     * Assumes result != q                                                 \
     */                                                                    \
    RwReal val = ((RwReal)2) * (q)->real ;                                 \
                                                                           \
    (result)->real =                                                       \
        (q)->real * (q)->real -                                            \
         RwV3dDotProductMacro(&(q)->imag, &(q)->imag);                     \
    RwV3dScaleMacro(&(result)->imag, &(q)->imag, val);                     \
}                                                                          \
MACRO_STOP

#define RtQuatSquareRootMacro( result, q )                                 \
MACRO_START                                                                \
{                                                                          \
    /*                                                                     \
     * Assumes result != q                                                 \
     * other root is of course -result                                     \
     */                                                                    \
    RwReal              val;                                               \
                                                                           \
    RtQuatModulusMacro(val,q);                                             \
    val = ((q)->real + val) * ((RwReal) 0.5);                              \
                                                                           \
    if (val > ((RwReal)0))                                                 \
    {                                                                      \
        rwSqrtMacro(val, val);                                             \
        (result)->real = val;                                              \
        val = ((RwReal)0.5) / val;                                         \
        RwV3dScale(&(result)->imag, &(q)->imag, val);                      \
    }                                                                      \
    else                                                                   \
    {                                                                      \
        result->imag.x = -(q)->real;                                       \
        rwSqrtMacro(result->imag.x, result->imag.x);                       \
        result->imag.y = ((RwReal)0);                                      \
        result->imag.x = ((RwReal)0);                                      \
        result->real   = ((RwReal)0);                                      \
    }                                                                      \
}                                                                          \
MACRO_STOP

#define RtQuatLogMacro(result, q)                                          \
MACRO_START                                                                \
{                                                                          \
    /*                                                                     \
     * Assumes result != q                                                 \
     */                                                                    \
    const RwReal        mod2 = RtQuatModulusSquaredMacro(q);               \
    RwReal              sin_b;                                             \
    RwReal              radians;                                           \
    RwReal              factor;                                            \
                                                                           \
    sin_b = RwV3dDotProduct(&(q)->imag, &(q)->imag);                       \
    rwSqrtMacro(sin_b, sin_b);                                             \
    radians = (RwReal) RwATan2(sin_b, (q)->real);                          \
    factor = (sin_b > (RwReal) 0) ? (((RwReal)radians) / sin_b) : 0 ;      \
                                                                           \
    RwV3dScaleMacro(&(result)->imag, &(q)->imag, factor);                  \
    (result)->real = ((RwReal) RwLog(mod2)) * ((RwReal) 0.5);              \
                                                                           \
}                                                                          \
MACRO_STOP

#define RtQuatExpMacro(result, q)                                          \
MACRO_START                                                                \
{                                                                          \
    /*                                                                     \
     * Assumes result != q                                                 \
     */                                                                    \
    const RwReal        exp_a = (RwReal)RwExp((q)->real);                  \
    RwReal              mod_b;                                             \
    RwReal              factor;                                            \
                                                                           \
    mod_b = RwV3dDotProduct(&(q)->imag, &(q)->imag);                       \
    rwSqrtMacro(mod_b, mod_b);                                             \
    factor = ( (mod_b > (RwReal) 0) ?                                      \
               (exp_a * ((RwReal)RwSin(mod_b)) / mod_b) :                  \
               0 ) ;                                                       \
                                                                           \
    RwV3dScaleMacro(&(result)->imag, &(q)->imag, factor);                  \
    (result)->real = exp_a * (RwReal)RwCos(mod_b);                         \
}                                                                          \
MACRO_STOP

#define RtQuatPowMacro( result, q, e)                                      \
MACRO_START                                                                \
{                                                                          \
    RtQuat qLog;                                                           \
                                                                           \
    RtQuatLogMacro(&qLog, q);                                              \
    RtQuatScaleMacro(&qLog, &qLog, e);                                     \
    RtQuatExpMacro(result, &qLog);                                         \
}                                                                          \
MACRO_STOP

#define RtQuatUnitLogMacro(result, q)                                     \
MACRO_START                                                               \
{                                                                         \
    /*                                                                    \
     * Assumes result != q                                                \
     */                                                                   \
    RwReal              sin_b ;                                           \
    RwReal              radians ;                                         \
    RwReal              factor ;                                          \
                                                                          \
    sin_b =  RwV3dDotProduct(&(q)->imag, &(q)->imag);                     \
    rwSqrtMacro(sin_b, sin_b);                                            \
    radians = (RwReal)RwATan2(sin_b, (q)->real);                          \
    factor = (sin_b > (RwReal) 0) ? (((RwReal)radians) / sin_b) : 0 ;     \
                                                                          \
    RwV3dScaleMacro(&(result)->imag, &(q)->imag, factor);                 \
    (result)->real = (RwReal)0;                                           \
                                                                          \
}                                                                         \
MACRO_STOP

#define RtQuatUnitExpMacro(result, q)                                      \
MACRO_START                                                                \
{                                                                          \
    /*                                                                     \
     * Assumes result != q                                                 \
     */                                                                    \
    RwReal              mod_b;                                             \
    RwReal             factor;                                             \
                                                                           \
    mod_b =  RwV3dDotProduct(&(q)->imag, &(q)->imag);                      \
    rwSqrtMacro(mod_b, mod_b);                                             \
    factor = (mod_b > (RwReal) 0) ? (((RwReal)RwSin(mod_b)) / mod_b) : 0 ; \
                                                                           \
    RwV3dScaleMacro(&(result)->imag, &(q)->imag, factor);                  \
    (result)->real = (RwReal)RwCos(mod_b);                                 \
                                                                           \
}                                                                          \
MACRO_STOP

#define RtQuatUnitPowMacro( result, q, e)                                  \
MACRO_START                                                                \
{                                                                          \
    RtQuat qLog;                                                           \
                                                                           \
    RtQuatUnitLogMacro(&qLog, q);                                          \
    RwV3dScaleMacro(&qLog.imag, &qLog.imag, e);                            \
    RtQuatUnitExpMacro(result, &qLog);                                     \
}                                                                          \
MACRO_STOP

#define RtQuatConvertToMatrixMacro(qpQuat, mpMatrix)                       \
MACRO_START                                                                \
{                                                                          \
    RwReal              rS;                                                \
    RwV3d               rV;                                                \
    RwV3d               rW;                                                \
    RwV3d               square;                                            \
    RwV3d               cross;                                             \
                                                                           \
    rS = ((RwReal) 2) / RtQuatModulusSquaredMacro((qpQuat));               \
                                                                           \
    RwV3dScale(&rV, &(qpQuat)->imag, rS);                                  \
    RwV3dScale(&rW, &rV, (qpQuat)->real);                                  \
                                                                           \
    square.x = (qpQuat)->imag.x * rV.x;                                    \
    square.y = (qpQuat)->imag.y * rV.y;                                    \
    square.z = (qpQuat)->imag.z * rV.z;                                    \
                                                                           \
    cross.x = (qpQuat)->imag.y * rV.z;                                     \
    cross.y = (qpQuat)->imag.z * rV.x;                                     \
    cross.z = (qpQuat)->imag.x * rV.y;                                     \
                                                                           \
    (mpMatrix)->right.x = ((RwReal) 1) - (square.y + square.z);            \
    (mpMatrix)->right.y = cross.z + rW.z;                                  \
    (mpMatrix)->right.z = cross.y - rW.y;                                  \
                                                                           \
    (mpMatrix)->up.x = cross.z - rW.z;                                     \
    (mpMatrix)->up.y = ((RwReal) 1) - (square.z + square.x);               \
    (mpMatrix)->up.z = cross.x + rW.x;                                     \
                                                                           \
    (mpMatrix)->at.x = cross.y + rW.y;                                     \
    (mpMatrix)->at.y = cross.x - rW.x;                                     \
    (mpMatrix)->at.z = ((RwReal) 1) - (square.x + square.y);               \
                                                                           \
    /* Set position */                                                     \
    (mpMatrix)->pos.x = ((RwReal) 0);                                      \
    (mpMatrix)->pos.y = ((RwReal) 0);                                      \
    (mpMatrix)->pos.z = ((RwReal) 0);                                      \
                                                                           \
    /* Matrix is orthogonal */                                             \
    rwMatrixSetFlags((mpMatrix),                                           \
                      (rwMATRIXTYPEORTHOGANAL  &                           \
                       ~rwMATRIXINTERNALIDENTITY) );                       \
                                                                           \
}                                                                          \
MACRO_STOP

#define RtQuatUnitConvertToMatrixMacro(qpQuat, mpMatrix)                   \
MACRO_START                                                                \
{                                                                          \
    const RwReal        x = (qpQuat)->imag.x;                              \
    const RwReal        y = (qpQuat)->imag.y;                              \
    const RwReal        z = (qpQuat)->imag.z;                              \
    const RwReal        w = (qpQuat)->real;                                \
    RwV3d               square;                                            \
    RwV3d               cross;                                             \
    RwV3d               wimag;                                             \
                                                                           \
    square.x = x * x;                                                      \
    square.y = y * y;                                                      \
    square.z = z * z;                                                      \
                                                                           \
    cross.x = y * z;                                                       \
    cross.y = z * x;                                                       \
    cross.z = x * y;                                                       \
                                                                           \
    wimag.x = w * x;                                                       \
    wimag.y = w * y;                                                       \
    wimag.z = w * z;                                                       \
                                                                           \
    (mpMatrix)->right.x = 1 - 2 * (square.y + square.z);                   \
    (mpMatrix)->right.y = 2 * (cross.z + wimag.z);                         \
    (mpMatrix)->right.z = 2 * (cross.y - wimag.y);                         \
                                                                           \
    (mpMatrix)->up.x = 2 * (cross.z - wimag.z);                            \
    (mpMatrix)->up.y = 1 - 2 * (square.x + square.z);                      \
    (mpMatrix)->up.z = 2 * (cross.x + wimag.x);                            \
                                                                           \
    (mpMatrix)->at.x = 2 * (cross.y + wimag.y);                            \
    (mpMatrix)->at.y = 2 * (cross.x - wimag.x);                            \
    (mpMatrix)->at.z = (1 - 2 * (square.x + square.y));                    \
                                                                           \
    /* Set position */                                                     \
    (mpMatrix)->pos.x = ((RwReal) 0);                                      \
    (mpMatrix)->pos.y = ((RwReal) 0);                                      \
    (mpMatrix)->pos.z = ((RwReal) 0);                                      \
                                                                           \
    /* Matrix is orthonormal */                                            \
    rwMatrixSetFlags((mpMatrix),                                           \
                      (rwMATRIXTYPEORTHONORMAL  &                          \
                       ~rwMATRIXINTERNALIDENTITY) );                       \
}                                                                          \
MACRO_STOP

#if (! ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) ))

#define RtQuatInit( result, _x, _y, _z, _w)                                \
    RtQuatInitMacro( result, _x, _y, _z, _w)

#define RtQuatAssign( to, from )                                           \
    RtQuatAssignMacro( to, from )

#define RtQuatAdd( result, q1, q2 )                                        \
    RtQuatAddMacro( result, q1, q2 )

#define RtQuatIncrementRealPart(result, s, q)                              \
    RtQuatIncrementRealPartMacro(result, s, q)

#define RtQuatDecrementRealPart(result, s, q)                              \
    RtQuatDecrementRealPartMacro(result, s, q)

#define RtQuatIncrement( result, dq )                                      \
    RtQuatIncrementMacro( result, dq )

#define RtQuatSub( result, q1, q2 )                                        \
    RtQuatSubMacro( result, q1, q2 )

#define RtQuatNegate( result, q )                                          \
    RtQuatNegateMacro( result, q )

#define RtQuatConjugate( result, q)                                        \
    RtQuatConjugateMacro( result, q)

#define RtQuatScale(result, q, scale )                                     \
    RtQuatScaleMacro(result, q, scale )

#define RtQuatModulusSquared( q )                                          \
    RtQuatModulusSquaredMacro( q )

#define RtQuatMultiply( result, q1, q2)                                    \
    RtQuatMultiplyMacro( result, q1, q2)

#define RtQuatReciprocal( result, q)                                       \
    RtQuatReciprocalMacro( result, q)

#define RtQuatSquare( result, q )                                          \
    RtQuatSquareMacro( result, q )

#define RtQuatSquareRoot( result, q )                                      \
    RtQuatSquareRootMacro( result, q )

#define RtQuatLog( result, q )                                             \
    RtQuatLogMacro( result, q )

#define RtQuatExp( result, q )                                             \
    RtQuatExpMacro( result, q )

#define RtQuatPow( result, q, e )                                          \
    RtQuatPowMacro( result, q, e )

#define RtQuatUnitLog( result, q )                                         \
    RtQuatUnitLogMacro( result, q )

#define RtQuatUnitExp( result, q )                                         \
    RtQuatUnitExpMacro( result, q )

#define RtQuatUnitPow( result, q, e )                                      \
    RtQuatUnitPowMacro( result, q, e )

#define RtQuatConvertToMatrix(qpQuat, mpMatrix)                            \
    RtQuatConvertToMatrixMacro(qpQuat, mpMatrix)    

#define RtQuatUnitConvertToMatrix(qpQuat, mpMatrix)                        \
    RtQuatUnitConvertToMatrixMacro(qpQuat, mpMatrix)    

#endif /* (! ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) )) */

/****************************************************************************
 Function prototypes
 */
#ifdef    __cplusplus
extern              "C"
{
#endif                          /* __cplusplus */

extern RwBool
RtQuatConvertFromMatrix(RtQuat * const qpQuat,
                        const RwMatrix * const mpMatrix);

extern RtQuat             *
RtQuatRotate(RtQuat * quat,
             const RwV3d * axis, 
             RwReal angle, 
             RwOpCombineType combineOp);

extern const RtQuat     *
RtQuatQueryRotate(const RtQuat *quat, 
                  RwV3d * unitAxis, 
                  RwReal * angle);

extern RwV3d *
RtQuatTransformVectors(RwV3d * vectorsOut,
                       const RwV3d * vectorsIn,
                       const RwInt32 numPoints,
                       const RtQuat *quat);

extern RwReal
RtQuatModulus(RtQuat * q);

#if ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) )

extern void
RtQuatInit(RtQuat * result, RwReal x, RwReal y, RwReal z, RwReal w);

extern void
RtQuatAssign(RtQuat * to, RtQuat * from);

extern void
RtQuatAdd(RtQuat * result, RtQuat * q1, RtQuat * q2);

extern void
RtQuatIncrementRealPart(RtQuat * result, RwReal s, RtQuat * q);

extern void
RtQuatDecrementRealPart(RtQuat * result, RwReal s, RtQuat * q);

extern void
RtQuatIncrement(RtQuat * result, RtQuat * dq);

extern void
RtQuatSub(RtQuat * result, RtQuat * q1, RtQuat * q2);

extern void
RtQuatNegate(RtQuat * result, RtQuat * q);

extern void
RtQuatConjugate(RtQuat * result, RtQuat * q);

extern void
RtQuatScale(RtQuat * result, RtQuat * q, RwReal scale);

extern RwReal
RtQuatModulusSquared(RtQuat * q);

extern void
RtQuatMultiply(RtQuat * result, RtQuat * q1, RtQuat * q2);

extern void
RtQuatReciprocal(RtQuat * result, RtQuat * q);

extern void
RtQuatSquare(RtQuat * result, RtQuat * q);

extern void
RtQuatSquareRoot(RtQuat * result, RtQuat * q);

extern void
RtQuatLog(RtQuat * result, RtQuat * q);

extern void
RtQuatExp(RtQuat * result, RtQuat * q);

extern void
RtQuatPow(RtQuat * result, RtQuat * q, RwReal e);

extern void
RtQuatUnitLog(RtQuat * result, RtQuat * q);

extern void
RtQuatUnitExp(RtQuat * result, RtQuat * q);

extern void
RtQuatUnitPow(RtQuat * result, RtQuat * q, RwReal e);

extern void
RtQuatConvertToMatrix(const RtQuat * const qpQuat,
                      RwMatrix * const mpMatrix);

extern void
RtQuatUnitConvertToMatrix(const RtQuat * const qpQuat,
                          RwMatrix * const mpMatrix);

#endif /* ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) ) */

#ifdef    __cplusplus
}
#endif                          /* __cplusplus */

/*
 * Backwards compatibility code
 */

typedef RtQuat RpQuat;

#define RpAnimQuatConvertFromMatrix(qpQuat, mpMatrix)                      \
    RtQuatConvertFromMatrix(qpQuat, mpMatrix)

#define RpAnimQuatConvertToMatrix(qpQuat,mpMatrix)                         \
    RtQuatUnitConvertToMatrix(qpQuat,mpMatrix)


#endif                          /* RTQUAT_H */

