/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program 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 3 of the License, or
(at your option) any later version.

This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "hsTypes.h"
#include "plMorphSequence.h"
#include "plMorphSequenceSDLMod.h"

#include "plAccessGeometry.h"
#include "plAccessVtxSpan.h"

#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plDrawInterface.h"
#include "pnSceneObject/plCoordinateInterface.h"

#include "plDrawableSpans.h"
#include "plInstanceDrawInterface.h"

#include "hsResMgr.h"
#include "plgDispatch.h"
#include "plMessage/plRenderMsg.h"

#include "plSharedMesh.h"

#include "plTweak.h"
#include "hsTimer.h"
#include "hsFastMath.h"

///////////////////////////////////////////////////////////////////////////

void plMorphDataSet::Read(hsStream* s, hsResMgr* mgr)
{
    hsKeyedObject::Read(s, mgr);
    
    int n = s->ReadSwap32();
    fMorphs.SetCount(n);
    int i;
    for( i = 0; i < n; i++ )
        fMorphs[i].Read(s, mgr);
}

void plMorphDataSet::Write(hsStream* s, hsResMgr* mgr)
{
    hsKeyedObject::Write(s, mgr);

    s->WriteSwap32(fMorphs.GetCount());
    int i;
    for( i = 0; i < fMorphs.GetCount(); i++ )
        fMorphs[i].Write(s, mgr);
}   

///////////////////////////////////////////////////////////////////////////

const UInt32 kChanMask = plAccessVtxSpan::kPositionMask
                        | plAccessVtxSpan::kNormalMask
                        | plAccessVtxSpan::kUVWMask;

// Probably want another type which is just initialize/override
// so we can set the mesh to whatever base set we want, as if
// that's what got loaded from disk.
// Use Access RW. That might already be handled in the customization stuff.

plConst(hsScalar)   kMorphTime(0.5);

class plMorphTarget
{
public:
    UInt16      fLayer;
    UInt16      fDelta;
    hsScalar    fWeight;
};

hsTArray<plMorphTarget> fTgtWgts;

plMorphSequence::plMorphSequence()
:   fMorphFlags(0),
    fMorphSDLMod(nil),
    fGlobalLayerRef(-1)
{
}

plMorphSequence::~plMorphSequence()
{
    DeInit();
}

hsBool plMorphSequence::MsgReceive(plMessage* msg)
{
    plRenderMsg* rend = plRenderMsg::ConvertNoRef(msg);
    if( rend )
    {
        // For now, I'm ignoring the target weight stuff for shared meshes.
        // Can always add it in later if desired.
        if( fTgtWgts.GetCount() )
        {
            hsScalar delWgt = hsTimer::GetDelSysSeconds() / (kMorphTime > 0 ? kMorphTime : 1.e-3f);
            int i;
            for( i = 0; i < fTgtWgts.GetCount(); i++ )
            {
                hsScalar currWgt = GetWeight(fTgtWgts[i].fLayer, fTgtWgts[i].fDelta);
                if( fTgtWgts[i].fWeight < currWgt )
                {
                    if( fTgtWgts[i].fWeight >= (currWgt -= delWgt) )
                        currWgt = fTgtWgts[i].fWeight;
                }
                else if( fTgtWgts[i].fWeight > currWgt )
                {
                    if( fTgtWgts[i].fWeight <= (currWgt += delWgt) )
                        currWgt = fTgtWgts[i].fWeight;
                }
                
                fMorphs[fTgtWgts[i].fLayer].SetWeight(fTgtWgts[i].fDelta, currWgt);

                if( fTgtWgts[i].fWeight == currWgt )
                {
                    fTgtWgts.Remove(i);
                    i--;
                }
            }
            ISetDirty(true);
        }

        if( !(fMorphFlags & kDirty) )
        {
            // We went a whole frame without getting dirty, 
            // we can stop refreshing now.
            plgDispatch::Dispatch()->UnRegisterForExactType(plRenderMsg::Index(), GetKey());

            return true;
        }
        ISetDirty(false);
        if( fMorphFlags & kDirtyIndices )
            IFindIndices();

        if( fMorphFlags & kHaveShared )
        {
            IApplyShared();
        }
        else
        {
            Apply();
        }
        return true;
    }

    plSharedMeshBCMsg *smMsg = plSharedMeshBCMsg::ConvertNoRef(msg);
    if (smMsg)
    {
        if (IGetDrawInterface()->GetKey() == smMsg->GetSender() || IIsUsingDrawable(smMsg->fDraw))
            fMorphFlags |= kDirtyIndices;
    }           

    plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg);
    if (refMsg)
    {
        plSharedMesh *mesh = plSharedMesh::ConvertNoRef(refMsg->GetRef());
        if (mesh)
        {
            if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest) )
            {
                AddSharedMesh(mesh);
            }
            else if( refMsg->GetContext() & plRefMsg::kOnReplace)
            {
                plSharedMesh *oldMesh = plSharedMesh::ConvertNoRef(refMsg->GetOldRef());
                if (oldMesh)
                    RemoveSharedMesh(oldMesh);
                AddSharedMesh(mesh);
            }
            else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
                RemoveSharedMesh(mesh);             
            
            return true;
        }
    }
    
    return plSingleModifier::MsgReceive(msg);
}

int plMorphSequence::GetNumLayers(plKey meshKey /* = nil */) const
{
    int index = IFindSharedMeshIndex(meshKey);
    if (index < 0)
        return fMorphs.GetCount();
    else
        return fSharedMeshes[index].fMesh->fMorphSet->fMorphs.GetCount();
}

int plMorphSequence::GetNumDeltas(int iLay, plKey meshKey /* = nil */) const 
{ 
    int index = IFindSharedMeshIndex(meshKey);
    if (index < 0)
        return fMorphs[iLay].GetNumDeltas(); 
    else
        return fSharedMeshes[index].fMesh->fMorphSet->fMorphs[iLay].GetNumDeltas();
}

hsScalar plMorphSequence::GetWeight(int iLay, int iDel, plKey meshKey /* = nil */) const 
{ 
    int index = IFindSharedMeshIndex(meshKey);
    if (index == -1)
        return fMorphs[iLay].GetWeight(iDel); 
    else
        return fSharedMeshes[index].fArrayWeights[iLay].fDeltaWeights[iDel];
}

void plMorphSequence::SetWeight(int iLay, int iDel, hsScalar w, plKey meshKey /* = nil */)
{
    int index = IFindSharedMeshIndex(meshKey);

    // Only dirty if the weight isn't for a pending mesh
    if(meshKey == nil || index >= 0)
        ISetDirty(true);

    if (meshKey == nil)
    {
        if( iLay < fMorphs.GetCount() )
        {
            if( kMorphTime > 0 )
            {
                plMorphTarget tgt;
                tgt.fLayer = iLay;
                tgt.fDelta = iDel;
                tgt.fWeight = w;

                fTgtWgts.Append(tgt);
            }
            else
            {
                fMorphs[iLay].SetWeight(iDel, w);
            }
        }
    }
    else if (index >= 0)
    {
        fSharedMeshes[index].fArrayWeights[iLay].fDeltaWeights[iDel] = w;
        fSharedMeshes[index].fFlags |= plSharedMeshInfo::kInfoDirtyMesh;
        if (index == fGlobalLayerRef)
            ISetAllSharedToGlobal();
    }
    else
    {
        // Setting weight for a mesh we haven't added yet (loading state)
        index = IFindPendingStateIndex(meshKey);
        if (index < 0)
        {
            fPendingStates.Push();
            index = fPendingStates.GetCount() - 1;
            fPendingStates[index].fSharedMeshKey = meshKey;
        }
        if (fPendingStates[index].fArrayWeights.GetCount() <= iLay)
        {
            int had = fPendingStates[index].fArrayWeights.GetCount();
            hsTArray<plMorphArrayWeights> temp(iLay + 1);
            temp = fPendingStates[index].fArrayWeights;
            temp.SetCount(iLay + 1);

            fPendingStates[index].fArrayWeights.Swap(temp);
            int i;
            for( i = had; i < iLay+1; i++ )
                fPendingStates[index].fArrayWeights[i].fDeltaWeights.Reset();
        }
        if (fPendingStates[index].fArrayWeights[iLay].fDeltaWeights.GetCount() <= iDel)
            fPendingStates[index].fArrayWeights[iLay].fDeltaWeights.ExpandAndZero(iDel + 1);

        fPendingStates[index].fArrayWeights[iLay].fDeltaWeights[iDel] = w;
    }
}

void plMorphSequence::ISetDirty(hsBool on) 
{ 
    if( on )
    {
        if( !(fMorphFlags & kDirty) )
            plgDispatch::Dispatch()->RegisterForExactType(plRenderMsg::Index(), GetKey());
        fMorphFlags |= kDirty; 

        // Even if we know we're already dirty, there could be new info this frame.
        // Need to tell our scene object
        //
        // Actually, in the case of the avatar, this sends an insane flurry of messages to the server 
        // when we drag (or even just hold the mouse button down on) the morph scrollbars. 
        // These messages are completely unneccessary. We broadcast our state when we join a new age, and that's enough.
        // Non-avatar related morphs (if they ever exist) will need to call DirtySynchState some special
        // way. Not here.
        //
        //if (GetTarget(0))
        //  GetTarget(0)->DirtySynchState(kSDLMorphSequence, 0);
    }
    else
    {
        fMorphFlags &= ~kDirty; 
    }
}

// MorphSequence - Init
void plMorphSequence::Init()
{
    if( fMorphFlags & kHaveShared )
    {
        IFindIndices();
    }
    else
    {
        if( !GetHaveSnap() )
        {
            // Take an access snapshot of all our spans
            const plDrawInterface* di = IGetDrawInterface();

            plAccessGeometry::Instance()->TakeSnapShot(di, kChanMask);

            ISetHaveSnap(true);
        }
    }
}

void plMorphSequence::Activate()
{
    Init();
    ISetDirty(true);
}

void plMorphSequence::DeActivate()
{
    if( fMorphFlags & kHaveShared )
    {
        IReleaseIndices();
    }
    plgDispatch::Dispatch()->UnRegisterForExactType(plRenderMsg::Index(), GetKey());
}

// MorphSequence - DeInit
void plMorphSequence::DeInit()
{
    if( fMorphFlags & kHaveShared )
    {
        IReleaseIndices();
    }
    else
    {
        if( GetHaveSnap() )
        {
            // Release all our snapshot data
            const plDrawInterface* di = IGetDrawInterface();

            if( di ) 
                plAccessGeometry::Instance()->ReleaseSnapShot(di);

            ISetHaveSnap(false);
        }
    }
}

void plMorphSequence::IRenormalize(hsTArray<plAccessSpan>& dst) const
{
    int i;
    for( i = 0; i < dst.GetCount(); i++ )
    {
        hsAssert(dst[i].HasAccessVtx(), "Come on, everyone has vertices");
        plAccessVtxSpan& accVtx = dst[i].AccessVtx();
        int j;
        for( j = 0; j < accVtx.VertCount(); j++ )
        {
            hsFastMath::Normalize(accVtx.Normal(j));
        }
    }
}

// MorphSequence - Apply
void plMorphSequence::Apply() const
{
    const plDrawInterface* di = IGetDrawInterface();
    if( !di )
        return;

    // Reset to initial.
    Reset(di);

    // We'll be accumulating into the buffer, so open RW
    hsTArray<plAccessSpan> dst;
    plAccessGeometry::Instance()->OpenRW(di, dst);

    // For each MorphArray
    int i;
    for( i = 0; i < fMorphs.GetCount(); i++ )
    {
        // Apply Delta
        fMorphs[i].Apply(dst);
    }

    IRenormalize(dst);

    // Close up the access spans
    plAccessGeometry::Instance()->Close(dst);
}

// MorphSequence - Reset to initial
void plMorphSequence::Reset(const plDrawInterface* di) const
{
    if( !di )
    {
        di = IGetDrawInterface();
    }

    // Use access span RestoreSnapshot
    if( di )
        plAccessGeometry::Instance()->RestoreSnapShot(di, kChanMask);
}

const plDrawInterface* plMorphSequence::IGetDrawInterface() const
{
    plSceneObject* so = GetTarget();
    if( !so )
        return nil;

    const plDrawInterface* di = so->GetDrawInterface();

    return di;
}

void plMorphSequence::AddTarget(plSceneObject* so)
{
    plSingleModifier::AddTarget(so);

    if (!fMorphSDLMod)
    {
        fMorphSDLMod = TRACKED_NEW plMorphSequenceSDLMod;
        so->AddModifier(fMorphSDLMod);
    }
}

void plMorphSequence::RemoveTarget(plSceneObject *so)
{
    plSingleModifier::RemoveTarget(so);

    if (so)
        if (fMorphSDLMod)
            so->RemoveModifier(fMorphSDLMod);

    delete fMorphSDLMod;
    fMorphSDLMod = nil;     
}
    
void plMorphSequence::Read(hsStream* s, hsResMgr* mgr)
{
    plSingleModifier::Read(s, mgr);

    fMorphFlags = 0;

    int n = s->ReadSwap32();
    fMorphs.SetCount(n);
    int i;
    for( i = 0; i < n; i++ )
        fMorphs[i].Read(s, mgr);
    
    n = s->ReadSwap32();
    for( i = 0; i < n; i++)
        mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef);
}

void plMorphSequence::Write(hsStream* s, hsResMgr* mgr)
{
    plSingleModifier::Write(s, mgr);

    s->WriteSwap32(fMorphs.GetCount());
    int i;
    for( i = 0; i < fMorphs.GetCount(); i++ )
        fMorphs[i].Write(s, mgr);

    s->WriteSwap32(fSharedMeshes.GetCount());
    for( i = 0; i < fSharedMeshes.GetCount(); i++ )
        mgr->WriteKey(s, fSharedMeshes[i].fMesh);
}

// Normal sequence of calls:
// 1) on notification that meshes have changed (or activate)
//      IFindIndices() - Sets up indices 
//      IApplyShared() - Find which mesh is active and
//          IResetShared(iActive);
//          IApplyShared(iActive);
//      go dormant
// 2) on weight change
//      SetWeight() - Register for render message and set the new weight
// 3) on render msg:
//      if dirty
//          IApplyShared() - Find which mesh is active and
//              IResetShared(iActive);
//              IApplyShared(iActive);
//      else
//          Unregister for render message.
// 4) on deinit
//      IReleaseIndices() - Just let's us know there's nothing much to be done.
//

void plMorphSequence::IResetShared()
{
    int i;
    for (i = 0; i < fSharedMeshes.GetCount(); i++)
        IResetShared(i);
}

void plMorphSequence::IApplyShared()
{
    int i;
    for (i = 0; i < fSharedMeshes.GetCount(); i++)
    {
        IResetShared(i);
        IApplyShared(i);
    }
}

void plMorphSequence::IFindIndices()
{
    fMorphFlags &= ~kDirtyIndices;

    int i;
    for( i = 0; i < fSharedMeshes.GetCount(); i++ )
        IFindIndices(i);
}

void plMorphSequence::IReleaseIndices()
{
    int i;
    for( i = 0; i < fSharedMeshes.GetCount(); i++ )
        IReleaseIndices(i);
}


void plMorphSequence::IApplyShared(int iShare)
{
    if( iShare >= fSharedMeshes.GetCount() || fSharedMeshes[iShare].fCurrDraw == nil)
        return;

    plSharedMeshInfo& mInfo = fSharedMeshes[iShare];

    hsTArray<plAccessSpan> dst;
    // Now copy each shared mesh geometryspan into the drawable
    // to get it back to it's pristine condition.
    int i;
    for( i = 0; i < mInfo.fMesh->fSpans.GetCount(); i++ )
    {
        plAccessSpan dstAcc;
        plAccessGeometry::Instance()->OpenRW(mInfo.fCurrDraw, mInfo.fCurrIdx[i], dstAcc);

        dst.Append(dstAcc);
    }

    // For each MorphArray
    for( i = 0; i < mInfo.fMesh->fMorphSet->fMorphs.GetCount(); i++ )
    {
        // Apply Delta
        mInfo.fMesh->fMorphSet->fMorphs[i].Apply(dst, &mInfo.fArrayWeights[i].fDeltaWeights);
    }

    IRenormalize(dst);

    // Close up the access spans
    plAccessGeometry::Instance()->Close(dst);
    mInfo.fFlags &= ~plSharedMeshInfo::kInfoDirtyMesh;
}

hsBool plMorphSequence::IResetShared(int iShare)
{
    if( iShare >= fSharedMeshes.GetCount() || fSharedMeshes[iShare].fCurrDraw == nil)
        return false;

    plSharedMeshInfo& mInfo = fSharedMeshes[iShare];

    // Now copy each shared mesh geometryspan into the drawable
    // to get it back to it's pristine condition.
    int i;
    for( i = 0; i < mInfo.fMesh->fSpans.GetCount(); i++ )
    {
        plAccessSpan srcAcc;
        plAccessGeometry::Instance()->AccessSpanFromGeometrySpan(srcAcc, mInfo.fMesh->fSpans[i]);
        plAccessSpan dstAcc;
        plAccessGeometry::Instance()->OpenRW(mInfo.fCurrDraw, mInfo.fCurrIdx[i], dstAcc);
        
        plAccPosNormUVWIterator srcIter(&srcAcc.AccessVtx());
        plAccPosNormUVWIterator dstIter(&dstAcc.AccessVtx());

        const int numUVWs = srcAcc.AccessVtx().NumUVWs();

        for( srcIter.Begin(), dstIter.Begin(); srcIter.More(); srcIter.Advance(), dstIter.Advance() )
        {
            *dstIter.Position() = *srcIter.Position();
            *dstIter.Normal() = *srcIter.Normal();

            int j;
            for( j = 0; j < numUVWs; j++ )
                *dstIter.UVW(j) = *srcIter.UVW(j);
        }
    }

    return true;
}

hsBool plMorphSequence::IFindIndices(int iShare)
{
    plSharedMeshInfo& mInfo = fSharedMeshes[iShare];
    mInfo.fCurrDraw = nil; // In case we fail.

    const plInstanceDrawInterface* di = plInstanceDrawInterface::ConvertNoRef(IGetDrawInterface());
    if( !di )
        return false;

    Int32 meshIdx = di->GetSharedMeshIndex(mInfo.fMesh);
    if( meshIdx < 0 )
        return false;

    plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable((UInt8)meshIdx));
    if( !dr )
        return false;

    mInfo.fCurrDraw = dr;

    plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex((UInt8)meshIdx));

    hsAssert(mInfo.fMesh->fSpans.GetCount() == diIndex.GetCount(), "Mismatch between geometry and indices");

    mInfo.fCurrIdx.SetCount(diIndex.GetCount());

    int i;
    for( i = 0; i < diIndex.GetCount(); i++ )
        mInfo.fCurrIdx[i] = diIndex[i];

    return true;
}

void plMorphSequence::IReleaseIndices(int iShare)
{
    plSharedMeshInfo& mInfo = fSharedMeshes[iShare];
    mInfo.fCurrDraw = nil;
}

Int32 plMorphSequence::IFindSharedMeshIndex(plKey meshKey) const
{
    int i;
    for( i = 0; i < fSharedMeshes.GetCount(); i++ )
    {
        if (fSharedMeshes[i].fMesh->GetKey() == meshKey)
            return i;
    }

    return -1;
}

Int32 plMorphSequence::IFindPendingStateIndex(plKey meshKey) const
{
    int i;
    for( i = 0; i < fPendingStates.GetCount(); i++ )
    {
        if (fPendingStates[i].fSharedMeshKey == meshKey)
            return i;
    }
    
    return -1;
}

hsBool plMorphSequence::IIsUsingDrawable(plDrawable *draw)
{
    int i;
    for (i = 0; i < fSharedMeshes.GetCount(); i++)
    {
        if (fSharedMeshes[i].fCurrDraw == draw)
            return true;
    }

    return false;
}

void plMorphSequence::AddSharedMesh(plSharedMesh* mesh)
{
    // This happens if SDL state tells us to use a mesh without morph info
    // (Either because the object used to but no longer does, or because
    // the SDL is giving us the wrong key.)
    if (!mesh->fMorphSet)
        return;

    if (fSharedMeshes.GetCount() == 0)
        plgDispatch::Dispatch()->RegisterForExactType(plSharedMeshBCMsg::Index(), GetKey());

    if (IFindSharedMeshIndex(mesh->GetKey()) >= 0)
        return; // We already have it.

    hsAssert(fSharedMeshes.GetCount() < 127, "Too many meshes for one morph sequence.");
    SetUseSharedMesh(true); 
    int pendingIndex = IFindPendingStateIndex(mesh->GetKey());

    plSharedMeshInfo mInfo;
    mInfo.fMesh = mesh;
    mInfo.fCurrDraw = nil;

    // Intialize our weights to zero.
    mInfo.fArrayWeights.Reset();
    mInfo.fArrayWeights.SetCount(mesh->fMorphSet->fMorphs.GetCount());
    int i, j;
    for (i = 0; i < mesh->fMorphSet->fMorphs.GetCount(); i++)
        mInfo.fArrayWeights[i].fDeltaWeights.ExpandAndZero(mesh->fMorphSet->fMorphs[i].GetNumDeltas());
            
    // Aha, we have some pending weights. Copy them in!
    if (pendingIndex >= 0)
    {
        // Filter in any data that's valid
        for (i = 0; i < mInfo.fArrayWeights.GetCount() && i < fPendingStates[pendingIndex].fArrayWeights.GetCount(); i++)
        {
            for (j = 0; j < mInfo.fArrayWeights[i].fDeltaWeights.GetCount() && 
                        j < fPendingStates[pendingIndex].fArrayWeights[i].fDeltaWeights.GetCount(); j++)
            {
                mInfo.fArrayWeights[i].fDeltaWeights[j] = fPendingStates[pendingIndex].fArrayWeights[i].fDeltaWeights[j];
            }
        }
            
        fPendingStates.Remove(pendingIndex);

        mInfo.fFlags |= plSharedMeshInfo::kInfoDirtyMesh;
        ISetDirty(true);
    }

    fSharedMeshes.Append(mInfo);
    IFindIndices(fSharedMeshes.GetCount() - 1);

    if (mesh->fFlags & plSharedMesh::kLayer0GlobalToMod)
    {
        fGlobalLayerRef = fSharedMeshes.GetCount() - 1;
        ISetAllSharedToGlobal();
    }
    else 
        ISetSingleSharedToGlobal(fSharedMeshes.GetCount() - 1);
}

void plMorphSequence::RemoveSharedMesh(plSharedMesh* mesh)
{
    int idx = IFindSharedMeshIndex(mesh->GetKey());
    if (idx < 0)
        return; // Don't have it... can't remove it can we?
    
    fSharedMeshes.Remove(idx);

    fGlobalLayerRef = -1;
    int i;
    for (i = 0; i < fSharedMeshes.GetCount(); i++)
    {
        if (fSharedMeshes[i].fMesh->fFlags & plSharedMesh::kLayer0GlobalToMod)
        {
            fGlobalLayerRef = i;
            break;
        }
    }
    
    if (fSharedMeshes.GetCount() == 0)
        plgDispatch::Dispatch()->UnRegisterForExactType(plSharedMeshBCMsg::Index(), GetKey());
}

void plMorphSequence::FindMorphMods(const plSceneObject *so, hsTArray<const plMorphSequence*> &mods)
{
    const plMorphSequence *morph = plMorphSequence::ConvertNoRef(so->GetModifierByType(plMorphSequence::Index()));
    if (morph)
        mods.Append(morph);
    
    const plCoordinateInterface *ci = so->GetCoordinateInterface();
    int i;
    for (i = 0; i < ci->GetNumChildren(); i++)
        FindMorphMods(ci->GetChild(i)->GetOwner(), mods);
}

void plMorphSequence::ISetAllSharedToGlobal()
{
    int i;
    for (i = 0; i < fSharedMeshes.GetCount(); i++)
    {
        if (i != fGlobalLayerRef)
            ISetSingleSharedToGlobal(i);
    }
}

void plMorphSequence::ISetSingleSharedToGlobal(int idx)
{
    if (fGlobalLayerRef < 0)
        return;

    int i;
    for (i = 0; i < fSharedMeshes[fGlobalLayerRef].fArrayWeights[0].fDeltaWeights.GetCount(); i++)
        SetWeight(0, i, fSharedMeshes[fGlobalLayerRef].fArrayWeights[0].fDeltaWeights[i], fSharedMeshes[idx].fMesh->GetKey());
}