// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "std3d.h"
#include "nel/3d/bone.h"
#include "nel/3d/anim_ctrl.h"
#include "nel/misc/hierarchical_timer.h"
namespace NL3D
{
// ***************************************************************************
// ***************************************************************************
// CBoneBase
// ***************************************************************************
// ***************************************************************************
static const CVector UnitScale(1,1,1);
// ***************************************************************************
CBoneBase::CBoneBase() : DefaultPos(CVector(0,0,0)), DefaultRotEuler(CVector(0,0,0)),
DefaultScale(UnitScale), DefaultPivot(CVector(0,0,0)), SkinScale(UnitScale)
{
FatherId= -1;
UnheritScale= true;
// Default: never disable.
LodDisableDistance= 0.f;
}
// ***************************************************************************
void CBoneBase::serial(NLMISC::IStream &f)
{
/*
Version 2:
- SkinScale
Version 1:
- LodDisableDistance
*/
sint ver= f.serialVersion(2);
f.serial(Name);
f.serial(InvBindPos);
f.serial(FatherId);
f.serial(UnheritScale);
if(ver>=1)
f.serial(LodDisableDistance);
else
{
// Default: never disable.
LodDisableDistance= 0.f;
}
f.serial(DefaultPos);
f.serial(DefaultRotEuler);
f.serial(DefaultRotQuat);
f.serial(DefaultScale);
f.serial(DefaultPivot);
if(ver>=2)
f.serial(SkinScale);
}
// ***************************************************************************
// ***************************************************************************
// CBone
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CBone::CBone(CBoneBase *boneBase)
{
nlassert(boneBase);
_BoneBase= boneBase;
// IAnimatable.
IAnimatable::resize(AnimValueLast);
ITransformable::setTransformMode(ITransformable::RotQuat);
ITransformable::setPos( _BoneBase->DefaultPos.getDefaultValue() );
ITransformable::setRotQuat( _BoneBase->DefaultRotQuat.getDefaultValue() );
ITransformable::setScale( _BoneBase->DefaultScale.getDefaultValue() );
ITransformable::setPivot( _BoneBase->DefaultPivot.getDefaultValue() );
// By default, the bone is not binded to a channelMixer.
_PosChannelId= -1;
_RotEulerChannelId= -1;
_RotQuatChannelId= -1;
_ScaleChannelId= -1;
_PivotChannelId= -1;
// No animCtrl by default
_AnimCtrl= NULL;
// Get default BoneBase SkinScale
_SkinScale= _BoneBase->SkinScale;
}
// ***************************************************************************
ITrack* CBone::getDefaultTrack (uint valueId)
{
nlassert(_BoneBase);
// what value ?
switch (valueId)
{
case PosValue: return &_BoneBase->DefaultPos;
case RotEulerValue: return &_BoneBase->DefaultRotEuler;
case RotQuatValue: return &_BoneBase->DefaultRotQuat;
case ScaleValue: return &_BoneBase->DefaultScale;
case PivotValue: return &_BoneBase->DefaultPivot;
}
// No, only ITrnasformable values!
nlstop;
// Deriver note: else call BaseClass::getDefaultTrack(valueId);
return NULL;
}
// ***************************************************************************
void CBone::registerToChannelMixer(CChannelMixer *chanMixer, const std::string &prefix)
{
// For CBone, channels are detailled.
// Bkup each channelId (for disable).
_PosChannelId= addValue(chanMixer, PosValue, OwnerBit, prefix, true);
_RotEulerChannelId= addValue(chanMixer, RotEulerValue, OwnerBit, prefix, true);
_RotQuatChannelId= addValue(chanMixer, RotQuatValue, OwnerBit, prefix, true);
_ScaleChannelId= addValue(chanMixer, ScaleValue, OwnerBit, prefix, true);
_PivotChannelId= addValue(chanMixer, PivotValue, OwnerBit, prefix, true);
// Deriver note: if necessary, call BaseClass::registerToChannelMixer(chanMixer, prefix);
}
// ***************************************************************************
void CBone::compute(CBone *parent, const CMatrix &rootMatrix, CSkeletonModel *skeletonForAnimCtrl)
{
// compute is called typically 800 time per frame.
#ifdef NL_DEBUG
nlassert(_BoneBase);
#endif
// get/compute our local matrix
const CMatrix &localMatrix= getMatrix();
// Compute LocalSkeletonMatrix.
// Root case?
if(!parent)
{
_LocalSkeletonMatrix= localMatrix;
}
// Else, son case, take world matrix from parent.
else
{
// UnheritScale case.
if(_BoneBase->UnheritScale)
{
CMatrix invScaleComp;
CVector fatherScale;
CVector trans;
/* Optim note:
the scale is rarely ==1, so don't optimize case where it may be.
*/
// retrieve our translation
localMatrix.getPos(trans);
// retrieve scale from our father.
parent->getScale(fatherScale);
// inverse this scale.
fatherScale.x= 1.0f / fatherScale.x;
fatherScale.y= 1.0f / fatherScale.y;
fatherScale.z= 1.0f / fatherScale.z;
// Compute InverseScale compensation:
// with UnheritScale, formula per bone should be T*Sf-1*P*R*S*P-1.
// But getMatrix() return T*P*R*S*P-1.
// So we must compute T*Sf-1*T-1, in order to get wanted result.
invScaleComp.setScale(fatherScale);
// Faster compute of the translation part: just "trans + fatherScale MUL -trans" where MUL is comp mul
trans.x-= fatherScale.x * trans.x;
trans.y-= fatherScale.y * trans.y;
trans.z-= fatherScale.z * trans.z;
invScaleComp.setPos(trans);
// And finally, we got ParentWM * T*Sf-1*P*R*S*P-1.
// Do: _LocalSkeletonMatrix= parent->_LocalSkeletonMatrix * invScaleComp * localMatrix
static CMatrix tmp;
tmp.setMulMatrixNoProj( parent->_LocalSkeletonMatrix, invScaleComp );
_LocalSkeletonMatrix.setMulMatrixNoProj( tmp, localMatrix );
}
// Normal case.
else
{
// Do: _LocalSkeletonMatrix= parent->_LocalSkeletonMatrix * localMatrix
_LocalSkeletonMatrix.setMulMatrixNoProj( parent->_LocalSkeletonMatrix, localMatrix );
}
}
// Compute WorldMatrix. Do: _WorldMatrix= rootMatrix * _LocalSkeletonMatrix
_WorldMatrix.setMulMatrixNoProj( rootMatrix, _LocalSkeletonMatrix );
// Compute BoneSkinMatrix. Easier of no SkinScale
if(_SkinScale==UnitScale)
{
// Do: _BoneSkinMatrix= _LocalSkeletonMatrix * _BoneBase->InvBindPos
_BoneSkinMatrix.setMulMatrixNoProj( _LocalSkeletonMatrix, _BoneBase->InvBindPos );
}
else
{
// Do: _BoneSkinMatrix= _LocalSkeletonMatrix * SkinScale * _BoneBase->InvBindPos
CMatrix tmp;
CMatrix scaleMat;
scaleMat.setScale(_SkinScale);
tmp.setMulMatrixNoProj(_LocalSkeletonMatrix, scaleMat);
_BoneSkinMatrix.setMulMatrixNoProj( tmp, _BoneBase->InvBindPos );
}
// When compute is done, do extra user ctrl?
if(_AnimCtrl && skeletonForAnimCtrl)
_AnimCtrl->execute(skeletonForAnimCtrl, this);
}
// ***************************************************************************
void CBone::interpolateBoneSkinMatrix(const CMatrix &otherMatrix, float interp)
{
CMatrix &curMatrix= _BoneSkinMatrix;
// interpolate rot/scale. Just interpolate basis vectors
CVector fatherI= otherMatrix.getI();
CVector curI= curMatrix.getI();
curI= fatherI*(1-interp) + curI*interp;
CVector fatherJ= otherMatrix.getJ();
CVector curJ= curMatrix.getJ();
curJ= fatherJ*(1-interp) + curJ*interp;
CVector fatherK= otherMatrix.getK();
CVector curK= curMatrix.getK();
curK= fatherK*(1-interp) + curK*interp;
// replace rotation
curMatrix.setRot(curI, curJ, curK);
// interpolate pos
CVector fatherPos= otherMatrix.getPos();
CVector curPos= curMatrix.getPos();
curPos= fatherPos*(1-interp) + curPos*interp;
curMatrix.setPos(curPos);
}
// ***************************************************************************
void CBone::lodEnableChannels(CChannelMixer *chanMixer, bool enable)
{
nlassert(chanMixer);
// Lod Enable channels if they are correclty registered to the channelMixer.
if( _PosChannelId>=0 )
chanMixer->lodEnableChannel(_PosChannelId, enable);
if( _RotEulerChannelId>=0 )
chanMixer->lodEnableChannel(_RotEulerChannelId, enable);
if( _RotQuatChannelId>=0 )
chanMixer->lodEnableChannel(_RotQuatChannelId, enable);
if( _ScaleChannelId>=0 )
chanMixer->lodEnableChannel(_ScaleChannelId, enable);
if( _PivotChannelId>=0 )
chanMixer->lodEnableChannel(_PivotChannelId, enable);
}
// ***************************************************************************
void CBone::setSkinScale(CVector &skinScale)
{
_SkinScale= skinScale;
}
} // NL3D