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