/*
===========================================================================
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_loadanimation.cpp : Responsible of loading animations

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

qboolean Compress(skelAnimGameFrame_t *current, skelAnimGameFrame_t *last, int channelIndex, int numChannels)
{
    float tolerance;
    float difference;
    int   i;

    // high-end PC don't need to compress...
    return false;

    for (i = 0; i < numChannels; i++) {
        tolerance = current->pChannels[channelIndex][i];
        if (tolerance > -0.000001 && tolerance < 0.000001) {
            current->pChannels[channelIndex][i] = 0.0f;
        }
    }

    if (last) {
        for (i = 0; i < numChannels; i++) {
            difference = last->pChannels[channelIndex][i] - current->pChannels[channelIndex][i];
            if (difference < -0.001f || difference >= 0.001f) {
                return false;
            }
        }
    }

    return true;
}

skelAnimDataGameHeader_t *EncodeFrames(
    skelAnimDataGameHeader_t *enAnim,
    skelAnimGameFrame_t      *m_frame,
    qboolean                  bLog,
    skelChannelList_c        *channelList,
    ChannelNameTable         *channelNames
)
{
    int                  endFrameCap;
    skanChannelHdr      *pChannel;
    int                  i, j;
    int                  frameCnt;
    skelAnimGameFrame_t *pCurrFrame;
    skelAnimGameFrame_t *pLastFrame;
    skanGameFrame       *pFrame;
    size_t               frameSize;
    int                  indexLastFrameAdded;
    channelType_t        channelType;

    pChannel    = enAnim->ary_channels;
    endFrameCap = enAnim->numFrames - 2;

    for (i = 0; i < enAnim->nTotalChannels; i++, pChannel++) {
        pLastFrame = NULL;
        pCurrFrame = m_frame;

        channelType = GetBoneChannelType(enAnim->channelList.ChannelName(channelNames, i));
        switch (channelType) {
        case CHANNEL_ROTATION:
            frameSize = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(vec4_t));
            frameCnt  = 0;
            for (j = 0; j < enAnim->numFrames; j++) {
                if (!Compress(pCurrFrame, pLastFrame, i, 4) || j >= endFrameCap) {
                    frameCnt++;
                    pLastFrame = pCurrFrame;
                }

                pCurrFrame++;
            }

            pChannel->ary_frames       = (skanGameFrame *)Skel_Alloc(frameSize * frameCnt);
            pChannel->nFramesInChannel = frameCnt;
            enAnim->nBytesUsed += frameSize * frameCnt;

            pLastFrame          = NULL;
            indexLastFrameAdded = 0;

            pCurrFrame = m_frame;
            pFrame     = pChannel->ary_frames;

            for (j = 0; j < enAnim->numFrames; j++) {
                if (!Compress(pCurrFrame, pLastFrame, i, 4) || j >= endFrameCap) {
                    pFrame                  = (skanGameFrame *)((byte *)pChannel->ary_frames + j * frameSize);
                    pFrame->nFrameNum       = j;
                    pFrame->nPrevFrameIndex = indexLastFrameAdded;

                    if (j > 0) {
                        indexLastFrameAdded++;
                    }

                    pFrame->pChannelData[0] = pCurrFrame->pChannels[i][0];
                    pFrame->pChannelData[1] = pCurrFrame->pChannels[i][1];
                    pFrame->pChannelData[2] = pCurrFrame->pChannels[i][2];
                    pFrame->pChannelData[3] = pCurrFrame->pChannels[i][3];

                    pLastFrame = pCurrFrame;
                }

                pCurrFrame++;
            }
            break;
        case CHANNEL_POSITION:
            frameSize = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(vec3_t));
            frameCnt  = 0;
            for (j = 0; j < enAnim->numFrames; j++) {
                if (!Compress(pCurrFrame, pLastFrame, i, 3) || j >= endFrameCap) {
                    frameCnt++;
                    pLastFrame = pCurrFrame;
                }

                pCurrFrame++;
            }

            pChannel->ary_frames       = (skanGameFrame *)Skel_Alloc(frameSize * frameCnt);
            pChannel->nFramesInChannel = frameCnt;
            enAnim->nBytesUsed += frameSize * frameCnt;

            pLastFrame          = NULL;
            indexLastFrameAdded = 0;

            pCurrFrame = m_frame;
            pFrame     = pChannel->ary_frames;

            for (j = 0; j < enAnim->numFrames; j++) {
                if (!Compress(pCurrFrame, pLastFrame, i, 1) || j >= endFrameCap) {
                    pFrame                  = (skanGameFrame *)((byte *)pChannel->ary_frames + j * frameSize);
                    pFrame->nFrameNum       = j;
                    pFrame->nPrevFrameIndex = indexLastFrameAdded;

                    if (j > 0) {
                        indexLastFrameAdded++;
                    }

                    pFrame->pChannelData[0] = pCurrFrame->pChannels[i][0];
                    pFrame->pChannelData[1] = pCurrFrame->pChannels[i][1];
                    pFrame->pChannelData[2] = pCurrFrame->pChannels[i][2];

                    pLastFrame = pCurrFrame;
                }

                pCurrFrame++;
            }
            break;
        case CHANNEL_NONE:
            pChannel->ary_frames       = NULL;
            pChannel->nFramesInChannel = 0;
            break;
        case CHANNEL_VALUE:
        default:
            frameSize = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(float));
            frameCnt  = 0;
            for (j = 0; j < enAnim->numFrames; j++) {
                if (!Compress(pCurrFrame, pLastFrame, i, 1) || j >= endFrameCap) {
                    frameCnt++;
                    pLastFrame = pCurrFrame;
                }

                pCurrFrame++;
            }

            pChannel->ary_frames       = (skanGameFrame *)Skel_Alloc(frameSize * frameCnt);
            pChannel->nFramesInChannel = frameCnt;
            enAnim->nBytesUsed += frameSize * frameCnt;

            pLastFrame          = NULL;
            indexLastFrameAdded = 0;

            pCurrFrame = m_frame;
            pFrame     = pChannel->ary_frames;

            for (j = 0; j < enAnim->numFrames; j++) {
                if (!Compress(pCurrFrame, pLastFrame, i, 3) || j >= endFrameCap) {
                    pFrame                  = (skanGameFrame *)((byte *)pChannel->ary_frames + j * frameSize);
                    pFrame->nFrameNum       = j;
                    pFrame->nPrevFrameIndex = indexLastFrameAdded;

                    if (j > 0) {
                        indexLastFrameAdded++;
                    }

                    pFrame->pChannelData[0] = pCurrFrame->pChannels[i][0];

                    pLastFrame = pCurrFrame;
                }

                pCurrFrame++;
            }
            break;
        }
    }

    return enAnim;
}

skelAnimDataGameHeader_t *
skeletor_c::ConvertSkelFileToGame(skelAnimDataFileHeader_t *pHeader, int iBuffLength, const char *path)
{
    int                       i, j;
    skelAnimFileFrame_t      *pFileFrame;
    skelAnimGameFrame_t      *pGameFrame;
    skelChannelName_t        *pChannelNames;
    skelAnimDataGameHeader_t *enAnim;
    skelAnimGameFrame_t      *oldFrame;
    skelAnimGameFrame_t      *newFrame;
    int                       channelIndex;

    static_assert(sizeof(skelAnimDataFileHeader_t) == 96, "Skeletor anim header size doesn't match!");
    static_assert(sizeof(skelAnimFileFrame_t) == 48, "Skeletor anim file frame size doesn't match!");

    if (pHeader->numFrames <= 0) {
        return NULL;
    }

    pGameFrame = new skelAnimGameFrame_t[pHeader->numFrames];
    pFileFrame = pHeader->frame;
    newFrame   = pGameFrame;

    for (i = 0; i < pHeader->numFrames; i++) {
        vec4_t* pChannels;

        newFrame->bounds[0][0] = LittleFloat(pFileFrame->bounds[0][0]);
        newFrame->bounds[0][1] = LittleFloat(pFileFrame->bounds[0][1]);
        newFrame->bounds[0][2] = LittleFloat(pFileFrame->bounds[0][2]);
        newFrame->bounds[1][0] = LittleFloat(pFileFrame->bounds[1][0]);
        newFrame->bounds[1][1] = LittleFloat(pFileFrame->bounds[1][1]);
        newFrame->bounds[1][2] = LittleFloat(pFileFrame->bounds[1][2]);
        newFrame->radius = LittleFloat(pFileFrame->radius);
        newFrame->delta[0] = LittleFloat(pFileFrame->delta[0]);
        newFrame->delta[1] = LittleFloat(pFileFrame->delta[1]);
        newFrame->delta[2] = LittleFloat(pFileFrame->delta[2]);
        newFrame->angleDelta = LittleFloat(pFileFrame->angleDelta);
        //
        // Load channels
        //
        newFrame->pChannels  = new vec4_t[pHeader->numChannels];
        pChannels = (vec4_t*)((byte*)pHeader + (sizeof(skelAnimDataFileHeader_t) + sizeof(skelAnimFileFrame_t) * (pHeader->numFrames - 1) + sizeof(vec4_t) * pHeader->numChannels * i));
        for (j = 0; j < pHeader->numChannels; j++) {
            newFrame->pChannels[j][0] = LittleFloat(pChannels[j][0]);
            newFrame->pChannels[j][1] = LittleFloat(pChannels[j][1]);
            newFrame->pChannels[j][2] = LittleFloat(pChannels[j][2]);
            newFrame->pChannels[j][3] = LittleFloat(pChannels[j][3]);
        }

        AddToBounds(newFrame->bounds, pFileFrame->bounds);

        pFileFrame++;
        newFrame++;
    }

    enAnim                  = skelAnimDataGameHeader_t::AllocRLEChannelData(pHeader->numChannels);
    enAnim->flags           = pHeader->flags;
    enAnim->frameTime       = pHeader->frameTime;
    enAnim->totalDelta      = pHeader->totalDelta;
    enAnim->totalAngleDelta = pHeader->totalAngleDelta;
    enAnim->numFrames       = pHeader->numFrames;
    enAnim->nTotalChannels  = pHeader->numChannels;
    ClearBounds(enAnim->bounds[0], enAnim->bounds[1]);
    enAnim->m_frame = (skelAnimGameFrame_t *)Skel_Alloc(pHeader->numFrames * sizeof(skelAnimGameFrame_t));

    oldFrame = pGameFrame;
    newFrame = enAnim->m_frame;

    for (i = 0; i < pHeader->numFrames; i++) {
        newFrame->bounds[0]  = oldFrame->bounds[0];
        newFrame->bounds[1]  = oldFrame->bounds[1];
        newFrame->radius     = oldFrame->radius;
        newFrame->delta      = oldFrame->delta;
        newFrame->angleDelta = oldFrame->angleDelta;
        newFrame->pChannels  = NULL;
        AddToBounds(enAnim->bounds, oldFrame->bounds);

        oldFrame++;
        newFrame++;
    }

    enAnim->channelList.ZeroChannels();

    pChannelNames = (skelChannelName_t *)((char *)pHeader + pHeader->ofsChannelNames);

    for (i = 0; i < pHeader->numChannels; i++) {
        channelIndex = m_channelNames.RegisterChannel(*pChannelNames);
        enAnim->channelList.AddChannel(channelIndex);
        pChannelNames++;
    }

    enAnim->channelList.PackChannels();
    EncodeFrames(enAnim, pGameFrame, qfalse, &enAnim->channelList, &m_channelNames);

    if (enAnim->channelList.HasChannel(&m_channelNames, "Bip01 pos")
        && enAnim->channelList.HasChannel(&m_channelNames, "Bip01 R Foot pos")
        && enAnim->channelList.HasChannel(&m_channelNames, "Bip01 L Foot pos")) {
        enAnim->bHasDelta = true;
    } else {
        enAnim->bHasDelta = false;
    }

    if (enAnim->channelList.HasChannel(&m_channelNames, "Bip01 Spine rot")
        && enAnim->channelList.HasChannel(&m_channelNames, "Bip01 Spine1 rot")) {
        enAnim->bHasUpper = true;
    } else {
        enAnim->bHasUpper = false;
    }

    if (enAnim->channelList.HasChannel(&m_channelNames, "VISEME_Bump")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_Cage_")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_Earth")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_Fave")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_If")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_New")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_Ox")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_Roar")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_Size")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_Though")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_Told")
        || enAnim->channelList.HasChannel(&m_channelNames, "VISME_Wet")
        || enAnim->channelList.HasChannel(&m_channelNames, "BROW_frown")
        || enAnim->channelList.HasChannel(&m_channelNames, "BROW_R_lift")
        || enAnim->channelList.HasChannel(&m_channelNames, "BROW_lift")
        || enAnim->channelList.HasChannel(&m_channelNames, "BROW_worry")
        || enAnim->channelList.HasChannel(&m_channelNames, "EYE_blink")
        || enAnim->channelList.HasChannel(&m_channelNames, "EYES_Excited__")
        || enAnim->channelList.HasChannel(&m_channelNames, "EYES_L_squint")
        || enAnim->channelList.HasChannel(&m_channelNames, "EYES_narrow__")
        || enAnim->channelList.HasChannel(&m_channelNames, "EYES_down")
        || enAnim->channelList.HasChannel(&m_channelNames, "EYES_left")
        || enAnim->channelList.HasChannel(&m_channelNames, "EYES_right")
        || enAnim->channelList.HasChannel(&m_channelNames, "EYES_smile")
        || enAnim->channelList.HasChannel(&m_channelNames, "EYES_up__")
        || enAnim->channelList.HasChannel(&m_channelNames, "JAW_open-closed")
        || enAnim->channelList.HasChannel(&m_channelNames, "JAW_open-open")
        || enAnim->channelList.HasChannel(&m_channelNames, "LIPS_compressed")
        || enAnim->channelList.HasChannel(&m_channelNames, "MOUTH_L_smile_closed")
        || enAnim->channelList.HasChannel(&m_channelNames, "MOUTH_L_smile_open")
        || enAnim->channelList.HasChannel(&m_channelNames, "MOUTH_L_snarl_closed_")
        || enAnim->channelList.HasChannel(&m_channelNames, "MOUTH_L_snarl_open")
        || enAnim->channelList.HasChannel(&m_channelNames, "MOUTH_grimace")
        || enAnim->channelList.HasChannel(&m_channelNames, "MOUTH_smile_closed")
        || enAnim->channelList.HasChannel(&m_channelNames, "MOUTH_smile_open")
        || enAnim->channelList.HasChannel(&m_channelNames, "MOUTH_Snarl_closed")
        || enAnim->channelList.HasChannel(&m_channelNames, "MOUTH_Snarl_open")) {
        enAnim->bHasMorph = true;
    } else {
        enAnim->bHasMorph = false;
    }

    for (i = 0; i < pHeader->numFrames; i++) {
        if (pGameFrame[i].pChannels) {
            delete[] pGameFrame[i].pChannels;
        }
    }

    delete[] pGameFrame;
    return enAnim;
}

void WriteEncodedFrames(msg_t *msg, skelAnimDataGameHeader_t *enAnim)
{
    skanChannelHdr *pChannel;
    skanGameFrame  *pFrame;
    int             i, j;

    MSG_WriteLong(msg, enAnim->numFrames);
    MSG_WriteShort(msg, enAnim->nTotalChannels);

    for (i = 0; i < enAnim->nTotalChannels; i++) {
        pChannel = &enAnim->ary_channels[i];
        MSG_WriteShort(msg, pChannel->nFramesInChannel);

        for (j = 0; j < pChannel->nFramesInChannel; j++) {
            vec4_t channelData;

            pFrame = &pChannel->ary_frames[i];
            MSG_WriteShort(msg, pFrame->nFrameNum);
            MSG_WriteShort(msg, pFrame->nPrevFrameIndex);

            channelData[0] = LittleFloat(pFrame->pChannelData[0]);
            channelData[1] = LittleFloat(pFrame->pChannelData[1]);
            channelData[2] = LittleFloat(pFrame->pChannelData[2]);
            channelData[3] = LittleFloat(pFrame->pChannelData[3]);
            MSG_WriteData(msg, channelData, sizeof(vec4_t));
        }
    }

    MSG_WriteLong(msg, enAnim->nBytesUsed);
}

void skeletor_c::SaveProcessedAnim(
    skelAnimDataGameHeader_t *enAnim, const char *path, skelAnimDataFileHeader_t *pHeader
)
{
    /*
	int i;
	skelChannelName_t *pChannelNames;
	msg_t msg;
	skelAnimGameFrame_t *newFrame;
	char npath[ 128 ];
	unsigned char buf[ 2000000 ];

	// FIXME:
	// Write animation
*/
}

void ReadEncodedFrames(msg_t *msg, skelAnimDataGameHeader_t *enAnim)
{
    skanChannelHdr *pChannel;
    skanGameFrame  *pFrame;
    size_t          frameSize;
    int             frameCnt;
    int             i, j;
    channelType_t   channelType;
    vec4_t          channelData;

    for (i = 0; i < enAnim->nTotalChannels; i++) {
        pChannel = &enAnim->ary_channels[i];

        channelType = GetBoneChannelType(enAnim->channelList.ChannelName(&skeletor_c::m_channelNames, i));
        frameCnt    = MSG_ReadShort(msg);

        switch (channelType) {
        case CHANNEL_ROTATION:
            frameSize = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(vec4_t));

            pFrame                     = (skanGameFrame *)Skel_Alloc(frameSize * frameCnt);
            pChannel->ary_frames       = pFrame;
            pChannel->nFramesInChannel = frameCnt;

            for (j = 0; j < frameCnt; j++) {
                pFrame                  = (skanGameFrame *)((byte *)pChannel->ary_frames + j * frameSize);
                pFrame->nFrameNum       = MSG_ReadShort(msg);
                pFrame->nPrevFrameIndex = MSG_ReadShort(msg);
                MSG_ReadData(msg, channelData, sizeof(vec4_t));

                pFrame->pChannelData[0] = LittleFloat(channelData[0]);
                pFrame->pChannelData[1] = LittleFloat(channelData[1]);
                pFrame->pChannelData[2] = LittleFloat(channelData[2]);
                pFrame->pChannelData[3] = LittleFloat(channelData[3]);
            }
            break;
        case CHANNEL_POSITION:
            frameSize = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(vec3_t));

            pFrame                     = (skanGameFrame *)Skel_Alloc(frameSize * frameCnt);
            pChannel->ary_frames       = pFrame;
            pChannel->nFramesInChannel = frameCnt;

            for (j = 0; j < frameCnt; j++) {
                pFrame                  = (skanGameFrame *)((byte *)pChannel->ary_frames + j * frameSize);
                pFrame->nFrameNum       = MSG_ReadShort(msg);
                pFrame->nPrevFrameIndex = MSG_ReadShort(msg);
                MSG_ReadData(msg, channelData, sizeof(vec4_t));

                pFrame->pChannelData[0] = LittleFloat(channelData[0]);
                pFrame->pChannelData[1] = LittleFloat(channelData[1]);
                pFrame->pChannelData[2] = LittleFloat(channelData[2]);
            }
            break;
        case CHANNEL_NONE:
            pChannel->ary_frames       = NULL;
            pChannel->nFramesInChannel = 0;

            for (j = 0; j < frameCnt; j++) {
                MSG_ReadShort(msg);
                MSG_ReadShort(msg);
                MSG_ReadData(msg, channelData, sizeof(vec4_t));
            }
            break;
        case CHANNEL_VALUE:
        default:
            frameSize = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(float));

            pFrame                     = (skanGameFrame *)Skel_Alloc(frameSize * frameCnt);
            pChannel->ary_frames       = pFrame;
            pChannel->nFramesInChannel = frameCnt;

            for (j = 0; j < frameCnt; j++) {
                pFrame                  = (skanGameFrame *)((byte *)pChannel->ary_frames + j * frameSize);
                pFrame->nFrameNum       = MSG_ReadShort(msg);
                pFrame->nPrevFrameIndex = MSG_ReadShort(msg);
                MSG_ReadData(msg, channelData, sizeof(vec4_t));

                pFrame->pChannelData[0] = LittleFloat(channelData[0]);
            }
            break;
        }
    }

    enAnim->nBytesUsed = MSG_ReadLong(msg);
}

void ReadEncodedFramesEx(msg_t *msg, skelAnimDataGameHeader_t *enAnim)
{
    skanChannelHdr *pChannel;
    skanGameFrame  *pFrame;
    size_t          frameSize;
    int             frameCnt;
    int             i, j;
    const char     *name;
    channelType_t   type;

    for (i = 0; i < enAnim->nTotalChannels; i++) {
        pChannel = &enAnim->ary_channels[i];

        name     = enAnim->channelList.ChannelName(&skeletor_c::m_channelNames, i);
        type     = GetBoneChannelType(name);
        frameCnt = MSG_ReadShort(msg);

        switch (type) {
        //
        // 4 channels (Quat)
        //
        case CHANNEL_ROTATION:
            frameSize                  = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(vec4_t));
            pFrame                     = (skanGameFrame *)Skel_Alloc(frameSize * frameCnt);
            pChannel->ary_frames       = pFrame;
            pChannel->nFramesInChannel = frameCnt;

            for (j = 0; j < pChannel->nFramesInChannel; j++) {
                pFrame                  = (skanGameFrame *)((byte *)pChannel->ary_frames + j * frameSize);
                pFrame->nFrameNum       = MSG_ReadShort(msg);
                pFrame->nPrevFrameIndex = MSG_ReadShort(msg);
                pFrame->pChannelData[0] = MSG_ReadFloat(msg);
                pFrame->pChannelData[1] = MSG_ReadFloat(msg);
                pFrame->pChannelData[2] = MSG_ReadFloat(msg);
                pFrame->pChannelData[3] = MSG_ReadFloat(msg);
            }
            break;
        //
        // 3 channels (Position)
        //
        case CHANNEL_POSITION:
            frameSize                  = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(vec3_t));
            pFrame                     = (skanGameFrame *)Skel_Alloc(frameSize * frameCnt);
            pChannel->ary_frames       = pFrame;
            pChannel->nFramesInChannel = frameCnt;

            for (j = 0; j < pChannel->nFramesInChannel; j++) {
                pFrame                  = (skanGameFrame *)((byte *)pChannel->ary_frames + j * frameSize);
                pFrame->nFrameNum       = MSG_ReadShort(msg);
                pFrame->nPrevFrameIndex = MSG_ReadShort(msg);
                pFrame->pChannelData[0] = MSG_ReadFloat(msg);
                pFrame->pChannelData[1] = MSG_ReadFloat(msg);
                pFrame->pChannelData[2] = MSG_ReadFloat(msg);
            }
            break;
        //
        // 1 channel (frame)
        //
        case CHANNEL_VALUE:
            frameSize            = (sizeof(skanGameFrame) - sizeof(skanGameFrame::pChannelData) + sizeof(float) * 1);
            pFrame               = (skanGameFrame *)Skel_Alloc(frameSize * frameCnt);
            pChannel->ary_frames = pFrame;
            pChannel->nFramesInChannel = frameCnt;

            for (j = 0; j < pChannel->nFramesInChannel; j++) {
                pFrame                  = (skanGameFrame *)((byte *)pChannel->ary_frames + j * frameSize);
                pFrame->nFrameNum       = MSG_ReadShort(msg);
                pFrame->nPrevFrameIndex = MSG_ReadShort(msg);
                pFrame->pChannelData[0] = MSG_ReadFloat(msg);
            }
            break;
        case CHANNEL_NONE:
            // Nothing to do
            break;
        }
    }
}

skelAnimDataGameHeader_t *skeletor_c::LoadProcessedAnim(const char *path, void *buffer, int len, const char *name)
{
    skelAnimDataGameHeader_t *enAnim;
    int                       i, j;
    msg_t                     msg;
    msg_t                     msgForAnim;
    int                       numChannels;
    skelAnimGameFrame_t      *newFrame;

    MSG_Init(&msg, (byte *)buffer, len);
    msg.cursize = len;
    MSG_BeginReading(&msg);

    numChannels             = MSG_ReadLong(&msg);
    enAnim                  = skelAnimDataGameHeader_t::AllocRLEChannelData(numChannels);
    enAnim->flags           = MSG_ReadLong(&msg);
    enAnim->frameTime       = MSG_ReadFloat(&msg);
    enAnim->totalDelta[0]   = MSG_ReadFloat(&msg);
    enAnim->totalDelta[1]   = MSG_ReadFloat(&msg);
    enAnim->totalDelta[2]   = MSG_ReadFloat(&msg);
    enAnim->totalAngleDelta = MSG_ReadFloat(&msg);
    enAnim->numFrames       = MSG_ReadLong(&msg);
    enAnim->nTotalChannels  = MSG_ReadShort(&msg);
    enAnim->bHasDelta       = MSG_ReadByte(&msg) != 0;
    enAnim->bHasUpper       = MSG_ReadByte(&msg) != 0;
    enAnim->bHasMorph       = MSG_ReadByte(&msg) != 0;

    newFrame        = (skelAnimGameFrame_t *)Skel_Alloc(enAnim->numFrames * sizeof(skelAnimGameFrame_t));
    enAnim->m_frame = newFrame;

    for (i = 0; i < enAnim->numFrames; i++) {
        newFrame->bounds[0][0] = MSG_ReadFloat(&msg);
        newFrame->bounds[0][1] = MSG_ReadFloat(&msg);
        newFrame->bounds[0][2] = MSG_ReadFloat(&msg);
        newFrame->bounds[1][0] = MSG_ReadFloat(&msg);
        newFrame->bounds[1][1] = MSG_ReadFloat(&msg);
        newFrame->bounds[1][2] = MSG_ReadFloat(&msg);
        newFrame->radius       = MSG_ReadFloat(&msg);
        newFrame->delta[0]     = MSG_ReadFloat(&msg);
        newFrame->delta[1]     = MSG_ReadFloat(&msg);
        newFrame->delta[2]     = MSG_ReadFloat(&msg);
        newFrame->angleDelta   = MSG_ReadFloat(&msg);
        newFrame->pChannels    = NULL;
        newFrame++;
    }

    enAnim->bounds[0][0] = MSG_ReadFloat(&msg);
    enAnim->bounds[0][1] = MSG_ReadFloat(&msg);
    enAnim->bounds[0][2] = MSG_ReadFloat(&msg);
    enAnim->bounds[1][0] = MSG_ReadFloat(&msg);
    enAnim->bounds[1][1] = MSG_ReadFloat(&msg);
    enAnim->bounds[1][2] = MSG_ReadFloat(&msg);

    enAnim->numFrames      = MSG_ReadLong(&msg);
    enAnim->nTotalChannels = MSG_ReadShort(&msg);

    msgForAnim = msg;

    for (i = 0; i < enAnim->nTotalChannels; i++) {
        int    frameCnt = MSG_ReadShort(&msgForAnim);
        vec4_t channelData;

        for (j = 0; j < frameCnt; j++) {
            MSG_ReadShort(&msgForAnim);
            MSG_ReadShort(&msgForAnim);
            MSG_ReadData(&msgForAnim, channelData, sizeof(vec4_t));
        }
    }

    enAnim->nBytesUsed = MSG_ReadLong(&msgForAnim);

    numChannels = MSG_ReadLong(&msgForAnim);
    enAnim->channelList.ZeroChannels();

    for (i = 0; i < numChannels; i++) {
        const char *name = MSG_ReadString(&msgForAnim);

        if (enAnim->channelList.AddChannel(m_channelNames.RegisterChannel(name)) != i) {
            Com_Printf("^~^~^ Animation '%s' has duplicate channel '%s'\n", path, name);
        }
    }

    ReadEncodedFrames(&msg, enAnim);
    return enAnim;
}

skelAnimDataGameHeader_t *skeletor_c::LoadProcessedAnimEx(const char *path, void *buffer, int len, const char *name)
{
    skelAnimDataGameHeader_t *enAnim;
    int                       i;
    msg_t                     msg;
    int                       numChannels;
    skelAnimGameFrame_t      *newFrame;

    MSG_Init(&msg, (byte *)buffer, len);
    msg.cursize = len;
    MSG_BeginReading(&msg);

    numChannels = MSG_ReadShort(&msg);
    enAnim      = skelAnimDataGameHeader_t::AllocRLEChannelData(numChannels);
    enAnim->channelList.ZeroChannels();
    enAnim->flags           = MSG_ReadLong(&msg);
    enAnim->frameTime       = MSG_ReadFloat(&msg);
    enAnim->totalDelta[0]   = MSG_ReadFloat(&msg);
    enAnim->totalDelta[1]   = MSG_ReadFloat(&msg);
    enAnim->totalDelta[2]   = MSG_ReadFloat(&msg);
    enAnim->totalAngleDelta = MSG_ReadFloat(&msg);
    enAnim->numFrames       = MSG_ReadLong(&msg);
    enAnim->bHasDelta       = MSG_ReadByte(&msg) != 0;
    enAnim->bHasUpper       = MSG_ReadByte(&msg) != 0;
    enAnim->bHasMorph       = MSG_ReadByte(&msg) != 0;

    for (i = 0; i < enAnim->nTotalChannels; i++) {
        enAnim->channelList.AddChannel(m_channelNames.RegisterChannel(MSG_ReadString(&msg)));
    }

    newFrame        = (skelAnimGameFrame_t *)Skel_Alloc(enAnim->numFrames * sizeof(skelAnimGameFrame_t));
    enAnim->m_frame = newFrame;

    for (i = 0; i < enAnim->numFrames; i++) {
        newFrame->bounds[0][0] = MSG_ReadFloat(&msg);
        newFrame->bounds[0][1] = MSG_ReadFloat(&msg);
        newFrame->bounds[0][2] = MSG_ReadFloat(&msg);
        newFrame->bounds[1][0] = MSG_ReadFloat(&msg);
        newFrame->bounds[1][1] = MSG_ReadFloat(&msg);
        newFrame->bounds[1][2] = MSG_ReadFloat(&msg);
        newFrame->radius       = MSG_ReadFloat(&msg);
        newFrame->delta[0]     = MSG_ReadFloat(&msg);
        newFrame->delta[1]     = MSG_ReadFloat(&msg);
        newFrame->delta[2]     = MSG_ReadFloat(&msg);
        newFrame->angleDelta   = MSG_ReadFloat(&msg);
        newFrame->pChannels    = NULL;
        newFrame++;
    }

    enAnim->bounds[0][0] = MSG_ReadFloat(&msg);
    enAnim->bounds[0][1] = MSG_ReadFloat(&msg);
    enAnim->bounds[0][2] = MSG_ReadFloat(&msg);
    enAnim->bounds[1][0] = MSG_ReadFloat(&msg);
    enAnim->bounds[1][1] = MSG_ReadFloat(&msg);
    enAnim->bounds[1][2] = MSG_ReadFloat(&msg);
    ReadEncodedFramesEx(&msg, enAnim);

    return enAnim;
}
