/*==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 .
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 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 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& 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 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 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 &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());
}