// 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); } } } }