447 lines
14 KiB
C++
447 lines
14 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#include "stdpch.h"
|
|
#include "nel/misc/matrix.h"
|
|
#include "nel/3d/u_scene.h"
|
|
#include "nel/3d/u_transform.h"
|
|
#include "nel/3d/u_skeleton.h"
|
|
#include "nel/3d/u_bone.h"
|
|
#include "attached_fx.h"
|
|
#include "character_cl.h"
|
|
#include "user_entity.h"
|
|
#include "entities.h"
|
|
#include "time_client.h"
|
|
#include "fx_manager.h"
|
|
|
|
extern NL3D::UScene *Scene;
|
|
|
|
|
|
using namespace NLMISC;
|
|
using namespace NL3D;
|
|
|
|
extern CUserEntity *UserEntity;
|
|
|
|
// *************************************************************************************
|
|
CAttachedFX::CAttachedFX()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
// *************************************************************************************
|
|
CAttachedFX::~CAttachedFX()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
// ***********************************************************************************************************************
|
|
void CAttachedFX::clear()
|
|
{
|
|
if (!FX.empty())
|
|
{
|
|
if (StickMode == CFXStickMode::SpawnPermanent)
|
|
{
|
|
FXMngr.addFX(FX, 100.f);
|
|
}
|
|
else
|
|
{
|
|
Scene->deleteInstance(FX);
|
|
}
|
|
}
|
|
FX = NULL;
|
|
AniFX = NULL;
|
|
TimeOutDate = FX_MANAGER_DEFAULT_TIMEOUT;
|
|
StickMode = CFXStickMode::Follow;
|
|
MaxAnimCount = 0;
|
|
UserBoneID = ~0;
|
|
TargeterUserBoneID = 0xff;
|
|
}
|
|
|
|
// ***********************************************************************************************************************
|
|
void CAttachedFX::create(CCharacterCL &parent,
|
|
const CBuildInfo &buildInfo,
|
|
const CTargeterInfo &targeterInfo
|
|
)
|
|
{
|
|
clear();
|
|
if (!buildInfo.Sheet) return;
|
|
UParticleSystemInstance instance = buildInfo.Sheet->createMatchingInstance();
|
|
// 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)
|
|
if (instance.empty()) return;
|
|
create(parent, instance, buildInfo, targeterInfo);
|
|
}
|
|
|
|
// ***********************************************************************************************************************
|
|
void CAttachedFX::create(CCharacterCL &parent,
|
|
NL3D::UParticleSystemInstance instance,
|
|
const CBuildInfo &buildInfo,
|
|
const CTargeterInfo &targeterInfo
|
|
)
|
|
{
|
|
nlassert(buildInfo.Sheet);
|
|
TimeOutDate = buildInfo.TimeOut;
|
|
const CFXStickMode *stickMode = buildInfo.StickMode ? buildInfo.StickMode : &buildInfo.Sheet->Sheet->StickMode;
|
|
if (!instance.empty())
|
|
{
|
|
instance.setClusterSystem(parent.getClusterSystem());
|
|
instance.setTransformMode(NL3D::UTransformable::DirectMatrix);
|
|
instance.show();
|
|
}
|
|
FX = instance;
|
|
AniFX = buildInfo.Sheet;
|
|
StickMode = stickMode->Mode;
|
|
SpawnTime = TimeInSec;
|
|
MaxAnimCount = buildInfo.MaxNumAnimCount;
|
|
TargeterInfo = targeterInfo;
|
|
switch(stickMode->Mode)
|
|
{
|
|
case CFXStickMode::StaticMatrix:
|
|
{
|
|
const CMatrix &staticMatrix = buildInfo.StaticMatrix ? *buildInfo.StaticMatrix : NLMISC::CMatrix::Identity;
|
|
instance.setMatrix(staticMatrix);
|
|
SpawnPos = staticMatrix.getPos();
|
|
}
|
|
break;
|
|
case CFXStickMode::Spawn:
|
|
case CFXStickMode::SpawnPermanent:
|
|
{
|
|
parent.alignFX(instance, parent.getScalePos());
|
|
SpawnPos = parent.pos() + buildInfo.StickOffset;
|
|
if (stickMode->Mode == CFXStickMode::SpawnPermanent)
|
|
{
|
|
instance.forceInstanciate();
|
|
}
|
|
}
|
|
break;
|
|
case CFXStickMode::UserBone:
|
|
{
|
|
if (parent.skeleton())
|
|
{
|
|
sint boneID = parent.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(stickMode->UserBoneName));
|
|
UserBoneID = boneID;
|
|
if (boneID != -1)
|
|
{
|
|
parent.skeleton()->stickObjectEx(instance, boneID, true);
|
|
NLMISC::CMatrix mat = instance.getMatrix();
|
|
mat.scale(buildInfo.Sheet->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
|
|
mat.setPos(buildInfo.StickOffset);
|
|
if (!instance.empty()) instance.setMatrix(mat);
|
|
SpawnPos = buildInfo.StickOffset;
|
|
// if parent is hidden, then force to compute the bone at least once
|
|
if (parent.skeleton()->getVisibility() == NL3D::UTransform::Hide)
|
|
{
|
|
parent.forceEvalAnim();
|
|
// force to compute fx at least once
|
|
parent.skeleton()->forceComputeBone(UserBoneID);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// bone not found or no skeleton
|
|
if (!parent.instance().empty())
|
|
{
|
|
instance.parent(parent.instance());
|
|
SpawnPos = buildInfo.StickOffset;
|
|
}
|
|
else
|
|
{
|
|
// just spawn at position of entity
|
|
SpawnPos = parent.pos() + buildInfo.StickOffset;
|
|
}
|
|
}
|
|
break;
|
|
case CFXStickMode::UserBoneOrientedTowardTargeter:
|
|
if (parent.skeleton())
|
|
{
|
|
UserBoneID = parent.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(stickMode->UserBoneName));
|
|
if (UserBoneID == 0xff)
|
|
{
|
|
nlwarning("Bad bone name : %s", NLMISC::CStringMapper::unmap(stickMode->UserBoneName).c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UserBoneID = 0xff;
|
|
}
|
|
SpawnPos = buildInfo.StickOffset;
|
|
update(parent, CMatrix::Identity);
|
|
break;
|
|
case CFXStickMode::UserBoneRay:
|
|
if (parent.skeleton())
|
|
{
|
|
UserBoneID = parent.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(stickMode->UserBoneName));
|
|
if (UserBoneID == 0xff)
|
|
{
|
|
nlwarning("Bad bone name : %s", NLMISC::CStringMapper::unmap(stickMode->UserBoneName).c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UserBoneID = 0xff;
|
|
}
|
|
SpawnPos = buildInfo.StickOffset;
|
|
TargeterUserBoneID =0xff;
|
|
if (targeterInfo.StickMode.UserBoneName != 0)
|
|
{
|
|
CEntityCL *targeter = EntitiesMngr.entity(TargeterInfo.Slot);
|
|
if (targeter && targeter->skeleton())
|
|
{
|
|
TargeterUserBoneID = parent.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(TargeterInfo.StickMode.UserBoneName));
|
|
}
|
|
}
|
|
update(parent, CMatrix::Identity);
|
|
break;
|
|
case CFXStickMode::OrientedTowardTargeter:
|
|
SpawnPos = buildInfo.StickOffset;
|
|
update(parent, CMatrix::Identity);
|
|
break;
|
|
default: // -> stick fx in 'Follow' mode
|
|
parent.alignFX(instance, parent.getScalePos());
|
|
SpawnPos = buildInfo.StickOffset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// ***********************************************************************************************************************
|
|
void CAttachedFX::evalTargeterStickPos(NLMISC::CVector &dest) const
|
|
{
|
|
CEntityCL *targeter = EntitiesMngr.entity(TargeterInfo.Slot);
|
|
if (!targeter)
|
|
{
|
|
dest = TargeterInfo.DefaultPos;
|
|
return;
|
|
}
|
|
switch(TargeterInfo.StickMode.Mode)
|
|
{
|
|
case CFXStickMode::Spawn:
|
|
case CFXStickMode::SpawnPermanent:
|
|
case CFXStickMode::Follow:
|
|
case CFXStickMode::FollowNoRotation:
|
|
case CFXStickMode::OrientedTowardTargeter:
|
|
dest = targeter->pos();
|
|
break;
|
|
case CFXStickMode::StaticMatrix:
|
|
nlwarning("Not implemented"); // this case is not used for now
|
|
dest = targeter->pos();
|
|
break;
|
|
case CFXStickMode::UserBoneOrientedTowardTargeter:
|
|
case CFXStickMode::UserBoneRay:
|
|
case CFXStickMode::UserBone:
|
|
if (targeter->skeleton() && TargeterUserBoneID != 0xff)
|
|
{
|
|
const UBone bone = targeter->skeleton()->getBone(TargeterUserBoneID);
|
|
targeter->forceEvalAnim();
|
|
targeter->skeleton()->forceComputeBone(TargeterUserBoneID);
|
|
dest = bone.getLastWorldMatrixComputed() * TargeterInfo.StickOffset;
|
|
}
|
|
else
|
|
{
|
|
dest = TargeterInfo.DefaultPos;
|
|
}
|
|
break;
|
|
};
|
|
}
|
|
|
|
// ***********************************************************************************************************************
|
|
void CAttachedFX::update(CCharacterCL &parent, const NLMISC::CMatrix &alignMatrix)
|
|
{
|
|
if (AniFX && !FX.empty())
|
|
{
|
|
NLMISC::CVector trackPos;
|
|
// see if fx has a track applied on it
|
|
if (AniFX->PosTrack)
|
|
{
|
|
// eval current pos
|
|
AniFX->PosTrack->interpolate((float) (TimeInSec - SpawnTime), trackPos);
|
|
}
|
|
else
|
|
{
|
|
trackPos.set(0.f, 0.f, 0.f);
|
|
}
|
|
// apply pos depending on mode
|
|
switch(StickMode)
|
|
{
|
|
case CFXStickMode::UserBone:
|
|
{
|
|
FX.setClusterSystem(parent.getClusterSystem());
|
|
if (!AniFX->PosTrack)
|
|
{
|
|
// no track for fx,
|
|
if (parent.skeleton() && UserBoneID != 0xff)
|
|
{
|
|
if (parent.skeleton()->getVisibility() == UTransform::Hide)
|
|
{
|
|
// if user no visible, force to compute the bone.
|
|
parent.forceEvalAnim();
|
|
parent.skeleton()->forceComputeBone(UserBoneID);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (parent.skeleton())
|
|
{
|
|
if (UserBoneID != 0xff)
|
|
{
|
|
CMatrix mat = FX.getMatrix();
|
|
mat.setPos(trackPos + SpawnPos);
|
|
mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
|
|
FX.setMatrix(mat);
|
|
if (&parent == UserEntity && UserEntity->skeleton())
|
|
{
|
|
if (UserEntity->skeleton()->getVisibility() == UTransform::Hide)
|
|
{
|
|
// if user no visible, force to compute the bone.
|
|
parent.forceEvalAnim();
|
|
UserEntity->skeleton()->forceComputeBone(UserBoneID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// no skeleton or bone not found
|
|
if (!parent.instance().empty())
|
|
{
|
|
CMatrix mat = FX.getMatrix();
|
|
mat.setPos(trackPos + SpawnPos);
|
|
mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
|
|
FX.setMatrix(mat);
|
|
}
|
|
else
|
|
{
|
|
// no skeleton, no instance
|
|
CMatrix mat = FX.getMatrix();
|
|
mat.setPos(trackPos + SpawnPos + parent.pos());
|
|
mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
|
|
FX.setMatrix(mat);
|
|
}
|
|
}
|
|
break;
|
|
case CFXStickMode::Follow:
|
|
// just change local pos
|
|
parent.alignFX(FX, alignMatrix, AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f, trackPos + SpawnPos);
|
|
break;
|
|
case CFXStickMode::FollowNoRotation:
|
|
{
|
|
// just update the pos
|
|
CMatrix mat = FX.getMatrix();
|
|
mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
|
|
mat.setPos(trackPos + parent.pos().asVector());
|
|
FX.setMatrix(mat);
|
|
FX.setClusterSystem(parent.getClusterSystem());
|
|
}
|
|
break;
|
|
case CFXStickMode::StaticObjectCastRay:
|
|
{
|
|
// if not animated need no updates
|
|
if (!AniFX->PosTrack) return;
|
|
nlwarning("Not implemented");
|
|
}
|
|
break;
|
|
case CFXStickMode::Spawn:
|
|
case CFXStickMode::SpawnPermanent:
|
|
{
|
|
if (!AniFX->PosTrack) return;
|
|
// put in local basis and offset spawn pos
|
|
CMatrix mat = FX.getMatrix();
|
|
mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
|
|
mat.setPos(SpawnPos + FX.getMatrix().mulVector(trackPos));
|
|
FX.setMatrix(mat);
|
|
// Do not update the cluster system because fx stays in place
|
|
}
|
|
break;
|
|
case CFXStickMode::OrientedTowardTargeter:
|
|
{
|
|
CEntityCL *targeter = EntitiesMngr.entity(TargeterInfo.Slot);
|
|
if (targeter)
|
|
{
|
|
CVectorD orientD = parent.pos() - targeter->pos();
|
|
CVector J((float) orientD.x, (float) orientD.y, 0.f); // project on XY plane
|
|
J.normalize();
|
|
CVector I = J ^ CVector::K;
|
|
CMatrix mat;
|
|
mat.setRot(I, J, CVector::K);
|
|
mat.setScale(AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f);
|
|
mat.setPos(trackPos + parent.pos().asVector());
|
|
FX.setMatrix(mat);
|
|
FX.setClusterSystem(parent.getClusterSystem());
|
|
}
|
|
}
|
|
break;
|
|
case CFXStickMode::UserBoneOrientedTowardTargeter:
|
|
{
|
|
CEntityCL *targeter = EntitiesMngr.entity(TargeterInfo.Slot);
|
|
if (targeter)
|
|
{
|
|
CVector orientD;
|
|
CMatrix orientMat;
|
|
if (UserBoneID == 0xff || !parent.skeleton())
|
|
{
|
|
// bone not found -> use parent position instead
|
|
orientD = parent.pos() - targeter->pos();
|
|
orientMat.setPos(trackPos + parent.pos());
|
|
}
|
|
else
|
|
{
|
|
const UBone bone = parent.skeleton()->getBone(UserBoneID);
|
|
parent.forceEvalAnim();
|
|
parent.skeleton()->forceComputeBone(UserBoneID);
|
|
const CMatrix &wm = bone.getLastWorldMatrixComputed();
|
|
// compute orientation toward targeter, and build a matrix from it
|
|
orientD = wm.getPos() - targeter->pos().asVector();
|
|
orientMat.setPos(trackPos + wm.getPos());
|
|
}
|
|
CVector J(orientD.x, orientD.y, 0.f); // project on XY plane
|
|
J.normalize();
|
|
CVector I = J ^ CVector::K;
|
|
float scale = AniFX->Sheet->ScaleFX ? parent.getScaleRef() : 1.f;
|
|
orientMat.setRot(scale * I, scale * J, scale * CVector::K, true);
|
|
FX.setMatrix(orientMat);
|
|
FX.setClusterSystem(parent.getClusterSystem());
|
|
}
|
|
}
|
|
break;
|
|
case CFXStickMode::UserBoneRay:
|
|
{
|
|
CVector aimingPoint;
|
|
evalTargeterStickPos(aimingPoint);
|
|
CVector startPoint = CVector::Null;
|
|
if (UserBoneID != 0xff && parent.skeleton())
|
|
{
|
|
const UBone bone = parent.skeleton()->getBone(UserBoneID);
|
|
parent.forceEvalAnim();
|
|
parent.skeleton()->forceComputeBone(UserBoneID);
|
|
startPoint = bone.getLastWorldMatrixComputed().getPos();
|
|
}
|
|
CMatrix rayMat;
|
|
CVector ray = aimingPoint - startPoint;
|
|
CVector I = ray.normed();
|
|
CVector K = (CVector::K - (I * CVector::K) * I).normed();
|
|
CVector J = K ^ I;
|
|
if (AniFX)
|
|
{
|
|
I *= ray.norm() / AniFX->Sheet->RayRefLength;
|
|
}
|
|
rayMat.setRot(I, J, K);
|
|
rayMat.setPos(startPoint + 0.5f * AniFX->Sheet->RayRefLength * I);
|
|
FX.setMatrix(rayMat);
|
|
// don't clusterize ray, because it can be quite large
|
|
FX.setForceClipRoot(true);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|