/*
===========================================================================
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
===========================================================================
*/

// skeletor.cpp : Skeletor

#include "q_shared.h"
#include "qcommon.h"
#include "skeletor.h"
#include <tiki.h>

#define EPSILON 0.000000000001f

ChannelNameTable* skeletor_c::ChannelNames()
{
    return &m_channelNames;
}

int skelAnimDataGameHeader_s::GetFrameNums(
    float timeSeconds, float timeTolerance, int *beforeFrame, int *afterFrame, float *beforeWeight, float *afterWeight
)
{
    int frameNum1;
    int frameNum2;

    frameNum1 = (int)(timeSeconds / frameTime);
    frameNum2 = frameNum1 + 1;

    *afterWeight = (timeSeconds / frameTime) - (float)frameNum1;

    if ((1.0 - *afterWeight) * frameTime < timeTolerance || *afterWeight * frameTime < timeTolerance) {
        if (*afterWeight > 0.5) {
            frameNum1++;
        }

        if (frameNum1 >= numFrames) {
            if ((flags & TAF_DELTADRIVEN)) {
                frameNum1 %= numFrames;
            } else {
                frameNum1 = numFrames - 1;
            }
        }

        *beforeFrame  = frameNum1;
        *beforeWeight = 1.0;
        *afterFrame   = 0;
        *afterWeight  = 0.0;

        return 1;
    } else if (frameNum2 >= numFrames) {
        if ((flags & TAF_DELTADRIVEN)) {
            frameNum2 %= numFrames;
        } else {
            frameNum2 = numFrames - 1;
        }

        if (frameNum1 >= numFrames) {
            if ((flags & TAF_DELTADRIVEN)) {
                frameNum1 %= numFrames;
            } else {
                *beforeFrame  = numFrames - 1;
                *beforeWeight = 1.0;
                return 1;
            }
        }
    }

    *beforeFrame  = frameNum1;
    *afterFrame   = frameNum2;
    *beforeWeight = 1.0 - *afterWeight;

    return 2;
}

SkelVec3 skelAnimDataGameHeader_s::GetDeltaOverTime(float time1, float time2)
{
    float    deltaWeight1;
    int      frameNum1;
    float    deltaWeight2;
    int      frameNum2;
    SkelVec3 delta;
    int      currFrame;
    float    s, d;

    deltaWeight1 = time1 / frameTime;
    deltaWeight2 = time2 / frameTime;
    frameNum1    = ceil(deltaWeight1);
    frameNum2    = ceil(deltaWeight2);

    d = frameNum1 - deltaWeight1;
    s = 1.0 - (frameNum2 - deltaWeight2);

    if (frameNum1 < frameNum2) {
        delta = m_frame[frameNum1 % numFrames].delta;
        VectorScale(delta, d, delta);

        for (currFrame = frameNum1 + 1; currFrame < frameNum2; currFrame++) {
            delta += m_frame[currFrame % numFrames].delta;
        }

        delta.x += m_frame[frameNum2 % numFrames].delta.x * s;
        delta.y += m_frame[frameNum2 % numFrames].delta.y * s;
        delta.z += m_frame[frameNum2 % numFrames].delta.z * s;
    } else {
        s = s - (1.0 - d);
        delta.x = m_frame[frameNum2 % numFrames].delta.x * s;
        delta.y = m_frame[frameNum2 % numFrames].delta.y * s;
        delta.z = m_frame[frameNum2 % numFrames].delta.z * s;
    }

    if (delta.x > -0.001f && delta.x < 0.001f) {
        delta.x = 0;
    }
    if (delta.y > -0.001f && delta.y < 0.001f) {
        delta.y = 0;
    }
    if (delta.z > -0.001f && delta.z < 0.001f) {
        delta.z = 0;
    }

    return delta;
}

float skelAnimDataGameHeader_s::GetAngularDeltaOverTime(float time1, float time2)
{
    float deltaWeight1;
    int   frameNum1;
    float deltaWeight2;
    int   frameNum2;
    float delta;
    int   currFrame;
    float s, d;

    deltaWeight1 = time1 / frameTime;
    deltaWeight2 = time2 / frameTime;
    frameNum1    = ceil(deltaWeight1);
    frameNum2    = ceil(deltaWeight2);

    d     = frameNum1 - deltaWeight1;
    s     = 1.0 - (frameNum2 - deltaWeight2);
    delta = 0;

    if (frameNum1 < frameNum2) {
        delta = m_frame[frameNum1 % numFrames].angleDelta * d;

        for (currFrame = frameNum1 + 1; currFrame < frameNum2; currFrame++) {
            delta += m_frame[currFrame % numFrames].angleDelta;
        }

        delta += m_frame[frameNum2 % numFrames].angleDelta * s;
    } else {
        s = s - (1.0 - d);
        delta = m_frame[frameNum2 % numFrames].angleDelta;
    }

    if (delta > -0.001f && delta < 0.001f) {
        delta = 0;
    }

    return delta;
}

skelAnimDataGameHeader_t *skelAnimDataGameHeader_s::AllocAnimData(size_t numFrames, size_t numChannels)
{
    skelAnimDataGameHeader_t *data;

    const size_t animSize =
        sizeof(skelAnimDataGameHeader_t) + (16 * (3 * numFrames - 3)) + 16 * numChannels * numFrames;

    data        = (skelAnimDataGameHeader_t *)Skel_Alloc(animSize);
    data->flags = 0;
    data->channelList.InitChannels();
    data->nBytesUsed = animSize;
    data->numFrames  = numFrames;
    return data;
}

skelAnimDataGameHeader_s *skelAnimDataGameHeader_s::AllocRLEChannelData(size_t numChannels)
{
    int                       animSize;
    skelAnimDataGameHeader_t *data;

    animSize             = sizeof(skelAnimDataGameHeader_t) + (numChannels - 1) * sizeof(skanChannelHdr);
    data                 = (skelAnimDataGameHeader_t *)Skel_Alloc(animSize);
    data->flags          = 0;
    data->nTotalChannels = numChannels;
    data->channelList.InitChannels();
    data->nBytesUsed = animSize;
    return data;
}

skeletor_c::skeletor_c(dtiki_t *tiki)
{
    int               numBones;
    int               mesh;
    skelHeaderGame_t *skelmodel;

    m_Tiki = tiki;

    m_morphTargetList.InitChannels();
    m_morphTargetList.ZeroChannels();

    m_frameBounds[0].set(-32, -32, 0);
    m_frameBounds[1].set(32, 32, 28);
    m_frameRadius                 = 64;
    m_frameList.numActionFrames   = 0;
    m_frameList.numMovementFrames = 0;
    m_targetLookLeft              = 0;
    m_targetLookRight             = 0;
    m_targetLookUp                = 0;
    m_targetLookDown              = 0;
    m_targetLookCrossed           = 0;
    m_targetBlink                 = 0;

    VectorClear(m_eyeTargetPos);
    VectorClear(m_eyePrevTargetPos);

    m_timeNextBlink = Sys_Milliseconds();
    numBones        = m_Tiki->m_boneList.NumChannels();
    m_bone          = (skelBone_Base **)Skel_Alloc(numBones * sizeof(skelBone_Base *));
    memset(m_bone, 0, numBones * sizeof(skelBone_Base *));

    for (mesh = 0; mesh < m_Tiki->numMeshes; mesh++) {
        skelmodel = TIKI_GetSkel(m_Tiki->mesh[mesh]);
        SkeletorLoadBonesFromBuffer(&m_Tiki->m_boneList, skelmodel, m_bone);
        LoadMorphTargetNames(skelmodel);
    }

    m_morphTargetList.PackChannels();
    m_headBoneIndex = m_Tiki->GetBoneNumFromName("Bip01 Head");
}

skeletor_c::~skeletor_c()
{
    int i;

    for (i = 0; i < m_Tiki->m_boneList.m_numChannels; i++) {
        delete m_bone[i];
    }

    m_morphTargetList.CleanUpChannels();
    Skel_Free(m_bone);
    m_bone = NULL;
}

void skelAnimDataGameHeader_s::DeallocAnimData(skelAnimDataGameHeader_t *data)
{
    skanChannelHdr *pChannel;
    int             i;
    int             channelType;

    if (!data) {
        return;
    }

    for (i = 0; i < data->nTotalChannels; i++) {
        pChannel = &data->ary_channels[i];
        channelType = GetBoneChannelType(data->channelList.ChannelName(skeletor_c::ChannelNames(), i));

        if (channelType != 2) {
            Skel_Free(pChannel->ary_frames);
        }
    }

    data->channelList.CleanUpChannels();

    if (data->m_frame) {
        Skel_Free(data->m_frame);
    }

    Skel_Free(data);
}

void ConvertToRotationName(const char *boneName, char *rotChannelName)
{
    strcpy(rotChannelName, boneName);
    strcat(rotChannelName, " rot");
}

void ConvertToPositionName(const char *boneName, char *posChannelName)
{
    strcpy(posChannelName, boneName);
    strcat(posChannelName, " rot");
}

void ConvertToFKRotationName(const char *boneName, char *rotChannelName)
{
    strcpy(rotChannelName, boneName);
    strcat(rotChannelName, " rot");
    strcat(rotChannelName, "FK");
}

void ConvertToFKPositionName(const char *boneName, char *rotChannelName)
{
    strcpy(rotChannelName, boneName);
    strcat(rotChannelName, " pos");
    strcat(rotChannelName, "FK");
}

int GetHighestFloat(float *selection)
{
    float currentHighest;
    int   currentHighestIndex;
    int   i;

    currentHighest      = selection[0];
    currentHighestIndex = 0;

    for (i = 0; i < 8; i++) {
        if (selection[i] > currentHighest) {
            currentHighest      = selection[i];
            currentHighestIndex = i;
        }
    }

    return currentHighestIndex;
}

void AddToBounds(SkelVec3 *bounds, SkelVec3 *newBounds)
{
    int i;

    for (i = 0; i < 3; i++) {
        if (bounds[0][i] > newBounds[0][i]) {
            bounds[0][i] = newBounds[0][i];
        }

        if (bounds[1][i] < newBounds[1][i]) {
            bounds[1][i] = newBounds[1][i];
        }
    }
}

void skeletor_c::SetPose(
    const frameInfo_t *frameInfo, const int *contIndices, const vec4_t *contValues, float actionWeight
)
{
    skelAnimDataGameHeader_t *animData;
    short int                *aliases;
    int                       boneNum;
    int                       blendNum;
    int                       i;
    int                       blendFrame;
    int                       movementBlendFrame;
    int                       actionBlendFrame;
    int                       realAnimIndex;
    int                       beforeFrame;
    int                       afterFrame;
    float                     beforeWeight;
    float                     afterWeight;
    int                       numFramesAdded;
    float                     cutoff_weight;
    int                       contNum;
    float                     animWeight;
    skanBlendInfo            *frame1, *frame2;

    for (i = 0; i < m_Tiki->m_boneList.NumChannels(); i++) {
        m_bone[i]->m_controller = NULL;
        m_bone[i]->m_isDirty    = true;
    }

    if (contIndices && contValues) {
        for (contNum = 0; contNum < 5; contNum++) {
            boneNum = contIndices[contNum];
            // Added in 2.0.
            //  Make sure the bone is a valid channel
            if (boneNum < 0 || boneNum >= m_Tiki->m_boneList.NumChannels()) {
                continue;
            }

            cutoff_weight = (contValues[contNum][3] - 1.0) * (contValues[contNum][3] - 1.0);
            if (cutoff_weight >= EPSILON) {
                m_bone[boneNum]->m_controller = (float *)contValues[contNum];
            }
        }
    }

    for (i = 0; i < 3; i++) {
        m_frameBounds[0][i] = -2.0f;
        m_frameBounds[1][i] = 2.0f;
    }

    m_frameRadius      = 2.0f;
    animWeight         = 0.0f;
    movementBlendFrame = 0;
    actionBlendFrame   = MAX_SKEL_BLEND_MOVEMENT_FRAMES;
    aliases            = m_Tiki->a->m_aliases;

    for (blendNum = 0; blendNum < MAX_FRAMEINFOS; blendNum++) {
        const frameInfo_t& currentFrame = frameInfo[blendNum];

        if (animWeight < currentFrame.weight) {
            animWeight = currentFrame.weight;
        }
    }

    cutoff_weight = animWeight * 0.01f;

    for (blendNum = 0; blendNum < MAX_FRAMEINFOS; blendNum++) {
        const frameInfo_t& currentFrame = frameInfo[blendNum];

        if (currentFrame.weight <= cutoff_weight) {
            continue;
        }

        // Added in 2.0.
        //  Validate the index number
        if (currentFrame.index < 0 || currentFrame.index > m_Tiki->a->num_anims) {
            continue;
        }

        realAnimIndex = aliases[currentFrame.index];
        // Added in 2.0.
        //  Check if the alias is within bounds
        if (realAnimIndex < 0 || realAnimIndex >= MAX_TIKI_ALIASES) {
            continue;
        }

        animData = SkeletorCacheGetData(realAnimIndex);
        // Added in 2.0.
        //  Make sure the anim data is valid
        if (!animData) {
            continue;
        }

        if (animData->bHasDelta) {
            blendFrame = movementBlendFrame;
        } else {
            blendFrame = actionBlendFrame;
        }

        beforeWeight = 0.0;
        afterWeight  = 0.0;
        beforeFrame  = 0.0;
        afterFrame   = 0.0;

        numFramesAdded =
            animData->GetFrameNums(currentFrame.time, 0.001f, &beforeFrame, &afterFrame, &beforeWeight, &afterWeight);

        frame1                 = &m_frameList.m_blendInfo[blendFrame];
        frame1->frame          = beforeFrame;
        frame1->pAnimationData = animData;
        frame1->weight         = beforeWeight * currentFrame.weight;

        AddToBounds(m_frameBounds, animData->bounds);

        if (frame1->pAnimationData->m_frame[frame1->frame].radius > m_frameRadius) {
            m_frameRadius = frame1->pAnimationData->m_frame[frame1->frame].radius;
        }

        if (numFramesAdded == 2) {
            frame2                 = &m_frameList.m_blendInfo[blendFrame + 1];
            frame2->frame          = afterFrame;
            frame2->pAnimationData = animData;
            frame2->weight         = afterWeight * currentFrame.weight;
            AddToBounds(m_frameBounds, animData->bounds);

            if (frame2->pAnimationData->m_frame[frame2->frame].radius > m_frameRadius) {
                m_frameRadius = frame2->pAnimationData->m_frame[frame2->frame].radius;
            }
        }

        if (animData->bHasDelta) {
            movementBlendFrame = blendFrame + numFramesAdded;
        } else {
            actionBlendFrame = blendFrame + numFramesAdded;
        }
    }

    for (i = 0; i < 3; i++) {
        m_frameBounds[0][i] += -7.0f;
        m_frameBounds[1][i] += 7.0f;
    }

    m_frameList.numMovementFrames = movementBlendFrame;
    m_frameList.numActionFrames   = actionBlendFrame - MAX_SKEL_BLEND_MOVEMENT_FRAMES;
    m_frameList.actionWeight      = actionWeight;

    assert(m_frameList.numMovementFrames < MAX_SKEL_BLEND_MOVEMENT_FRAMES);
    assert(m_frameList.numActionFrames < MAX_SKEL_BLEND_ACTION_FRAMES);
}

static SkelMat4 GetGlobalDefaultPosition(skelBone_Base *bone)
{
    SkelMat4 lLocalPosition;
    SkelMat4 lGlobalPosition;
    SkelMat4 lParentGlobalPosition;

    lLocalPosition = bone->GetTransform(NULL);

    if (bone->Parent()) {
        //lParentGlobalPosition = GetGlobalDefaultPosition( bone->Parent() );
        //lGlobalPosition.Multiply( lParentGlobalPosition, lLocalPosition );
        lGlobalPosition = lLocalPosition;
    } else {
        lGlobalPosition = lLocalPosition;
    }

    return lGlobalPosition;
}

SkelMat4 GlobalToLocal(skelBone_Base *bone, SkelMat4 pGlobalPosition)
{
    SkelMat4 lLocalPosition;
    SkelMat4 lParentGlobalPosition;

    if (bone->Parent()) {
        lParentGlobalPosition = GetGlobalDefaultPosition(bone->Parent());
        lParentGlobalPosition.Inverse();
        lLocalPosition.Multiply(lParentGlobalPosition, pGlobalPosition);
    } else {
        lLocalPosition = pGlobalPosition;
    }

    return lLocalPosition;
}

void BoneGetFrames(
    skelHeaderGame_t         *skelmodel,
    skelAnimDataGameHeader_t *animData,
    skelChannelList_c        *boneList,
    int                       boneNum,
    Container<skanAnimFrame>& outFrames
)
{
    int             numBones;
    skelBone_Base **bone;
    int             i;
    skanAnimFrame   frame;
    int             localChannelNum;

    outFrames.FreeObjectList();

    numBones = boneList->NumChannels();

    bone = (skelBone_Base **)Skel_Alloc(sizeof(skelBone_Base *) * numBones);
    memset(bone, 0, sizeof(skelBone_Base *) * numBones);

    SkeletorLoadBonesFromBuffer(boneList, skelmodel, bone);

    for (i = 0; i < numBones; i++) {
        bone[i]->m_controller = NULL;
        bone[i]->m_isDirty    = true;
    }

    // process the rot channel
    localChannelNum = animData->channelList.GetLocalFromGlobal(bone[boneNum]->GetChannelIndex(1));
    if (localChannelNum >= 0) {
        skanChannelHdr *channel = &animData->ary_channels[localChannelNum];

        for (i = 0; i < channel->nFramesInChannel; i++) {
            skanGameFrame *pFrame = &channel->ary_frames[i];

            frame.nFrameNum = pFrame->nFrameNum;
            VectorClear(frame.pos);
            QuatToAngles(pFrame->pChannelData, frame.rot);

            outFrames.AddObject(frame);
        }
    }

    if (bone[i]->GetChannelIndex(0) != bone[i]->GetChannelIndex(1)) {
        // process the pos channel
        localChannelNum = animData->channelList.GetLocalFromGlobal(bone[boneNum]->GetChannelIndex(0));
        if (localChannelNum >= 0) {
            skanChannelHdr *channel = &animData->ary_channels[localChannelNum];

            for (i = 0; i < channel->nFramesInChannel; i++) {
                skanGameFrame *pFrame    = &channel->ary_frames[i];
                skanAnimFrame *pOutFrame = &outFrames[i];

                VectorCopy(pFrame->pChannelData, pOutFrame->pos);
            }
        }
    }
}

void SkeletorGetAnimFrame2(
    skelHeaderGame_t         *skelmodel,
    skelChannelList_c        *boneList,
    skelBoneCache_t          *bones,
    skelAnimStoreFrameList_c *frameList,
    float                    *radius,
    vec3_t                   *mins,
    vec3_t                   *maxes
)
{
    int              numBones;
    skelBone_Base  **bone;
    int              i;
    skelAnimFrame_t *newFrame;

    numBones = skelmodel->numBones;

    bone = (skelBone_Base **)Skel_Alloc(sizeof(skelBone_Base *) * numBones);
    memset(bone, 0, sizeof(skelBone_Base *) * numBones);

    SkeletorLoadBonesFromBuffer(boneList, skelmodel, bone);

    for (i = 0; i < numBones; i++) {
        bone[i]->m_controller = NULL;
        bone[i]->m_isDirty    = true;
    }

    newFrame            = (skelAnimFrame_t *)Skel_Alloc(sizeof(skelAnimFrame_t) + sizeof(SkelMat4) * numBones);
    newFrame->radius    = 0;
    newFrame->bounds[0] = SkelVec3();
    newFrame->bounds[1] = SkelVec3();

    for (i = 0; i < numBones; i++) {
        //skelBone_Base *Parent = bone[ i ]->Parent();
        //bone[ i ]->SetParent( &skeletor_c::m_worldBone );
        newFrame->bones[i] = bone[i]->GetTransform(frameList);
        //bone[ i ]->SetParent( Parent );
    }

    for (i = 0; i < numBones; i++) {
        VectorCopy(newFrame->bones[i][3], bones[i].offset);
        bones[i].matrix[0][0] = newFrame->bones[i][0][0];
        bones[i].matrix[0][1] = newFrame->bones[i][0][1];
        bones[i].matrix[0][2] = newFrame->bones[i][0][2];
        bones[i].matrix[0][3] = 0;
        bones[i].matrix[1][0] = newFrame->bones[i][1][0];
        bones[i].matrix[1][1] = newFrame->bones[i][1][1];
        bones[i].matrix[1][2] = newFrame->bones[i][1][2];
        bones[i].matrix[1][3] = 0;
        bones[i].matrix[2][0] = newFrame->bones[i][2][0];
        bones[i].matrix[2][1] = newFrame->bones[i][2][1];
        bones[i].matrix[2][2] = newFrame->bones[i][2][2];
        bones[i].matrix[2][3] = 0;
    }

    for (i = 0; i < numBones; i++) {
        delete bone[i];
    }

    Skel_Free(bone);

    if (radius) {
        *radius = newFrame->radius;
    }

    if (mins || maxes) {
        for (i = 0; i < 3; i++) {
            if (mins) {
                (*mins)[i] = newFrame->bounds[0][i];
            }
            if (maxes) {
                (*maxes)[i] = newFrame->bounds[1][i];
            }
        }
    }

    Skel_Free(newFrame);
}

void SkeletorGetAnimFrame(
    skelHeaderGame_t         *skelmodel,
    skelAnimDataGameHeader_t *animData,
    skelChannelList_c        *boneList,
    skelBoneCache_t          *bones,
    int                       frame,
    float                    *radius,
    vec3_t                   *mins,
    vec3_t                   *maxes
)
{
    int                      numBones;
    skelBone_Base          **bone;
    int                      i;
    skelAnimStoreFrameList_c frameList;
    skelAnimFrame_t         *newFrame;

    frameList.actionWeight = animData ? 1.0 : 0;

    if (animData) {
        if (!animData->bHasDelta) {
            frameList.numMovementFrames              = 0;
            frameList.numActionFrames                = 1;

            frameList.m_blendInfo[MAX_SKEL_BLEND_MOVEMENT_FRAMES].weight         = 1.0;
            frameList.m_blendInfo[MAX_SKEL_BLEND_MOVEMENT_FRAMES].pAnimationData = animData;
            frameList.m_blendInfo[MAX_SKEL_BLEND_MOVEMENT_FRAMES].frame          = frame;
        } else {
            frameList.numMovementFrames             = 1;
            frameList.numActionFrames               = 0;
            frameList.m_blendInfo[0].weight         = 1.0;
            frameList.m_blendInfo[0].pAnimationData = animData;
            frameList.m_blendInfo[0].frame          = frame;
        }
    }
    numBones = skelmodel->numBones;

    bone = (skelBone_Base **)Skel_Alloc(sizeof(skelBone_Base *) * numBones);
    memset(bone, 0, sizeof(skelBone_Base *) * numBones);

    SkeletorLoadBonesFromBuffer(boneList, skelmodel, bone);

    for (i = 0; i < numBones; i++) {
        bone[i]->m_controller = NULL;
        bone[i]->m_isDirty    = true;
    }

    newFrame = (skelAnimFrame_t *)Skel_Alloc(sizeof(skelAnimFrame_t) + sizeof(SkelMat4) * numBones);

    if (animData) {
        if (animData->m_frame) {
            newFrame->radius = animData->m_frame->radius;
        } else {
            newFrame->radius = 0;
        }

        newFrame->bounds[0] = animData->bounds[0];
        newFrame->bounds[1] = animData->bounds[1];
    } else {
        newFrame->radius    = 0;
        newFrame->bounds[0] = SkelVec3();
        newFrame->bounds[1] = SkelVec3();
    }

    if (animData) {
        for (i = 0; i < numBones; i++) {
            //skelBone_Base *Parent = bone[ i ]->Parent();
            //bone[ i ]->SetParent( &skeletor_c::m_worldBone );
            newFrame->bones[i] = bone[i]->GetTransform(&frameList);
            //bone[ i ]->SetParent( Parent );
        }
    } else {
        for (i = 0; i < numBones; i++) {
            newFrame->bones[i] = SkelMat4();
        }
    }

    for (i = 0; i < numBones; i++) {
        VectorCopy(newFrame->bones[i][3], bones[i].offset);
        bones[i].matrix[0][0] = newFrame->bones[i][0][0];
        bones[i].matrix[0][1] = newFrame->bones[i][0][1];
        bones[i].matrix[0][2] = newFrame->bones[i][0][2];
        bones[i].matrix[0][3] = 0;
        bones[i].matrix[1][0] = newFrame->bones[i][1][0];
        bones[i].matrix[1][1] = newFrame->bones[i][1][1];
        bones[i].matrix[1][2] = newFrame->bones[i][1][2];
        bones[i].matrix[1][3] = 0;
        bones[i].matrix[2][0] = newFrame->bones[i][2][0];
        bones[i].matrix[2][1] = newFrame->bones[i][2][1];
        bones[i].matrix[2][2] = newFrame->bones[i][2][2];
        bones[i].matrix[2][3] = 0;
    }

    for (i = 0; i < numBones; i++) {
        delete bone[i];
    }

    Skel_Free(bone);

    if (radius) {
        *radius = newFrame->radius;
    }

    if (mins || maxes) {
        for (i = 0; i < 3; i++) {
            if (mins) {
                (*mins)[i] = newFrame->bounds[0][i];
            }
            if (maxes) {
                (*maxes)[i] = newFrame->bounds[1][i];
            }
        }
    }

    Skel_Free(newFrame);
}

void TIKI_GetSkelAnimFrameInternal2(
    dtiki_t                  *tiki,
    skelBoneCache_t          *bones,
    skelAnimStoreFrameList_c *frameList,
    float                    *radius,
    vec3_t                   *mins,
    vec3_t                   *maxes
)
{
    //int boneNum;
    int              numBones;
    skelBone_Base  **bone;
    int              i;
    skelAnimFrame_t *newFrame;
    //int realAnimIndex;
    //skanBlendInfo *frame;
    skelHeaderGame_t *skelmodel;

    numBones = tiki->m_boneList.NumChannels();

    bone = (skelBone_Base **)Skel_Alloc(sizeof(skelBone_Base *) * numBones);
    memset(bone, 0, sizeof(skelBone_Base *) * numBones);

    for (i = 0; i < tiki->numMeshes; i++) {
        skelmodel = TIKI_GetSkel(tiki->mesh[i]);
        SkeletorLoadBonesFromBuffer(&tiki->m_boneList, skelmodel, bone);
    }

    for (i = 0; i < numBones; i++) {
        bone[i]->m_controller = NULL;
        bone[i]->m_isDirty    = true;
    }

    newFrame = (skelAnimFrame_t *)Skel_Alloc(sizeof(skelAnimFrame_t) + sizeof(SkelMat4) * numBones);

    /*
	if (animData)
	{
		if (animData->m_frame)
		{
			newFrame->radius = animData->m_frame->radius;
		}
		else
		{
			newFrame->radius = 0;
		}

		newFrame->bounds[0] = animData->bounds[0];
		newFrame->bounds[1] = animData->bounds[1];
	}
	else
	*/
    {
        newFrame->radius    = 0;
        newFrame->bounds[0] = SkelVec3();
        newFrame->bounds[1] = SkelVec3();
    }

    for (i = 0; i < numBones; i++) {
        newFrame->bones[i] = bone[i]->GetTransform(frameList);
    }

    for (i = 0; i < numBones; i++) {
        delete bone[i];
    }

    Skel_Free(bone);

    for (i = 0; i < numBones; i++) {
        VectorCopy(newFrame->bones[i][3], bones[i].offset);
        bones[i].matrix[0][0] = newFrame->bones[i][0][0];
        bones[i].matrix[0][1] = newFrame->bones[i][0][1];
        bones[i].matrix[0][2] = newFrame->bones[i][0][2];
        bones[i].matrix[0][3] = 0;
        bones[i].matrix[1][0] = newFrame->bones[i][1][0];
        bones[i].matrix[1][1] = newFrame->bones[i][1][1];
        bones[i].matrix[1][2] = newFrame->bones[i][1][2];
        bones[i].matrix[1][3] = 0;
        bones[i].matrix[2][0] = newFrame->bones[i][2][0];
        bones[i].matrix[2][1] = newFrame->bones[i][2][1];
        bones[i].matrix[2][2] = newFrame->bones[i][2][2];
        bones[i].matrix[2][3] = 0;
    }

    if (radius) {
        *radius = newFrame->radius;
    }

    if (mins && maxes) {
        for (i = 0; i < 3; i++) {
            (*mins)[i]  = newFrame->bounds[0][i];
            (*maxes)[i] = newFrame->bounds[1][i];
        }
    }

    Skel_Free(newFrame);
}

void TIKI_GetSkelAnimFrameInternal(
    dtiki_t                  *tiki,
    skelBoneCache_t          *bones,
    skelAnimDataGameHeader_t *animData,
    int                       frame,
    float                    *radius,
    vec3_t                   *mins,
    vec3_t                   *maxes
)
{
    //int boneNum;
    int                      numBones;
    skelBone_Base          **bone;
    int                      i;
    skelAnimStoreFrameList_c frameList;
    skelAnimFrame_t         *newFrame;
    //int realAnimIndex;
    //skanBlendInfo *frame;
    skelHeaderGame_t *skelmodel;

    frameList.actionWeight = animData ? 1.0 : 0;
    if (!animData || !animData->bHasDelta) {
        frameList.numMovementFrames              = 0;
        frameList.numActionFrames                = 1;

        frameList.m_blendInfo[MAX_SKEL_BLEND_MOVEMENT_FRAMES].weight         = 1.0;
        frameList.m_blendInfo[MAX_SKEL_BLEND_MOVEMENT_FRAMES].pAnimationData = animData;
        frameList.m_blendInfo[MAX_SKEL_BLEND_MOVEMENT_FRAMES].frame          = frame;
    } else {
        frameList.numMovementFrames             = 1;
        frameList.numActionFrames               = 0;
        frameList.m_blendInfo[0].weight         = 1.0;
        frameList.m_blendInfo[0].pAnimationData = animData;
        frameList.m_blendInfo[0].frame          = frame;
    }
    numBones = tiki->m_boneList.NumChannels();

    bone = (skelBone_Base **)Skel_Alloc(sizeof(skelBone_Base *) * numBones);
    memset(bone, 0, sizeof(skelBone_Base *) * numBones);

    for (i = 0; i < tiki->numMeshes; i++) {
        skelmodel = TIKI_GetSkel(tiki->mesh[i]);
        SkeletorLoadBonesFromBuffer(&tiki->m_boneList, skelmodel, bone);
    }

    for (i = 0; i < numBones; i++) {
        bone[i]->m_controller = NULL;
        bone[i]->m_isDirty    = true;
    }

    newFrame = (skelAnimFrame_t *)Skel_Alloc(sizeof(skelAnimFrame_t) + sizeof(SkelMat4) * numBones);

    if (animData) {
        if (animData->m_frame) {
            newFrame->radius = animData->m_frame->radius;
        } else {
            newFrame->radius = 0;
        }

        newFrame->bounds[0] = animData->bounds[0];
        newFrame->bounds[1] = animData->bounds[1];
    } else {
        newFrame->radius    = 0;
        newFrame->bounds[0] = SkelVec3();
        newFrame->bounds[1] = SkelVec3();
    }

    for (i = 0; i < numBones; i++) {
        newFrame->bones[i] = bone[i]->GetTransform(&frameList);
    }

    for (i = 0; i < numBones; i++) {
        delete bone[i];
    }

    Skel_Free(bone);

    for (i = 0; i < numBones; i++) {
        VectorCopy(newFrame->bones[i][3], bones[i].offset);
        bones[i].matrix[0][0] = newFrame->bones[i][0][0];
        bones[i].matrix[0][1] = newFrame->bones[i][0][1];
        bones[i].matrix[0][2] = newFrame->bones[i][0][2];
        bones[i].matrix[0][3] = 0;
        bones[i].matrix[1][0] = newFrame->bones[i][1][0];
        bones[i].matrix[1][1] = newFrame->bones[i][1][1];
        bones[i].matrix[1][2] = newFrame->bones[i][1][2];
        bones[i].matrix[1][3] = 0;
        bones[i].matrix[2][0] = newFrame->bones[i][2][0];
        bones[i].matrix[2][1] = newFrame->bones[i][2][1];
        bones[i].matrix[2][2] = newFrame->bones[i][2][2];
        bones[i].matrix[2][3] = 0;
    }

    if (radius) {
        *radius = newFrame->radius;
    }

    if (mins && maxes) {
        for (i = 0; i < 3; i++) {
            (*mins)[i]  = newFrame->bounds[0][i];
            (*maxes)[i] = newFrame->bounds[1][i];
        }
    }

    Skel_Free(newFrame);
}

void TIKI_GetSkelAnimFrame2(
    dtiki_t *tiki, skelBoneCache_t *bones, int anim, int frame, float *radius, vec3_t *mins, vec3_t *maxes
)
{
    short                    *aliases;
    skelAnimDataGameHeader_t *animData;

    aliases = tiki->a->m_aliases;
    if (*aliases == -1) {
        SKEL_Warning(
            "TIKI_GetSkelAnimFrame: Bad anim in static model %s, couldn't generate pose properly.\n", tiki->name
        );
        return;
    }

    animData = SkeletorCacheGetData(aliases[anim]);

    TIKI_GetSkelAnimFrameInternal(tiki, bones, animData, frame, radius, mins, maxes);
}

void TIKI_GetSkelAnimFrame(dtiki_t *tiki, skelBoneCache_t *bones, float *radius, vec3_t *mins, vec3_t *maxes)
{
    TIKI_GetSkelAnimFrame2(tiki, bones, 0, 0, radius, mins, maxes);
}

void skeletor_c::GetFrame(skelAnimFrame_t *newFrame)
{
    int boneNum;
    int numBones;

    numBones = m_Tiki->m_boneList.NumChannels();

    for (boneNum = 0; boneNum < numBones; boneNum++) {
        newFrame->bones[boneNum] = GetBoneFrame(boneNum);
    }

    newFrame->bounds[0] = m_frameBounds[0];
    newFrame->bounds[1] = m_frameBounds[1];
    newFrame->radius    = m_frameRadius;
}

SkelMat4& skeletor_c::GetBoneFrame(int boneIndex) const
{
    return m_bone[boneIndex]->GetTransform(&m_frameList);
}

bool skeletor_c::IsBoneOnGround(int boneIndex, float threshold)
{
    return GetBoneFrame(boneIndex).val[3][2] < threshold;
}

float skeletor_c::GetRadius()
{
    return m_frameRadius;
}

float skeletor_c::GetCentroidRadius(float *centroid)
{
    centroid[0] = (m_frameBounds[0][0] + m_frameBounds[1][0]) * 0.5f;
    centroid[1] = (m_frameBounds[0][1] + m_frameBounds[1][1]) * 0.5f;
    centroid[2] = (m_frameBounds[0][2] + m_frameBounds[1][2]) * 0.5f;
    return m_frameRadius;
}

int skeletor_c::GetMorphWeightFrame(int index, float time, int *data)
{
    return GetMorphWeightFrame(data);
}

float DecodeFrameValue(skanChannelHdr *channelFrames, int desiredFrameNum)
{
    skanGameFrame *foundFrame;
    size_t         frameSize;
    int            i;

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

    foundFrame = channelFrames->ary_frames;

    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[0];
}

int skeletor_c::GetMorphWeightFrame(int *data)
{
    int    numTargets;
    int    animChannelNum;
    int    blendNum;
    float  weight;
    int    modelChannelNum;
    float  channelData;

    numTargets = m_morphTargetList.NumChannels();

    if (!numTargets) {
        return 0;
    }

    memset(data, 0, sizeof(*data) * numTargets);

    for (blendNum = 0; blendNum < m_frameList.numMovementFrames; blendNum++) {
        const skanBlendInfo& blendInfo = m_frameList.m_blendInfo[blendNum];
        weight                         = blendInfo.weight;

        if (weight > 0.001) {
            for (modelChannelNum = 0; modelChannelNum < numTargets; modelChannelNum++) {
                animChannelNum = m_morphTargetList.GlobalChannel(modelChannelNum);
                animChannelNum = blendInfo.pAnimationData->channelList.LocalChannel(animChannelNum);

                if (animChannelNum >= 0) {
                    channelData = DecodeFrameValue(&blendInfo.pAnimationData->ary_channels[animChannelNum], blendInfo.frame);
                    data[modelChannelNum] += (int)(channelData * weight);
                }
            }
        }
    }

    for (blendNum = 0; blendNum < m_frameList.numActionFrames; blendNum++) {
        const skanBlendInfo& blendInfo = m_frameList.m_blendInfo[blendNum + MAX_SKEL_BLEND_MOVEMENT_FRAMES];
        weight                         = blendInfo.weight;

        if (weight > 0.001) {
            for (modelChannelNum = 0; modelChannelNum < numTargets; modelChannelNum++) {
                animChannelNum = m_morphTargetList.GlobalChannel(modelChannelNum);
                animChannelNum = blendInfo.pAnimationData->channelList.LocalChannel(animChannelNum);

                if (animChannelNum >= 0) {
                    channelData = DecodeFrameValue(&blendInfo.pAnimationData->ary_channels[animChannelNum], blendInfo.frame);
                    data[modelChannelNum] += (int)(channelData * weight);
                }
            }
        }
    }

    if (m_headBoneIndex >= 0 && !VectorCompareEpsilon(m_eyeTargetPos, vec3_origin, EPSILON)) {
        SkelVec3        lookPos    = m_eyeTargetPos;
        const SkelMat4& headOrient = GetBoneFrame(m_headBoneIndex);
        SkelMat4        invHeadOrient;
        invHeadOrient.TransposeRotOf(headOrient);

        SkelVec3 temp = lookPos;
        lookPos[0]    = temp[0] * invHeadOrient[0][0] * temp[1] * invHeadOrient[1][0] + temp[2] * invHeadOrient[2][0];
        lookPos[1]    = temp[0] * invHeadOrient[0][1] * temp[1] * invHeadOrient[1][1] + temp[2] * invHeadOrient[2][1];
        lookPos[2]    = temp[0] * invHeadOrient[0][2] * temp[1] * invHeadOrient[1][2] + temp[2] * invHeadOrient[2][2];

        float lookLeftAmount = lookPos[2] * 100 + data[m_targetLookLeft] - data[m_targetLookRight];
        float lookUpAmount   = lookPos[0] * 100 + data[m_targetLookUp] - data[m_targetLookDown];

        const float s = VectorLengthSquared(lookPos);

        if (s == 0.0) {
            lookPos[0] = 1.0;
        } else if (s != 1.0) {
            float l = 1.0 / sqrt(s);
            VectorScale(lookPos, l, lookPos);
        }

        if (m_targetLookLeft >= 0 && m_targetLookRight >= 0 && m_targetLookUp >= 0 && m_targetLookDown >= 0) {
            if (lookLeftAmount > 0.0) {
                if (lookLeftAmount > 100.0) {
                    lookLeftAmount = 100.0;
                }

                data[m_targetLookLeft]  = lookLeftAmount;
                data[m_targetLookRight] = 0;
            } else {
                if (lookLeftAmount < -100.0) {
                    lookLeftAmount = -100.0;
                }

                data[m_targetLookLeft]  = 0;
                data[m_targetLookRight] = -lookLeftAmount;
            }

            if (m_targetLookUp > 0.0) {
                if (lookUpAmount > 100.0) {
                    lookUpAmount = 100.0;
                }

                data[m_targetLookUp]   = lookUpAmount;
                data[m_targetLookDown] = 0;
            } else {
                if (lookUpAmount < -133.0) {
                    lookUpAmount = -133.0;
                }

                data[m_targetLookUp]   = 0.0;
                data[m_targetLookDown] = (-lookUpAmount * 0.75);
            }
        }
    }

    // check for blink
    if (m_targetBlink >= 0) {
        int sysMilliseconds;
        int blinkAmount;

        sysMilliseconds = Sys_Milliseconds();

        if (sysMilliseconds > m_timeNextBlink) {
            if (sysMilliseconds <= m_timeNextBlink + 250) {
                blinkAmount = sysMilliseconds - m_timeNextBlink;

                if (blinkAmount > 100) {
                    blinkAmount = 250 - blinkAmount;

                    if (blinkAmount > 100) {
                        blinkAmount = 100;
                    }
                }

                if (data[m_targetBlink] < blinkAmount) {
                    data[m_targetBlink] = blinkAmount;
                }
            } else {
                m_timeNextBlink = rand() / 5 + sysMilliseconds - 1000;
            }
        }
    }

    return numTargets;
}

void skeletor_c::SetEyeTargetPos(const float *pEyeTargetPos)
{
    VectorCopy(pEyeTargetPos, m_eyeTargetPos);
}

int skeletor_c::GetBoneParent(int boneIndex)
{
    int            iBoneNum;
    skelBone_Base *pBoneParent = m_bone[boneIndex]->Parent();

    for (iBoneNum = 0; iBoneNum < m_Tiki->m_boneList.NumChannels(); iBoneNum++) {
        if (m_bone[iBoneNum] == pBoneParent) {
            return iBoneNum;
        }
    }

    return -1;
    //return m_bone[ boneIndex ] - m_bone[ boneIndex ]->Parent();
}

const char *dtiki_s::GetBoneNameFromNum(int num) const
{
    return m_boneList.ChannelName(&skeletor_c::m_boneNames, num);
}

int dtiki_s::GetBoneNumFromName(const char *name)
{
    int iGlobalChannel;

    iGlobalChannel = skeletor_c::m_boneNames.FindNameLookup(name);

    if (iGlobalChannel < 0) {
        return -1;
    }

    return m_boneList.LocalChannel(iGlobalChannel);
}
