Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / client / src / attached_fx.cpp
blob762898f67ff1886068c5b8e775b71cb156068bfb
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2013 Matt RAYKOWSKI (sfb) <matt.raykowski@gmail.com>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdpch.h"
21 #include "nel/misc/matrix.h"
22 #include "nel/3d/u_scene.h"
23 #include "nel/3d/u_transform.h"
24 #include "nel/3d/u_skeleton.h"
25 #include "nel/3d/u_bone.h"
26 #include "attached_fx.h"
27 #include "character_cl.h"
28 #include "user_entity.h"
29 #include "entities.h"
30 #include "time_client.h"
31 #include "fx_manager.h"
33 #ifdef DEBUG_NEW
34 #define new DEBUG_NEW
35 #endif
37 extern NL3D::UScene *Scene;
40 using namespace NLMISC;
41 using namespace NL3D;
43 extern CUserEntity *UserEntity;
45 // *************************************************************************************
46 CAttachedFX::CAttachedFX()
48 clear();
51 // *************************************************************************************
52 CAttachedFX::~CAttachedFX()
54 clear();
57 // ***********************************************************************************************************************
58 void CAttachedFX::clear()
60 if (!FX.empty())
62 if (StickMode == CFXStickMode::SpawnPermanent)
64 FXMngr.addFX(FX, 100.f);
66 else
68 Scene->deleteInstance(FX);
71 FX = NULL;
72 AniFX = NULL;
73 TimeOutDate = FX_MANAGER_DEFAULT_TIMEOUT;
74 StickMode = CFXStickMode::Follow;
75 MaxAnimCount = 0;
76 UserBoneID = ~0;
77 TargeterUserBoneID = 0xff;
80 // ***********************************************************************************************************************
81 void CAttachedFX::create(CCharacterCL &parent,
82 const CBuildInfo &buildInfo,
83 const CTargeterInfo &targeterInfo
86 clear();
87 if (!buildInfo.Sheet) return;
88 UParticleSystemInstance instance = buildInfo.Sheet->createMatchingInstance();
89 // TODO nico : user params are not in the buildInfo, but are set by createMatchingInstance then by the caller (they could be set directly by createMatchingInstance)
90 if (instance.empty()) return;
91 create(parent, instance, buildInfo, targeterInfo);
94 // ***********************************************************************************************************************
95 void CAttachedFX::create(CCharacterCL &parent,
96 NL3D::UParticleSystemInstance instance,
97 const CBuildInfo &buildInfo,
98 const CTargeterInfo &targeterInfo
101 nlassert(buildInfo.Sheet);
102 TimeOutDate = buildInfo.TimeOut;
103 const CFXStickMode *stickMode = buildInfo.StickMode ? buildInfo.StickMode : &buildInfo.Sheet->Sheet->StickMode;
104 if (!instance.empty())
106 instance.setClusterSystem(parent.getClusterSystem());
107 instance.setTransformMode(NL3D::UTransformable::DirectMatrix);
108 instance.show();
110 FX = instance;
111 AniFX = buildInfo.Sheet;
112 StickMode = stickMode->Mode;
113 SpawnTime = TimeInSec;
114 MaxAnimCount = buildInfo.MaxNumAnimCount;
115 TargeterInfo = targeterInfo;
116 switch(stickMode->Mode)
118 case CFXStickMode::StaticMatrix:
120 const CMatrix &staticMatrix = buildInfo.StaticMatrix ? *buildInfo.StaticMatrix : NLMISC::CMatrix::Identity;
121 instance.setMatrix(staticMatrix);
122 SpawnPos = staticMatrix.getPos();
124 break;
125 case CFXStickMode::Spawn:
126 case CFXStickMode::SpawnPermanent:
128 parent.alignFX(instance, parent.getScalePos());
129 SpawnPos = parent.pos() + buildInfo.StickOffset;
130 if (stickMode->Mode == CFXStickMode::SpawnPermanent)
132 instance.forceInstanciate();
135 break;
136 case CFXStickMode::UserBone:
138 if (parent.skeleton())
140 sint boneID = parent.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(stickMode->UserBoneName));
141 UserBoneID = boneID;
142 if (boneID != -1)
144 parent.skeleton()->stickObjectEx(instance, boneID, true);
145 NLMISC::CMatrix mat = instance.getMatrix();
146 mat.scale(buildInfo.Sheet->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
147 mat.setPos(buildInfo.StickOffset);
148 if (!instance.empty()) instance.setMatrix(mat);
149 SpawnPos = buildInfo.StickOffset;
150 // if parent is hidden, then force to compute the bone at least once
151 if (parent.skeleton()->getVisibility() == NL3D::UTransform::Hide)
153 parent.forceEvalAnim();
154 // force to compute fx at least once
155 parent.skeleton()->forceComputeBone(UserBoneID);
157 break;
160 // bone not found or no skeleton
161 if (!parent.instance().empty())
163 instance.parent(parent.instance());
164 SpawnPos = buildInfo.StickOffset;
166 else
168 // just spawn at position of entity
169 SpawnPos = parent.pos() + buildInfo.StickOffset;
172 break;
173 case CFXStickMode::UserBoneOrientedTowardTargeter:
174 if (parent.skeleton())
176 UserBoneID = parent.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(stickMode->UserBoneName));
177 if (UserBoneID == 0xff)
179 nlwarning("Bad bone name : %s", NLMISC::CStringMapper::unmap(stickMode->UserBoneName).c_str());
182 else
184 UserBoneID = 0xff;
186 SpawnPos = buildInfo.StickOffset;
187 update(parent, CMatrix::Identity);
188 break;
189 case CFXStickMode::UserBoneRay:
190 if (parent.skeleton())
192 UserBoneID = parent.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(stickMode->UserBoneName));
193 if (UserBoneID == 0xff)
195 nlwarning("Bad bone name : %s", NLMISC::CStringMapper::unmap(stickMode->UserBoneName).c_str());
198 else
200 UserBoneID = 0xff;
202 SpawnPos = buildInfo.StickOffset;
203 TargeterUserBoneID =0xff;
204 if (targeterInfo.StickMode.UserBoneName != 0)
206 CEntityCL *targeter = EntitiesMngr.entity(TargeterInfo.Slot);
207 if (parent.skeleton() && targeter && targeter->skeleton())
209 TargeterUserBoneID = parent.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(TargeterInfo.StickMode.UserBoneName));
212 update(parent, CMatrix::Identity);
213 break;
214 case CFXStickMode::OrientedTowardTargeter:
215 SpawnPos = buildInfo.StickOffset;
216 update(parent, CMatrix::Identity);
217 break;
218 default: // -> stick fx in 'Follow' mode
219 parent.alignFX(instance, parent.getScalePos());
220 SpawnPos = buildInfo.StickOffset;
221 break;
226 // ***********************************************************************************************************************
227 void CAttachedFX::evalTargeterStickPos(NLMISC::CVector &dest) const
229 CEntityCL *targeter = EntitiesMngr.entity(TargeterInfo.Slot);
230 if (!targeter)
232 dest = TargeterInfo.DefaultPos;
233 return;
235 switch(TargeterInfo.StickMode.Mode)
237 case CFXStickMode::Spawn:
238 case CFXStickMode::SpawnPermanent:
239 case CFXStickMode::Follow:
240 case CFXStickMode::FollowNoRotation:
241 case CFXStickMode::OrientedTowardTargeter:
242 dest = targeter->pos();
243 break;
244 case CFXStickMode::StaticMatrix:
245 nlwarning("Not implemented"); // this case is not used for now
246 dest = targeter->pos();
247 break;
248 case CFXStickMode::UserBoneOrientedTowardTargeter:
249 case CFXStickMode::UserBoneRay:
250 case CFXStickMode::UserBone:
251 if (targeter->skeleton() && TargeterUserBoneID != 0xff)
253 const UBone bone = targeter->skeleton()->getBone(TargeterUserBoneID);
254 targeter->forceEvalAnim();
255 targeter->skeleton()->forceComputeBone(TargeterUserBoneID);
256 dest = bone.getLastWorldMatrixComputed() * TargeterInfo.StickOffset;
258 else
260 dest = TargeterInfo.DefaultPos;
262 break;
266 // ***********************************************************************************************************************
267 void CAttachedFX::update(CCharacterCL &parent, const NLMISC::CMatrix &alignMatrix)
269 if (AniFX && !FX.empty())
271 NLMISC::CVector trackPos;
272 // see if fx has a track applied on it
273 if (AniFX->PosTrack)
275 // eval current pos
276 AniFX->PosTrack->interpolate((float) (TimeInSec - SpawnTime), trackPos);
278 else
280 trackPos.set(0.f, 0.f, 0.f);
282 // apply pos depending on mode
283 switch(StickMode)
285 case CFXStickMode::UserBone:
287 FX.setClusterSystem(parent.getClusterSystem());
288 if (!AniFX->PosTrack)
290 // no track for fx,
291 if (parent.skeleton() && UserBoneID != 0xff)
293 if (parent.skeleton()->getVisibility() == UTransform::Hide)
295 // if user no visible, force to compute the bone.
296 parent.forceEvalAnim();
297 parent.skeleton()->forceComputeBone(UserBoneID);
300 return;
302 if (parent.skeleton())
304 if (UserBoneID != 0xff)
306 CMatrix mat = FX.getMatrix();
307 mat.setPos(trackPos + SpawnPos);
308 mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
309 FX.setMatrix(mat);
310 if (&parent == UserEntity && UserEntity->skeleton())
312 if (UserEntity->skeleton()->getVisibility() == UTransform::Hide)
314 // if user no visible, force to compute the bone.
315 parent.forceEvalAnim();
316 UserEntity->skeleton()->forceComputeBone(UserBoneID);
321 // no skeleton or bone not found
322 if (!parent.instance().empty())
324 CMatrix mat = FX.getMatrix();
325 mat.setPos(trackPos + SpawnPos);
326 mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
327 FX.setMatrix(mat);
329 else
331 // no skeleton, no instance
332 CMatrix mat = FX.getMatrix();
333 mat.setPos(trackPos + SpawnPos + parent.pos());
334 mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
335 FX.setMatrix(mat);
338 break;
339 case CFXStickMode::Follow:
340 // just change local pos
341 parent.alignFX(FX, alignMatrix, AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f, trackPos + SpawnPos);
342 break;
343 case CFXStickMode::FollowNoRotation:
345 // just update the pos
346 CMatrix mat = FX.getMatrix();
347 mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
348 mat.setPos(trackPos + parent.pos().asVector());
349 FX.setMatrix(mat);
350 FX.setClusterSystem(parent.getClusterSystem());
352 break;
353 case CFXStickMode::StaticObjectCastRay:
355 // if not animated need no updates
356 if (!AniFX->PosTrack) return;
357 nlwarning("Not implemented");
359 break;
360 case CFXStickMode::Spawn:
361 case CFXStickMode::SpawnPermanent:
363 if (!AniFX->PosTrack) return;
364 // put in local basis and offset spawn pos
365 CMatrix mat = FX.getMatrix();
366 mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
367 mat.setPos(SpawnPos + FX.getMatrix().mulVector(trackPos));
368 FX.setMatrix(mat);
369 // Do not update the cluster system because fx stays in place
371 break;
372 case CFXStickMode::OrientedTowardTargeter:
374 CEntityCL *targeter = EntitiesMngr.entity(TargeterInfo.Slot);
375 if (targeter)
377 CVectorD orientD = parent.pos() - targeter->pos();
378 CVector J((float) orientD.x, (float) orientD.y, 0.f); // project on XY plane
379 J.normalize();
380 CVector I = J ^ CVector::K;
381 CMatrix mat;
382 mat.setRot(I, J, CVector::K);
383 mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
384 mat.setPos(trackPos + parent.pos().asVector());
385 FX.setMatrix(mat);
386 FX.setClusterSystem(parent.getClusterSystem());
389 break;
390 case CFXStickMode::UserBoneOrientedTowardTargeter:
392 CEntityCL *targeter = EntitiesMngr.entity(TargeterInfo.Slot);
393 if (targeter)
395 CVector orientD;
396 CMatrix orientMat;
397 if (UserBoneID == 0xff || !parent.skeleton())
399 // bone not found -> use parent position instead
400 orientD = parent.pos() - targeter->pos();
401 orientMat.setPos(trackPos + parent.pos());
403 else
405 const UBone bone = parent.skeleton()->getBone(UserBoneID);
406 parent.forceEvalAnim();
407 parent.skeleton()->forceComputeBone(UserBoneID);
408 const CMatrix &wm = bone.getLastWorldMatrixComputed();
409 // compute orientation toward targeter, and build a matrix from it
410 orientD = wm.getPos() - targeter->pos().asVector();
411 orientMat.setPos(trackPos + wm.getPos());
413 CVector J(orientD.x, orientD.y, 0.f); // project on XY plane
414 J.normalize();
415 CVector I = J ^ CVector::K;
416 float scale = AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f;
417 orientMat.setRot(scale * I, scale * J, scale * CVector::K, true);
418 FX.setMatrix(orientMat);
419 FX.setClusterSystem(parent.getClusterSystem());
422 break;
423 case CFXStickMode::UserBoneRay:
425 CVector aimingPoint;
426 evalTargeterStickPos(aimingPoint);
427 CVector startPoint = CVector::Null;
428 if (UserBoneID != 0xff && parent.skeleton())
430 const UBone bone = parent.skeleton()->getBone(UserBoneID);
431 parent.forceEvalAnim();
432 parent.skeleton()->forceComputeBone(UserBoneID);
433 startPoint = bone.getLastWorldMatrixComputed().getPos();
435 CMatrix rayMat;
436 CVector ray = aimingPoint - startPoint;
437 CVector I = ray.normed();
438 CVector K = (CVector::K - (I * CVector::K) * I).normed();
439 CVector J = K ^ I;
440 if (AniFX)
442 I *= ray.norm() / AniFX->Sheet->RayRefLength;
444 rayMat.setRot(I, J, K);
445 rayMat.setPos(startPoint + 0.5f * AniFX->Sheet->RayRefLength * I);
446 FX.setMatrix(rayMat);
447 // don't clusterize ray, because it can be quite large
448 FX.setForceClipRoot(true);