/*
===========================================================================
Copyright (C) 2024 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// skeletorbones.cpp : Skeletor bones

#include "q_shared.h"
#include "qcommon.h"
#include "skeletor.h"

char            *skelBone_Names[8];
ChannelNameTable skeletor_c::m_channelNames;
ChannelNameTable skeletor_c::m_boneNames;
skelBone_World   skeletor_c::m_worldBone;

skelBone_World::skelBone_World()
{
    m_isDirty = false;
}

void SkeletorLoadBoneFromBuffer(skelChannelList_c *boneList, boneData_t *boneData, skelBone_Base **bone)
{
    int            newBoneIndex;
    const char    *boneName;
    int            newBoneParent;
    skelBone_Base *parentBone;

    newBoneIndex = boneList->GetLocalFromGlobal(boneData->channel);

    if (!bone[newBoneIndex]) {
        boneName = skeletor_c::m_boneNames.FindName(boneData->channel);

        if (boneData->parent < 0) {
            newBoneParent = -1;
        } else {
            newBoneParent = boneList->GetLocalFromGlobal(boneData->parent);
        }

        switch (boneData->boneType) {
        case SKELBONE_ZERO:
            {
                skelBone_Zero *newBone = new skelBone_Zero;
                bone[newBoneIndex]     = newBone;
                break;
            }
        case SKELBONE_ROTATION:
            {
                skelBone_Rotation *newBone = new skelBone_Rotation;
                bone[newBoneIndex]         = newBone;
                newBone->SetChannels(boneData->channelIndex[0]);
                break;
            }
        case SKELBONE_POSROT:
            {
                skelBone_PosRot *newBone;

                if (!strncmp(boneName, "Bip01", 5)) {
                    newBone            = new skelBone_Root;
                    bone[newBoneIndex] = newBone;
                } else {
                    newBone            = new skelBone_PosRot;
                    bone[newBoneIndex] = newBone;
                }

                newBone->SetChannels(boneData->channelIndex[0], boneData->channelIndex[1]);
                break;
            }
        case SKELBONE_IKSHOULDER:
            {
                skelBone_IKshoulder *newBone = new skelBone_IKshoulder;
                bone[newBoneIndex]           = newBone;
                break;
            }
        case SKELBONE_IKELBOW:
            {
                skelBone_IKelbow *newBone = new skelBone_IKelbow;
                bone[newBoneIndex]        = newBone;
                newBone->SetBoneRefs((skelBone_IKshoulder *)bone[boneList->GetLocalFromGlobal(boneData->refIndex[0])]);
                break;
            }
        case SKELBONE_IKWRIST:
            {
                skelBone_IKwrist    *newBone = new skelBone_IKwrist;
                skelBone_IKshoulder *shoulder;
                bone[newBoneIndex] = newBone;
                shoulder           = (skelBone_IKshoulder *)bone[boneList->GetLocalFromGlobal(boneData->refIndex[0])];
                newBone->SetChannels(boneData->channelIndex[0], boneData->channelIndex[1]);
                newBone->SetBoneRefs(shoulder);
                shoulder->SetWristBone(newBone);
                break;
            }
        case SKELBONE_AVROT:
            {
                skelBone_AvRot *newBone = new skelBone_AvRot;
                skelBone_AvRot *ref1, *ref2;
                bone[newBoneIndex] = newBone;
                ref1               = (skelBone_AvRot *)bone[boneList->GetLocalFromGlobal(boneData->refIndex[0])];
                ref2               = (skelBone_AvRot *)bone[boneList->GetLocalFromGlobal(boneData->refIndex[1])];
                newBone->SetBoneRefs(ref1, ref2);
                break;
            }
        case SKELBONE_HOSEROT:
            {
                skelBone_HoseRot *newBone = new skelBone_HoseRot;
                bone[newBoneIndex]        = newBone;
                newBone->SetBoneRefs(bone[boneList->GetLocalFromGlobal(boneData->refIndex[0])]);
                break;
            }
        case SKELBONE_HOSEROTBOTH:
            {
                skelBone_HoseRotBoth *newBone = new skelBone_HoseRotBoth;
                bone[newBoneIndex]            = newBone;
                newBone->SetBoneRefs(bone[boneList->GetLocalFromGlobal(boneData->refIndex[0])]);
                break;
            }
        case SKELBONE_HOSEROTPARENT:
            {
                skelBone_HoseRotParent *newBone = new skelBone_HoseRotParent;
                bone[newBoneIndex]              = newBone;
                newBone->SetBoneRefs(bone[boneList->GetLocalFromGlobal(boneData->refIndex[0])]);
                break;
            }
        default:
            break;
        }

        bone[newBoneIndex]->SetBaseValue(boneData);

        if (newBoneParent < 0) {
            parentBone = &skeletor_c::m_worldBone;
        } else {
            parentBone = bone[newBoneParent];
        }

        bone[newBoneIndex]->SetParent(parentBone);
    }
}

void SkeletorLoadBonesFromBuffer(skelChannelList_c *boneList, skelHeaderGame_t *buffer, skelBone_Base **bone)
{
    int boneNum;

    for (boneNum = 0; boneNum < buffer->numBones; boneNum++) {
        SkeletorLoadBoneFromBuffer(boneList, &buffer->pBones[boneNum], bone);
    }
}

float *DecodeRLEPosValue(skanChannelHdr *channelFrames, int desiredFrameNum)
{
    skanGameFrame *foundFrame = channelFrames->ary_frames;
    size_t         frameSize;
    int            i;

    frameSize = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(vec3_t));

    for (i = 0; i < channelFrames->nFramesInChannel; i++) {
        if (foundFrame->nFrameNum >= desiredFrameNum) {
            break;
        }

        foundFrame = (skanGameFrame *)((byte *)foundFrame + frameSize);
    }

    if (foundFrame->nFrameNum > desiredFrameNum) {
        foundFrame = (skanGameFrame *)((byte *)channelFrames->ary_frames + foundFrame->nPrevFrameIndex * frameSize);
    }

    return foundFrame->pChannelData;
}

float *DecodeRLERotValue(skanChannelHdr *channelFrames, int desiredFrameNum)
{
    skanGameFrame *foundFrame = channelFrames->ary_frames;
    size_t         frameSize;
    int            i;

    frameSize = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(vec4_t));

    for (i = 0; i < channelFrames->nFramesInChannel; i++) {
        if (foundFrame->nFrameNum >= desiredFrameNum) {
            break;
        }

        foundFrame = (skanGameFrame *)((byte *)foundFrame + frameSize);
    }

    if (foundFrame->nFrameNum > desiredFrameNum) {
        foundFrame = (skanGameFrame *)((byte *)channelFrames->ary_frames + foundFrame->nPrevFrameIndex * frameSize);
    }

    return foundFrame->pChannelData;
}

SkelQuat skelAnimStoreFrameList_c::GetSlerpValue(int globalChannelNum) const
{
    SkelQuat  actionQuat, movementQuat;
    SkelQuat  outQuat;
    float     totalWeight;
    SkelQuat *pIncomingQuat;
    float     incomingWeight;
    int       localChannelNum;
    float     channelActionWeight;
    //float movementWeight;
    int                  nTotal;
    int                  i;
    const skanBlendInfo *pFrame;
    float                t;

    actionQuat.Set(0, 0, 0, 0);
    movementQuat.Set(0, 0, 0, 0);

    nTotal      = 0;
    totalWeight = 0.0;

    if (actionWeight > 0.001) {
        for (i = 0; i < numActionFrames; i++) {
            pFrame = &m_blendInfo[i + MAX_SKEL_BLEND_MOVEMENT_FRAMES];

            localChannelNum = pFrame->pAnimationData->channelList.GetLocalFromGlobal(globalChannelNum);
            if (localChannelNum >= 0) {
                incomingWeight = pFrame->weight;
                if (incomingWeight == 0.0) {
                    continue;
                }
                pIncomingQuat = (SkelQuat *)DecodeRLERotValue(
                    &pFrame->pAnimationData->ary_channels[localChannelNum], pFrame->frame
                );
                totalWeight += incomingWeight;
                nTotal++;

                if (DotProduct4(*pIncomingQuat, actionQuat) >= 0.0) {
                    actionQuat.x += pIncomingQuat->x * incomingWeight;
                    actionQuat.y += pIncomingQuat->y * incomingWeight;
                    actionQuat.z += pIncomingQuat->z * incomingWeight;
                    actionQuat.w += pIncomingQuat->w * incomingWeight;
                } else {
                    actionQuat.x -= pIncomingQuat->x * incomingWeight;
                    actionQuat.y -= pIncomingQuat->y * incomingWeight;
                    actionQuat.z -= pIncomingQuat->z * incomingWeight;
                    actionQuat.w -= pIncomingQuat->w * incomingWeight;
                }
            }
        }
    }

    if (nTotal) {
        channelActionWeight = actionWeight;
        if (nTotal > 1) {
            t = 1.0 / actionQuat.Length();
        } else {
            t = 1.0 / totalWeight;
        }

        actionQuat.x = actionQuat.x * t;
        actionQuat.y = actionQuat.y * t;
        actionQuat.z = actionQuat.z * t;
        actionQuat.w = actionQuat.w * t;
    } else {
        channelActionWeight = 0.0;
    }

    nTotal      = 0;
    totalWeight = 0.0;

    if (channelActionWeight < 0.999) {
        for (i = 0; i < numMovementFrames; i++) {
            pFrame = &m_blendInfo[i];

            localChannelNum = pFrame->pAnimationData->channelList.GetLocalFromGlobal(globalChannelNum);
            if (localChannelNum >= 0) {
                incomingWeight = pFrame->weight;
                if (incomingWeight == 0.0) {
                    continue;
                }
                pIncomingQuat = (SkelQuat *)DecodeRLERotValue(
                    &pFrame->pAnimationData->ary_channels[localChannelNum], pFrame->frame
                );
                totalWeight += incomingWeight;
                nTotal++;

                if (DotProduct4(*pIncomingQuat, movementQuat) >= 0.0) {
                    movementQuat.x += pIncomingQuat->x * incomingWeight;
                    movementQuat.y += pIncomingQuat->y * incomingWeight;
                    movementQuat.z += pIncomingQuat->z * incomingWeight;
                    movementQuat.w += pIncomingQuat->w * incomingWeight;
                } else {
                    movementQuat.x -= pIncomingQuat->x * incomingWeight;
                    movementQuat.y -= pIncomingQuat->y * incomingWeight;
                    movementQuat.z -= pIncomingQuat->z * incomingWeight;
                    movementQuat.w -= pIncomingQuat->w * incomingWeight;
                }
            }
        }
    }

    if (nTotal) {
        if (nTotal > 1) {
            t = 1.0 / movementQuat.Length();
        } else {
            t = 1.0 / totalWeight;
        }

        movementQuat.x = movementQuat.x * t;
        movementQuat.y = movementQuat.y * t;
        movementQuat.z = movementQuat.z * t;
        movementQuat.w = movementQuat.w * t;
    } else {
        movementQuat.w = 1.0;
    }

    if (channelActionWeight < 0.001) {
        outQuat.x = movementQuat.x;
        outQuat.y = movementQuat.y;
        outQuat.z = movementQuat.z;
        outQuat.w = movementQuat.w;
        return outQuat;
    } else if (channelActionWeight >= 0.999) {
        outQuat.x = actionQuat.x;
        outQuat.y = actionQuat.y;
        outQuat.z = actionQuat.z;
        outQuat.w = actionQuat.w;
        return outQuat;
    }

    t = 1.0 - channelActionWeight;

    if (DotProduct4(actionQuat, movementQuat) >= 0.0) {
        outQuat.x = movementQuat.x * t + actionQuat.x * channelActionWeight;
        outQuat.y = movementQuat.y * t + actionQuat.y * channelActionWeight;
        outQuat.z = movementQuat.z * t + actionQuat.z * channelActionWeight;
        outQuat.w = movementQuat.w * t + actionQuat.w * channelActionWeight;
    } else {
        outQuat.x = movementQuat.x * t - actionQuat.x * channelActionWeight;
        outQuat.y = movementQuat.y * t - actionQuat.y * channelActionWeight;
        outQuat.z = movementQuat.z * t - actionQuat.z * channelActionWeight;
        outQuat.w = movementQuat.w * t - actionQuat.w * channelActionWeight;
    }

    t         = 1.0 / outQuat.Length();
    outQuat.x = outQuat.x * t;
    outQuat.y = outQuat.y * t;
    outQuat.z = outQuat.z * t;
    outQuat.w = outQuat.w * t;

    return outQuat;
}

void skelAnimStoreFrameList_c::GetLerpValue3(int globalChannelNum, SkelVec3 *outVec) const
{
    float                totalWeight;
    SkelVec3             incomingVec;
    float                incomingWeight;
    int                  localChannelNum;
    vec3_t               result;
    float                t;
    float                channelActionWeight;
    int                  i;
    const skanBlendInfo *pFrame;

    totalWeight = 0.0;

    if (actionWeight > 0.001) {
        VectorClear(result);

        for (i = 0; i < numActionFrames; i++) {
            pFrame = &m_blendInfo[i + MAX_SKEL_BLEND_MOVEMENT_FRAMES];

            localChannelNum = pFrame->pAnimationData->channelList.GetLocalFromGlobal(globalChannelNum);
            if (localChannelNum >= 0) {
                incomingWeight = pFrame->weight;
                incomingVec = DecodeRLEPosValue(&pFrame->pAnimationData->ary_channels[localChannelNum], pFrame->frame);
                totalWeight += incomingWeight;
                result[0] += incomingVec[0] * incomingWeight;
                result[1] += incomingVec[1] * incomingWeight;
                result[2] += incomingVec[2] * incomingWeight;
            }
        }
    }

    if (totalWeight != 0.0) {
        t = 1.0 / totalWeight;
        VectorScale(result, t, *outVec);
        channelActionWeight = actionWeight;
    } else {
        VectorClear(*outVec);
        channelActionWeight = 0.0;
    }

    if (channelActionWeight >= 0.999 || !numMovementFrames) {
        return;
    }

    totalWeight = 0.0;
    VectorClear(result);

    for (i = 0; i < numMovementFrames; i++) {
        pFrame = &m_blendInfo[i];

        localChannelNum = pFrame->pAnimationData->channelList.GetLocalFromGlobal(globalChannelNum);
        if (localChannelNum >= 0) {
            incomingWeight = pFrame->weight;
            incomingVec    = DecodeRLEPosValue(&pFrame->pAnimationData->ary_channels[localChannelNum], pFrame->frame);
            totalWeight += incomingWeight;
            result[0] += incomingVec[0] * incomingWeight;
            result[1] += incomingVec[1] * incomingWeight;
            result[2] += incomingVec[2] * incomingWeight;
        }
    }

    if (totalWeight != 0.0) {
        t         = 1.0 / totalWeight * (1.0 - channelActionWeight);
        outVec->x = outVec->x * channelActionWeight + result[0] * t;
        outVec->y = outVec->y * channelActionWeight + result[1] * t;
        outVec->z = outVec->z * channelActionWeight + result[2] * t;
    }
}

skelBone_Base::skelBone_Base()
{
    m_parent     = NULL;
    m_isDirty    = true;
    m_controller = NULL;
}

skelBone_Base::~skelBone_Base() {}

int skelBone_Base::GetNumChannels(boneType_t boneType)
{
    switch (boneType) {
    case SKELBONE_ROTATION:
        return 1;
    case SKELBONE_POSROT:
    case SKELBONE_IKWRIST:
        return 2;
    default:
        return 0;
    }
}

int skelBone_Base::GetNumBoneRefs(boneType_t boneType)
{
    switch (boneType) {
    case SKELBONE_AVROT:
        return 2;
    case SKELBONE_IKELBOW:
    case SKELBONE_IKWRIST:
    case SKELBONE_HOSEROT:
    case SKELBONE_HOSEROTBOTH:
    case SKELBONE_HOSEROTPARENT:
        return 1;
    default:
        return 0;
    }
}

SkelMat4& skelBone_Base::GetTransform(const skelAnimStoreFrameList_c *frames)
{
    if (m_isDirty) {
        return GetDirtyTransform(frames);
    } else {
        return m_cachedValue;
    }
}

void skelBone_Base::SetParent(skelBone_Base *parent)
{
    m_parent = parent;
}

void skelBone_Base::SetBaseValue(boneData_t *boneData) {}

int skelBone_Base::GetChannelIndex(int num)
{
    return 0;
}

skelBone_Base *skelBone_Base::GetBoneRef(int num)
{
    return NULL;
}

skelBone_Base *skelBone_Base::Parent() const
{
    return m_parent;
}

SkelMat4& skelBone_Zero::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    if (m_parent) {
        m_cachedValue = m_parent->GetTransform(frames);
    }

    m_isDirty = false;
    return m_cachedValue;
}

void skelBone_Zero::SetBaseValue(boneData_t *boneData) {}

int skelBone_Zero::GetChannelIndex(int num)
{
    return 0;
}

skelBone_Base *skelBone_Zero::GetBoneRef(int num)
{
    return NULL;
}

SkelMat4& skelBone_Rotation::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    SkelMat4 incomingValue;
    SkelQuat incomingQuat;

    incomingQuat = frames->GetSlerpValue(m_quatChannel);
    incomingQuat.GetMat4(incomingValue);
    VectorCopy(m_baseValue, incomingValue[3]);

    if (m_parent) {
        m_cachedValue.Multiply(incomingValue, m_parent->GetTransform(frames));
    } else {
        m_cachedValue = incomingValue;
    }

    if (m_controller) {
        SkelMat3 m;

        incomingQuat.Set(m_controller);
        incomingQuat.GetMat3(m);
        m_cachedValue.RotateBy(m);
    }

    m_isDirty = false;

    return m_cachedValue;
}

void skelBone_Rotation::SetChannels(int num)
{
    m_quatChannel = num;
}

void skelBone_Rotation::SetBaseValue(boneData_t *data)
{
    m_baseValue = data->offset;
}

int skelBone_Rotation::GetChannelIndex(int num)
{
    return m_quatChannel;
}

skelBone_Base *skelBone_Rotation::GetBoneRef(int bone)
{
    return 0;
}

SkelMat4& skelBone_PosRot::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    SkelVec3 incomingOffset;
    SkelMat4 incomingValue;
    SkelQuat incomingQuat;

    incomingQuat = frames->GetSlerpValue(m_quatChannel);
    frames->GetLerpValue3(m_offsetChannel, &incomingOffset);
    incomingQuat.GetMat4(incomingValue);
    VectorCopy(incomingOffset, incomingValue[3]);

    if (m_parent) {
        m_cachedValue.Multiply(incomingValue, m_parent->GetTransform(frames));
    } else {
        m_cachedValue = incomingValue;
    }

    if (m_controller) {
        SkelMat3 m;

        incomingQuat.Set(m_controller);
        incomingQuat.GetMat3(m);
        m_cachedValue.RotateBy(m);
    }

    m_isDirty = false;

    return m_cachedValue;
}

void skelBone_PosRot::SetChannels(int quatChannel, int offsetChannel)
{
    m_quatChannel   = quatChannel;
    m_offsetChannel = offsetChannel;
}

void skelBone_PosRot::SetBaseValue(boneData_t *boneData) {}

int skelBone_PosRot::GetChannelIndex(int num)
{
    if (num) {
        return m_quatChannel;
    } else {
        return m_offsetChannel;
    }
}

skelBone_Base *skelBone_PosRot::GetBoneRef(int num)
{
    return NULL;
}

SkelMat4& skelBone_Root::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    SkelVec3 incomingOffset;
    SkelMat4 incomingValue;
    SkelQuat incomingQuat;

    incomingQuat = frames->GetSlerpValue(m_quatChannel);
    frames->GetLerpValue3(m_offsetChannel, &incomingOffset);
    incomingQuat.GetMat4(incomingValue);
    VectorCopy(incomingOffset, incomingValue[3]);

    if (m_parent) {
        m_cachedValue.Multiply(incomingValue, m_parent->GetTransform(frames));
    } else {
        m_cachedValue = incomingValue;
    }

    if (m_controller) {
        SkelMat3 m;

        incomingQuat.Set(m_controller);
        incomingQuat.GetMat3(m);
        m_cachedValue.RotateBy(m);
    }

    m_isDirty = false;

    return m_cachedValue;
}

skelBone_IKshoulder::skelBone_IKshoulder()
{
    m_wrist = NULL;
}

SkelMat4& skelBone_IKshoulder::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    SkelMat4 baseMatrix, targetMatrix;
    float    desiredLength;
    SkelVec3 newBaseY;
    SkelVec3 newBaseZ;
    SkelVec3 cross;
    float    cosUpperAngle;
    float    sinUpperAngle;
    float    maxLength;
    float    length;
    float    cosA;
    float    sinA;

    if (!m_wrist) {
        m_cachedValue.MakeIdentity();
        return m_cachedValue;
    }

    if (m_parent) {
        baseMatrix = m_parent->GetTransform(frames);
    }

    baseMatrix[3][0] +=
        m_baseValue[0] * baseMatrix[0][0] + m_baseValue[1] * baseMatrix[1][0] + m_baseValue[2] * baseMatrix[2][0];
    baseMatrix[3][1] +=
        m_baseValue[0] * baseMatrix[0][1] + m_baseValue[1] * baseMatrix[1][1] + m_baseValue[2] * baseMatrix[2][1];
    baseMatrix[3][2] +=
        m_baseValue[0] * baseMatrix[0][2] + m_baseValue[1] * baseMatrix[1][2] + m_baseValue[2] * baseMatrix[2][2];
    baseMatrix.InvertAxis(0);
    baseMatrix.InvertAxis(2);

    m_wristAngle = frames->GetSlerpValue(m_wrist->m_quatChannel);
    m_wristAngle.GetMat4(targetMatrix);

    frames->GetLerpValue3(m_wrist->m_offsetChannel, &m_wristPos);
    m_cachedValue = baseMatrix;
    VectorCopy(m_wristPos, targetMatrix[3]);
    VectorSubtract(targetMatrix[3], baseMatrix[3], m_cachedValue[0]);

    sinUpperAngle = VectorNormalize(m_cachedValue[0]);

    newBaseY = baseMatrix[2];
    newBaseZ = m_cachedValue[0];

    CrossProduct(newBaseY, newBaseZ, cross);
    VectorCopy(cross, m_cachedValue[1]);

    newBaseY = targetMatrix[2];
    newBaseZ = m_cachedValue[0];

    CrossProduct(newBaseY, newBaseZ, cross);
    VectorAdd(m_cachedValue[1], cross, m_cachedValue[1]);

    desiredLength = VectorNormalize(m_cachedValue[1]);

    if (!desiredLength) {
        m_cachedValue[1][1] = 1.0;
    }

    newBaseY = m_cachedValue[0];
    newBaseZ = m_cachedValue[1];

    CrossProduct(newBaseY, newBaseZ, cross);
    VectorCopy(cross, m_cachedValue[2]);

    cosUpperAngle = m_upperLength + m_lowerLength - 0.001;

    if (sinUpperAngle > cosUpperAngle) {
        sinUpperAngle = cosUpperAngle;

        m_wristPos = m_cachedValue[3];

        m_wristPos[0] += m_cachedValue[0][0] * cosUpperAngle;
        m_wristPos[1] += m_cachedValue[0][1] * cosUpperAngle;
        m_wristPos[2] += m_cachedValue[0][2] * cosUpperAngle;
    }

    maxLength = (sinUpperAngle * sinUpperAngle + m_upperLength * m_upperLength - m_lowerLength * m_lowerLength)
              / (sinUpperAngle * m_upperLength + sinUpperAngle * m_upperLength);

    if (maxLength > 1.0) {
        maxLength = 1.0;
    }

    m_cosElbowAngle =
        -((m_lowerLength * m_lowerLength + m_upperLength * m_upperLength - sinUpperAngle * sinUpperAngle)
          / (m_upperLength * m_lowerLength + m_upperLength * m_lowerLength));

    length = -sqrt(1.0 - maxLength * maxLength);

    cosA                = m_cachedValue[0][0];
    sinA                = m_cachedValue[1][0];
    m_cachedValue[0][0] = cosA * maxLength - sinA * length;
    m_cachedValue[1][0] = sinA * maxLength + cosA * length;

    cosA                = m_cachedValue[0][1];
    sinA                = m_cachedValue[1][1];
    m_cachedValue[0][1] = cosA * maxLength - sinA * length;
    m_cachedValue[1][1] = sinA * maxLength + cosA * length;

    cosA                = m_cachedValue[0][2];
    sinA                = m_cachedValue[1][2];
    m_cachedValue[0][2] = cosA * maxLength - sinA * length;
    m_cachedValue[1][2] = sinA * maxLength + cosA * length;

    m_isDirty = false;

    return m_cachedValue;
}

void skelBone_IKshoulder::SetBaseValue(boneData_t *boneData)
{
    m_baseValue = boneData->offset;
}

int skelBone_IKshoulder::GetChannelIndex(int num)
{
    return -1;
}

skelBone_Base *skelBone_IKshoulder::GetBoneRef(int num)
{
    return m_wrist;
}

void skelBone_IKshoulder::SetElbowValue(float elbowOffset)
{
    m_upperLength = elbowOffset;
}

void skelBone_IKshoulder::SetWristValue(float wristOffset)
{
    m_lowerLength = wristOffset;
}

void skelBone_IKshoulder::SetWristBone(skelBone_IKwrist *wrist)
{
    this->m_wrist = wrist;
}

float skelBone_IKshoulder::GetUpperLength()
{
    return m_upperLength;
}

float skelBone_IKshoulder::GetLowerLength()
{
    return m_lowerLength;
}

SkelMat4& skelBone_IKwrist::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    if (m_shoulder->m_isDirty) {
        m_shoulder->GetDirtyTransform(frames);
    }

    m_shoulder->m_wristAngle.GetMat4(m_cachedValue);
    VectorCopy(m_shoulder->m_wristPos, m_cachedValue[3]);
    m_isDirty = false;

    return m_cachedValue;
}

void skelBone_IKwrist::SetChannels(int quatChannel, int offsetChannel)
{
    m_quatChannel   = quatChannel;
    m_offsetChannel = offsetChannel;
}

void skelBone_IKwrist::SetBoneRefs(skelBone_IKshoulder *shoulder)
{
    m_shoulder = shoulder;
}

void skelBone_IKwrist::SetBaseValue(boneData_t *boneData)
{
    m_shoulder->SetWristValue(boneData->length);
}

int skelBone_IKwrist::GetChannelIndex(int num)
{
    return -1;
}

skelBone_Base *skelBone_IKwrist::GetBoneRef(int num)
{
    return m_shoulder;
}

SkelMat4& skelBone_IKelbow::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    float fLength;
    float cosA, sinA;

    m_cachedValue = m_shoulder->GetTransform(frames);

    m_cachedValue[3][0] += m_cachedValue[0][0] * m_shoulder->m_upperLength;
    m_cachedValue[3][1] += m_cachedValue[0][1] * m_shoulder->m_upperLength;
    m_cachedValue[3][2] += m_cachedValue[0][2] * m_shoulder->m_upperLength;

    fLength = sqrt(1.0 - m_shoulder->m_cosElbowAngle * m_shoulder->m_cosElbowAngle);

    cosA                = m_cachedValue[0][0];
    sinA                = m_cachedValue[1][0];
    m_cachedValue[0][0] = cosA * m_shoulder->m_cosElbowAngle - sinA * fLength;
    m_cachedValue[1][0] = sinA * m_shoulder->m_cosElbowAngle + cosA * fLength;

    cosA                = m_cachedValue[0][1];
    sinA                = m_cachedValue[1][1];
    m_cachedValue[0][1] = cosA * m_shoulder->m_cosElbowAngle - sinA * fLength;
    m_cachedValue[1][1] = sinA * m_shoulder->m_cosElbowAngle + cosA * fLength;

    cosA                = m_cachedValue[0][2];
    sinA                = m_cachedValue[1][2];
    m_cachedValue[0][2] = cosA * m_shoulder->m_cosElbowAngle - sinA * fLength;
    m_cachedValue[1][2] = sinA * m_shoulder->m_cosElbowAngle + cosA * fLength;

    m_isDirty = false;

    return m_cachedValue;
}

void skelBone_IKelbow::SetBoneRefs(skelBone_IKshoulder *shoulder)
{
    m_shoulder = shoulder;
}

void skelBone_IKelbow::SetBaseValue(boneData_t *boneData)
{
    m_shoulder->SetElbowValue(boneData->length);
}

int skelBone_IKelbow::GetChannelIndex(int num)
{
    return -1;
}

skelBone_Base *skelBone_IKelbow::GetBoneRef(int num)
{
    return m_shoulder;
}

skelBone_AvRot::skelBone_AvRot()
{
    m_reference1 = NULL;
    m_reference2 = NULL;
}

SkelMat4& skelBone_AvRot::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    SkelQuat temp1, temp2;
    SkelMat4 temp;

    temp1 = m_reference1->GetTransform(frames);
    temp2 = m_reference2->GetTransform(frames);

    // lerp values
    Slerp(temp1, temp2, m_bone2weight, &m_cachedQuat);

    VectorCopy(m_basePos, m_cachedValue[3]);

    if (m_parent) {
        temp.Multiply(m_cachedValue, m_parent->GetTransform(frames));
        m_cachedValue = temp;
    }

    m_cachedQuat.GetMat4(m_cachedValue);

    m_isDirty = false;

    return m_cachedValue;
}

void skelBone_AvRot::SetBoneRefs(skelBone_Base *ref1, skelBone_Base *ref2)
{
    m_reference1 = ref1;
    m_reference2 = ref2;
}

void skelBone_AvRot::SetBaseValue(boneData_t *boneData)
{
    m_basePos     = boneData->offset;
    m_bone2weight = boneData->weight;
}

int skelBone_AvRot::GetChannelIndex(int num)
{
    return -1;
}

skelBone_Base *skelBone_AvRot::GetBoneRef(int num)
{
    if (num) {
        return m_reference2;
    } else {
        return m_reference1;
    }
}

SkelMat4& skelBone_HoseRot::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    SkelMat4 mat;

    if (m_parent) {
        mat = m_parent->GetTransform(frames);
    }

    return GetDirtyTransform(mat, m_target->GetTransform(frames));
}

SkelMat4& skelBone_HoseRot::GetDirtyTransform(SkelMat4& myParentTM, SkelMat4& targetTM)
{
    SkelMat4 m;
    SkelMat4 invParentTM;
    SkelMat4 temp;
    SkelVec3 rotaxis;
    SkelVec3 targetup, targetaim;
    SkelVec3 upaxis, aimaxis;
    SkelVec3 tmp;
    SkelQuat targetQuat;
    float    l, s, c;
    float    angle, vScale;

    aimaxis   = myParentTM[0];
    targetaim = targetTM[0];

    CrossProduct(targetaim, aimaxis, rotaxis);

    s = VectorLengthSquared(rotaxis);

    if (s == 0.0) {
        rotaxis.x = 1.0;
    } else if (s != 1.0) {
        l = 1.0 / sqrt(s);
        VectorScale(rotaxis, l, rotaxis);
    }

    s = DotProduct(aimaxis, targetaim);
    if (s < 1.0) {
        if (s > -0.999) {
            angle = acos(s);
        } else {
            angle = 6.2831855f;
        }
    } else {
        angle = 0.0;
    }

    vScale = angle * m_bendRatio;
    if (vScale > m_bendMax) {
        vScale = m_bendMax;
    }

    temp[0][0] = myParentTM[0][0];
    temp[0][1] = myParentTM[1][0];
    temp[0][2] = myParentTM[2][0];
    temp[1][0] = myParentTM[0][1];
    temp[1][1] = myParentTM[1][1];
    temp[1][2] = myParentTM[2][1];
    temp[2][0] = myParentTM[0][2];
    temp[2][1] = myParentTM[1][2];
    temp[2][2] = myParentTM[2][2];
    temp[3][0] =
        -(myParentTM[0][0] * myParentTM[3][0] + myParentTM[0][1] * myParentTM[3][1]
          + myParentTM[0][2] * myParentTM[3][2]);
    temp[3][1] =
        -(myParentTM[1][0] * myParentTM[3][0] + myParentTM[1][1] * myParentTM[3][1]
          + myParentTM[1][2] * myParentTM[3][2]);
    temp[3][2] =
        -(myParentTM[2][0] * myParentTM[3][0] + myParentTM[2][1] * myParentTM[3][1]
          + myParentTM[2][2] * myParentTM[3][2]);

    m.Multiply(temp, myParentTM);

    VectorCopy(rotaxis, tmp);
    rotaxis[0] = tmp[0] * temp[0][0] + tmp[1] * temp[1][0] + tmp[2] * temp[2][0];
    rotaxis[1] = tmp[0] * temp[0][1] + tmp[1] * temp[1][1] + tmp[2] * temp[2][1];
    rotaxis[2] = tmp[0] * temp[0][2] + tmp[1] * temp[1][2] + tmp[2] * temp[2][2];

    targetaim.y = cos(vScale * 0.5);
    targetup.y  = rotaxis.x * targetaim.y;
    targetup.z  = rotaxis.y * targetaim.y;
    targetaim.x = rotaxis.z * targetaim.y;

    c = cos(vScale * 0.5);
    l = sqrt(1.0 - c * c);

    m_cachedQuat[0] = rotaxis[0] * l;
    m_cachedQuat[1] = rotaxis[1] * l;
    m_cachedQuat[2] = rotaxis[2] * l;
    m_cachedQuat[3] = c;

    if (m_spinRatio < 1.0) {
        m.Multiply(targetTM, temp);
        MatToQuat(m.val, targetQuat.val);
        Slerp(targetQuat, m_cachedQuat, m_spinRatio, &m_cachedQuat);
    }

    m_cachedQuat.GetMat4(m_cachedValue);
    VectorCopy(m_basePos, m_cachedValue[3]);

    m.Multiply(m_cachedValue, myParentTM);
    m_cachedValue = m;

    m_isDirty = false;

    return m_cachedValue;
}

void skelBone_HoseRot::SetBoneRefs(skelBone_Base *ref)
{
    m_target = ref;
}

void skelBone_HoseRot::SetBaseValue(boneData_t *boneData)
{
    m_bendRatio = boneData->bendRatio;
    m_bendMax   = boneData->bendMax;
    m_spinRatio = boneData->spinRatio;
    m_basePos   = boneData->offset;
}

int skelBone_HoseRot::GetChannelIndex(int num)
{
    return -1;
}

skelBone_Base *skelBone_HoseRot::GetBoneRef(int num)
{
    return m_target;
}

SkelMat4& skelBone_HoseRotBoth::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    SkelMat4 myParentTM;
    SkelMat4 targetTM;

    if (m_parent) {
        myParentTM = m_parent->GetTransform(frames);
        targetTM   = m_target->GetTransform(frames);
    }

    VectorInverse(targetTM[0]);
    VectorInverse(targetTM[2]);
    VectorInverse(myParentTM[0]);
    VectorInverse(myParentTM[2]);

    return skelBone_HoseRot::GetDirtyTransform(myParentTM, targetTM);
}

void skelBone_HoseRotBoth::SetBaseValue(boneData_t *boneData)
{
    skelBone_HoseRot::SetBaseValue(boneData);
    m_basePos.x = -m_basePos.x;
    m_basePos.z = -m_basePos.z;
}

SkelMat4& skelBone_HoseRotParent::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    SkelMat4 myParentTM;
    SkelMat4 targetTM;

    if (m_parent) {
        myParentTM = m_parent->GetTransform(frames);
        targetTM   = m_target->GetTransform(frames);
    }

    VectorInverse(myParentTM[0]);
    VectorInverse(myParentTM[2]);

    return skelBone_HoseRot::GetDirtyTransform(myParentTM, targetTM);
}

void skelBone_HoseRotParent::SetBaseValue(boneData_t *boneData)
{
    skelBone_HoseRot::SetBaseValue(boneData);
    m_basePos.x = -m_basePos.x;
    m_basePos.z = -m_basePos.z;
}

SkelMat4& skelBone_World::GetDirtyTransform(const skelAnimStoreFrameList_c *frames)
{
    m_cachedValue.MakeIdentity();
    m_isDirty = false;
    return m_cachedValue;
}

void skelBone_World::SetBaseValue(boneData_t *boneData) {}

int skelBone_World::GetChannelIndex(int num)
{
    return 0;
}

skelBone_Base *skelBone_World::GetBoneRef(int num)
{
    return NULL;
}
