Merge branch 'ryzom/ark-features' into main/gingo-test
[ryzomcore.git] / nel / src / 3d / target_anim_ctrl.cpp
blob8145fdb93afafb12cdb28dd3206809b1f2c60618
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "std3d.h"
18 #include "nel/misc/common.h"
19 #include "nel/3d/target_anim_ctrl.h"
20 #include "nel/3d/bone.h"
21 #include "nel/3d/skeleton_model.h"
22 #include "nel/3d/scene.h"
24 using namespace std;
25 using namespace NLMISC;
27 #ifdef DEBUG_NEW
28 #define new DEBUG_NEW
29 #endif
31 namespace NL3D {
34 // ***************************************************************************
35 CTargetAnimCtrl::CTargetAnimCtrl()
37 Mode= DirectionMode;
38 WorldTarget= CVector::Null;
39 EyePos= CVector::Null;
40 // Default Direction to "LookBack".
41 DefaultWorldDirection.set(0,0,1,0);
42 MaxAngle= (float)Pi/3;
43 MaxAngularVelocity= (float)(2*Pi);
44 Enabled= true;
45 _EnableToDisableTransition= false;
46 _LastEnabled= true;
50 // ***************************************************************************
51 CTargetAnimCtrl::~CTargetAnimCtrl()
56 // ***************************************************************************
57 void CTargetAnimCtrl::execute(CSkeletonModel *model, CBone *bone)
59 // no op if req not met
60 if(!bone || !model || bone->getTransformMode()!=ITransformable::RotQuat)
61 return;
63 // If the user changed the Enabled state, must do a transition.
64 if(_LastEnabled!=Enabled)
66 _LastEnabled= Enabled;
67 // if re-enable the control while completely disabled before
68 if(Enabled && !_EnableToDisableTransition)
70 // set the LastLSRotation so that it match the current anim ones.
71 _LastLSRotation= getCurrentLSRotationFromBone(model, bone);
73 // if disable the ctrl, then do a transition first
74 if(!Enabled)
76 _EnableToDisableTransition= true;
80 // If not enabled, and not in transition, no op.
81 if( !Enabled && !_EnableToDisableTransition)
82 return;
84 // If Target mode, compute CurrentWorldDirection
85 // ***********
86 // NB: does need to compute direction if disabled (even if in transition)
87 if(Mode==TargetMode && Enabled)
89 // get the eye pos in world.
90 CVector worldEye= bone->getWorldMatrix()*EyePos;
91 // get the world dir
92 CVector worldDir= (WorldTarget - worldEye).normed();
93 // get the associated quat
94 CMatrix mat;
95 mat.setRot(CVector::I, worldDir, CVector::K);
96 mat.normalize(CMatrix::YZX);
97 CurrentWorldDirection= mat.getRot();
101 // compute rotation to apply In LocalSkeleton Space
102 // ***********
103 CQuat currentLSRotation;
105 /* Get the Skeleton default WorldMatrix (bind pos). used later
106 TRICK: to get the default Skeleton WorldMatrix, get it from Bone[0] (ie "Bip01")
107 We cannot use the Default Pos/Rot of the skeleton model because of export bug (not exported...)
108 So, since Bip01 as no Local Rot (yes, its true, exporter report all Bip01 rots on Skeleton),
109 we are sure that Bip01 BindPos is the default Skeleton World Matrix.
111 CQuat rootInvBP= model->Bones[0].getBoneBase().InvBindPos.getRot();
113 // If ctrl not enabled, take the LSRotation from the animation
114 if(!Enabled)
116 currentLSRotation= getCurrentLSRotationFromBone(model, bone);
118 else
120 // Get the wanted direction in LocalSkeleton Space.
121 CQuat currentLSDirection;
123 // Get the current wanted WorldDirection into LocalSkeleton space (hence using current skeleton WorldMatrix).
124 CMatrix toLSSpace= model->getWorldMatrix();
125 currentLSDirection= toLSSpace.getRot().conjugate() * CurrentWorldDirection;
127 // Get the default WorldDirection into LocalSkeleton space (hence using default skeleton WorldMatrix).
128 CQuat defaultLSDirection= rootInvBP * DefaultWorldDirection;
130 /* get the rotation to apply to the bone when it is in bind Pos. If this quat is identity,
131 then the bone will be in default (or bind) pos.
132 It is in essence the "rotation to apply to defaultDirection in LS space, in order to get
133 the wanted current direction in LS space".
134 The makeClosest() is here just to ensure that the resulting angle<Pi (for clamp direction later)
136 currentLSDirection.makeClosest(defaultLSDirection);
137 currentLSRotation= currentLSDirection * defaultLSDirection.conjugate();
141 // Clamp direction, and smooth direction changes.
142 // ***********
143 // if not enabled, then LSRotation comes from the animation => do not clamp
144 if(Enabled)
146 // to AngleAxis.
147 CAngleAxis angleAxis= currentLSRotation.getAngleAxis();
148 // Clamp the angle
149 clamp(angleAxis.Angle, -MaxAngle, MaxAngle);
150 // back To Quat.
151 currentLSRotation.setAngleAxis(angleAxis);
154 // get the dt of the scene.
155 CScene *scene= model->getOwnerScene();
156 float sceneDt= scene->getEllapsedTime();
157 float maxDeltaAngle= MaxAngularVelocity*sceneDt;
158 // get the quat that change from LastRotation to CurrentRotation
159 CQuat rotMod= _LastLSRotation.conjugate() * currentLSRotation;
160 // compute how many rotation we are allowed to do.
161 float rotModAngle= (float)fabs(rotMod.getAngle());
162 bool rotSpeedClamped= false;
163 if(rotModAngle)
165 float factor= (float)fabs(maxDeltaAngle) / rotModAngle;
166 // If cannot do all the rotation this frame
167 if(factor<1)
169 // then slerp between last and current rotation
170 currentLSRotation.makeClosest(_LastLSRotation);
171 currentLSRotation= CQuat::slerp(_LastLSRotation, currentLSRotation, factor);
172 rotSpeedClamped= true;
176 // if the rotation has not been clamped for speed consideration, and if !Enabed, it's mean we have ended
177 // the transition => no more compute next time
178 if(!Enabled && !rotSpeedClamped)
179 _EnableToDisableTransition= false;
181 // bkup last rotation
182 _LastLSRotation= currentLSRotation;
184 // Apply the weighted Rotation to the bone
185 // ***********
186 // Get the bone Bind Pos in LocalSkeleton space (hence using Default Skeleton WorldMatrix)
187 CQuat boneBindPosInWorld= bone->getBoneBase().InvBindPos.getRot().conjugate();
188 CQuat boneBindPosInLS= rootInvBP * boneBindPosInWorld;
189 // rotate it to match our wanted direction.
190 boneBindPosInLS= currentLSRotation * boneBindPosInLS;
192 // get it in bone local space.
193 // get the Bone Parent LocalSkeletonMatrix
194 CBone *boneParent= bone->getFatherId()==-1? NULL : &model->Bones[bone->getFatherId()];
195 CQuat currentLocalQuat;
196 if(!boneParent)
197 currentLocalQuat= boneBindPosInLS;
198 else
200 // compute the rotation to apply, in local space
201 CQuat qp= boneParent->getLocalSkeletonMatrix().getRot();
202 currentLocalQuat= qp.conjugate() * boneBindPosInLS;
205 // set the new LocalRotQuat
206 bone->setRotQuat(currentLocalQuat);
207 // and recompute the bone (but without AnimCtrl of course :) )
208 bone->compute(boneParent, model->getWorldMatrix(), NULL);
213 // ***************************************************************************
214 CQuat CTargetAnimCtrl::getCurrentLSRotationFromBone(CSkeletonModel *model, CBone *bone)
216 // get the current rotation matrix (qmat) of this bone, in LS space
217 CQuat currentLSRot= bone->getLocalSkeletonMatrix().getRot();
219 // get the default bindPos (qb) rotation of this bone, in LS space.
220 CQuat boneBindPosInWorld= bone->getBoneBase().InvBindPos.getRot().conjugate();
221 CQuat rootInvBP= model->Bones[0].getBoneBase().InvBindPos.getRot();
222 CQuat boneBindPosInLS= rootInvBP * boneBindPosInWorld;
224 // The rotation (qrot) is computed such that qmat= qrot * qb
225 currentLSRot.makeClosest(boneBindPosInLS);
226 return currentLSRot * boneBindPosInLS.conjugate();
231 } // NL3D