/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "hsTypes.h" #include "plLineFollowMod.h" #include "plStereizer.h" #include "../plInterp/plAnimPath.h" #include "hsResMgr.h" #include "../pnMessage/plRefMsg.h" #include "../pnSceneObject/plSceneObject.h" #include "../pnSceneObject/plCoordinateInterface.h" #include "../pnSceneObject/plDrawInterface.h" #include "plgDispatch.h" #include "../plMessage/plListenerMsg.h" #include "../plMessage/plRenderMsg.h" #include "../pnMessage/plTimeMsg.h" #include "hsBounds.h" #include "plPipeline.h" #include "hsFastMath.h" #include "../pnMessage/plPlayerPageMsg.h" #include "../pnNetCommon/plNetApp.h" #include "../plNetClient/plNetClientMgr.h" #include "hsTimer.h" plLineFollowMod::plLineFollowMod() : fPath(nil), fPathParent(nil), fRefObj(nil), fFollowMode(kFollowListener), fFollowFlags(kNone), fOffset(0), fOffsetClamp(0), fSpeedClamp(0) { fSearchPos.Set(0,0,0); } plLineFollowMod::~plLineFollowMod() { delete fPath; } void plLineFollowMod::SetSpeedClamp(hsScalar fps) { fSpeedClamp = fps; if( fSpeedClamp > 0 ) { fFollowFlags |= kSpeedClamp; } else { fFollowFlags &= ~kSpeedClamp; } } void plLineFollowMod::SetOffsetFeet(hsScalar f) { fOffset = f; if( fOffset != 0 ) { fFollowFlags &= ~kOffsetAng; fFollowFlags |= kOffsetFeet; } else { fFollowFlags &= ~kOffset; } } void plLineFollowMod::SetForceToLine(hsBool on) { if( on ) fFollowFlags |= kForceToLine; else fFollowFlags &= ~kForceToLine; } void plLineFollowMod::SetOffsetDegrees(hsScalar f) { fOffset = hsScalarDegToRad(f); if( fOffset != 0 ) { fFollowFlags &= ~kOffsetFeet; fFollowFlags |= kOffsetAng; fTanOffset = tanf(f); } else { fFollowFlags &= ~kOffset; } } void plLineFollowMod::SetOffsetClamp(hsScalar f) { fOffsetClamp = f; if( fOffsetClamp > 0 ) { fFollowFlags |= kOffsetClamp; } else { fFollowFlags &= ~kOffsetClamp; } } void plLineFollowMod::SetPath(plAnimPath* path) { delete fPath; fPath = path; } void plLineFollowMod::Read(hsStream* stream, hsResMgr* mgr) { plMultiModifier::Read(stream, mgr); fPath = plAnimPath::ConvertNoRef(mgr->ReadCreatable(stream)); mgr->ReadKeyNotifyMe(stream, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefParent), plRefFlags::kPassiveRef); mgr->ReadKeyNotifyMe(stream, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefObject), plRefFlags::kPassiveRef); int n = stream->ReadSwap32(); while(n--) { mgr->ReadKeyNotifyMe(stream, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefStereizer), plRefFlags::kPassiveRef); } UInt32 f = stream->ReadSwap32(); SetFollowMode(FollowMode(f & 0xffff)); fFollowFlags = (UInt16)((f >> 16) & 0xffff); if( fFollowFlags & kOffset ) { fOffset = stream->ReadSwapScalar(); } if( fFollowFlags & kOffsetAng ) { fTanOffset = tanf(fOffset); } if( fFollowFlags & kOffsetClamp ) { fOffsetClamp = stream->ReadSwapScalar(); } if( fFollowFlags & kSpeedClamp ) { fSpeedClamp = stream->ReadSwapScalar(); } } void plLineFollowMod::Write(hsStream* stream, hsResMgr* mgr) { plMultiModifier::Write(stream, mgr); mgr->WriteCreatable(stream, fPath); mgr->WriteKey(stream, fPathParent); mgr->WriteKey(stream, fRefObj); stream->WriteSwap32(fStereizers.GetCount()); int i; for( i = 0; i < fStereizers.GetCount(); i++ ) mgr->WriteKey(stream, fStereizers[i]->GetKey()); UInt32 f = UInt32(fFollowMode) | (UInt32(fFollowFlags) << 16); stream->WriteSwap32(f); if( fFollowFlags & kOffset ) stream->WriteSwapScalar(fOffset); if( fFollowFlags & kOffsetClamp ) stream->WriteSwapScalar(fOffsetClamp); if( fFollowFlags & kSpeedClamp ) stream->WriteSwapScalar(fSpeedClamp); } #include "plProfile.h" plProfile_CreateTimer("LineFollow", "RenderSetup", LineFollow); hsBool plLineFollowMod::MsgReceive(plMessage* msg) { plGenRefMsg* refMsg = plGenRefMsg::ConvertNoRef(msg); if( refMsg ) { if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) ) { plSceneObject* obj = plSceneObject::ConvertNoRef(refMsg->GetRef()); if( kRefParent == refMsg->fType ) fPathParent = obj; else if( kRefObject == refMsg->fType ) fRefObj = obj; else if( kRefStereizer == refMsg->fType ) { plStereizer* ster = plStereizer::ConvertNoRef(refMsg->GetRef()); int idx = fStereizers.Find(ster); if( idx == fStereizers.kMissingIndex ) fStereizers.Append(ster); } } else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) { if( kRefParent == refMsg->fType ) fPathParent = nil; else if( kRefObject == refMsg->fType ) fRefObj = nil; else if( kRefStereizer == refMsg->fType ) { plStereizer* ster = (plStereizer*)(refMsg->GetRef()); int idx = fStereizers.Find(ster); if( idx != fStereizers.kMissingIndex ) fStereizers.Remove(idx); } } return true; } plRenderMsg* rend = plRenderMsg::ConvertNoRef(msg); if( rend ) { plProfile_BeginLap(LineFollow, this->GetKey()->GetUoid().GetObjectName()); hsPoint3 oldPos = fSearchPos; fSearchPos = rend->Pipeline()->GetViewPositionWorld(); ICheckForPop(oldPos, fSearchPos); plProfile_EndLap(LineFollow, this->GetKey()->GetUoid().GetObjectName()); return true; } plListenerMsg* list = plListenerMsg::ConvertNoRef(msg); if( list ) { hsPoint3 oldPos = fSearchPos; fSearchPos = list->GetPosition(); ICheckForPop(oldPos, fSearchPos); ISetupStereizers(list); return true; } plPlayerPageMsg* pPMsg = plPlayerPageMsg::ConvertNoRef(msg); if (pPMsg) { if (pPMsg->fPlayer == plNetClientMgr::GetInstance()->GetLocalPlayerKey() && !pPMsg->fUnload) { fRefObj = (plSceneObject*)pPMsg->fPlayer->GetObjectPtr(); } return true; } return plMultiModifier::MsgReceive(msg); } void plLineFollowMod::SetFollowMode(FollowMode f) { IUnRegister(); fFollowMode = f; IRegister(); plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey()); } void plLineFollowMod::IUnRegister() { switch( fFollowMode ) { case kFollowObject: break; case kFollowListener: plgDispatch::Dispatch()->UnRegisterForExactType(plListenerMsg::Index(), GetKey()); break; case kFollowCamera: plgDispatch::Dispatch()->UnRegisterForExactType(plRenderMsg::Index(), GetKey()); break; case kFollowLocalAvatar: plgDispatch::Dispatch()->UnRegisterForExactType(plPlayerPageMsg::Index(), GetKey()); break; } } void plLineFollowMod::IRegister() { switch( fFollowMode ) { case kFollowObject: break; case kFollowListener: plgDispatch::Dispatch()->RegisterForExactType(plListenerMsg::Index(), GetKey()); break; case kFollowCamera: plgDispatch::Dispatch()->RegisterForExactType(plRenderMsg::Index(), GetKey()); break; case kFollowLocalAvatar: { if (plNetClientApp::GetInstance() && plNetClientApp::GetInstance()->GetLocalPlayer()) fRefObj = ((plSceneObject*)plNetClientApp::GetInstance()->GetLocalPlayer()); plgDispatch::Dispatch()->RegisterForExactType(plPlayerPageMsg::Index(), GetKey()); break; } } } hsBool plLineFollowMod::IEval(double secs, hsScalar del, UInt32 dirty) { if( !fPath ) return false; ISetPathTransform(); if( !IGetSearchPos() ) return false; hsMatrix44 tgtXfm; IGetTargetTransform(fSearchPos, tgtXfm); if( fFollowFlags & kOffset ) IOffsetTargetTransform(tgtXfm); int i; for( i = 0; i < GetNumTargets(); i++ ) { ISetTargetTransform(i, tgtXfm); } return true; } hsBool plLineFollowMod::IOffsetTargetTransform(hsMatrix44& tgtXfm) { hsPoint3 tgtPos = tgtXfm.GetTranslate(); hsVector3 tgt2src(&fSearchPos, &tgtPos); hsScalar t2sLen = tgt2src.Magnitude(); hsFastMath::NormalizeAppr(tgt2src); hsVector3 out; out.Set(-tgt2src.fY, tgt2src.fX, 0); // (0,0,1) X (tgt2src) if( fFollowFlags & kOffsetAng ) { hsScalar del = t2sLen * fTanOffset; if( fFollowFlags & kOffsetClamp ) { if( del > fOffsetClamp ) del = fOffsetClamp; else if( del < -fOffsetClamp ) del = -fOffsetClamp; } out *= del; } else if( fFollowFlags & kOffsetFeet ) { out *= fOffset; } else out.Set(0,0,0); if( fFollowFlags & kForceToLine ) { hsPoint3 newSearch = tgtPos; newSearch += out; IGetTargetTransform(newSearch, tgtXfm); } else { tgtXfm.fMap[0][3] += out[0]; tgtXfm.fMap[1][3] += out[1]; tgtXfm.fMap[2][3] += out[2]; } return true; } hsBool plLineFollowMod::IGetTargetTransform(hsPoint3& searchPos, hsMatrix44& tgtXfm) { hsScalar t = fPath->GetExtremePoint(searchPos); if( fFollowFlags & kFullMatrix ) { fPath->SetCurTime(t, plAnimPath::kNone); fPath->GetMatrix44(&tgtXfm); } else { fPath->SetCurTime(t, plAnimPath::kCalcPosOnly); hsPoint3 pos; fPath->GetPosition(&pos); tgtXfm.MakeTranslateMat((hsVector3*)&pos); } return true; } void plLineFollowMod::ISetPathTransform() { if( fPathParent && fPathParent->GetCoordinateInterface() ) { hsMatrix44 l2w = fPathParent->GetCoordinateInterface()->GetLocalToWorld(); hsMatrix44 w2l = fPathParent->GetCoordinateInterface()->GetWorldToLocal(); fPath->SetTransform(l2w, w2l); } } void plLineFollowMod::ICheckForPop(const hsPoint3& oldPos, const hsPoint3& newPos) { hsVector3 del(&oldPos, &newPos); hsScalar elapsed = hsTimer::GetDelSysSeconds(); hsScalar speedSq = 0.f; if (elapsed > 0.f) speedSq = del.MagnitudeSquared() / elapsed; const hsScalar kMaxSpeedSq = 30.f * 30.f; // (feet per sec)^2 if( speedSq > kMaxSpeedSq ) fFollowFlags |= kSearchPosPop; else fFollowFlags &= ~kSearchPosPop; } hsBool plLineFollowMod::IGetSearchPos() { hsPoint3 oldPos = fSearchPos; if( kFollowObject == fFollowMode ) { if( !fRefObj ) return false; if( fRefObj->GetCoordinateInterface() ) { fSearchPos = fRefObj->GetCoordinateInterface()->GetWorldPos(); ICheckForPop(oldPos, fSearchPos); return true; } else if( fRefObj->GetDrawInterface() ) { fSearchPos = fRefObj->GetDrawInterface()->GetWorldBounds().GetCenter(); ICheckForPop(oldPos, fSearchPos); return true; } return false; } else if (fFollowMode == kFollowLocalAvatar) { if (!fRefObj) return false; if( fRefObj->GetCoordinateInterface() ) { fSearchPos = fRefObj->GetCoordinateInterface()->GetWorldPos(); ICheckForPop(oldPos, fSearchPos); return true; } else if( fRefObj->GetDrawInterface() ) { fSearchPos = fRefObj->GetDrawInterface()->GetWorldBounds().GetCenter(); ICheckForPop(oldPos, fSearchPos); return true; } return false; } return true; } hsMatrix44 plLineFollowMod::IInterpMatrices(const hsMatrix44& m0, const hsMatrix44& m1, hsScalar parm) { hsMatrix44 retVal; int i, j; for( i = 0; i < 3; i++ ) { for( j = 0; j < 4; j++ ) { retVal.fMap[i][j] = m0.fMap[i][j] * (1.f - parm) + m1.fMap[i][j] * parm; } } retVal.fMap[3][0] = retVal.fMap[3][1] = retVal.fMap[3][2] = 0; retVal.fMap[3][3] = 1.f; retVal.NotIdentity(); return retVal; } hsMatrix44 plLineFollowMod::ISpeedClamp(plCoordinateInterface* ci, const hsMatrix44& unclTgtXfm) { // If our search position has popped, or delsysseconds is zero, just return as is. if( (fFollowFlags & kSearchPosPop) || !(hsTimer::GetDelSysSeconds() > 0) ) return unclTgtXfm; const hsMatrix44 currL2W = ci->GetLocalToWorld(); const hsPoint3 oldPos = currL2W.GetTranslate(); const hsPoint3 newPos = unclTgtXfm.GetTranslate(); const hsVector3 del(&newPos, &oldPos); hsScalar elapsed = hsTimer::GetDelSysSeconds(); hsScalar speed = 0.f; if (elapsed > 0.f) speed = del.Magnitude() / elapsed; if( speed > fSpeedClamp ) { hsScalar parm = fSpeedClamp / speed; hsMatrix44 clTgtXfm = IInterpMatrices(currL2W, unclTgtXfm, parm); return clTgtXfm; } return unclTgtXfm; } void plLineFollowMod::ISetTargetTransform(int iTarg, const hsMatrix44& unclTgtXfm) { plCoordinateInterface* ci = IGetTargetCoordinateInterface(iTarg); if( ci ) { hsMatrix44 tgtXfm = fFollowFlags & kSpeedClamp ? ISpeedClamp(ci, unclTgtXfm) : unclTgtXfm; if( fFollowFlags & kFullMatrix ) { // This branch currently never gets taken. If it ever does, // we should probably optimize out this GetInverse() (depending // on how often it gets taken). const hsMatrix44& l2w = tgtXfm; hsMatrix44 w2l; l2w.GetInverse(&w2l); ci->SetTransform(l2w, w2l); } else { hsMatrix44 l2w = ci->GetLocalToWorld(); hsMatrix44 w2l = ci->GetWorldToLocal(); hsPoint3 pos = tgtXfm.GetTranslate(); hsPoint3 oldPos = l2w.GetTranslate(); l2w.SetTranslate(&pos); hsMatrix44 xlate; xlate.Reset(); xlate.SetTranslate(&oldPos); w2l = w2l * xlate; xlate.SetTranslate(&-pos); w2l = w2l * xlate; ci->SetTransform(l2w, w2l); } hsPoint3 newPos = tgtXfm.GetTranslate(); int i; for( i = 0; i < fStereizers.GetCount(); i++ ) { if( fStereizers[i] ) { fStereizers[i]->SetWorldInitPos(newPos); fStereizers[i]->Stereize(); } } } } void plLineFollowMod::ISetupStereizers(const plListenerMsg* listMsg) { int i; for( i = 0; i < fStereizers.GetCount(); i++ ) { if( fStereizers[i] ) fStereizers[i]->SetFromListenerMsg(listMsg); } } void plLineFollowMod::AddTarget(plSceneObject* so) { plMultiModifier::AddTarget(so); if( so ) plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey()); } void plLineFollowMod::RemoveTarget(plSceneObject* so) { plMultiModifier::RemoveTarget(so); } void plLineFollowMod::AddStereizer(const plKey& key) { hsgResMgr::ResMgr()->SendRef(plKey(key), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefStereizer), plRefFlags::kPassiveRef); } void plLineFollowMod::RemoveStereizer(const plKey& key) { hsgResMgr::ResMgr()->SendRef(plKey(key), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRemove, 0, kRefStereizer), plRefFlags::kPassiveRef); } // derived version of this class for rail cameras // the difference is the rail camera just calculates // the desired position but does not move the target to // it. plRailCameraMod::plRailCameraMod() : fCurrentTime(0.0f), fTargetTime(0.0f), fFarthest(false) { plLineFollowMod::plLineFollowMod(); fGoal.Set(0,0,0); } plRailCameraMod::~plRailCameraMod() { } hsBool plRailCameraMod::IGetTargetTransform(hsPoint3& searchPos, hsMatrix44& tgtXfm) { if (fPath->GetFarthest()) { fFarthest = true; fPath->SetFarthest(false); } fTargetTime = fPath->GetExtremePoint(searchPos); fPath->SetCurTime(fTargetTime, plAnimPath::kCalcPosOnly); hsPoint3 pos; fPath->GetPosition(&pos); tgtXfm.MakeTranslateMat((hsVector3*)&pos); return true; } hsPoint3 plRailCameraMod::GetGoal(double secs, hsScalar speed) { hsScalar delTime; int dir; if (fTargetTime == fCurrentTime) return fGoal; if (fTargetTime > fCurrentTime) { dir = 1; delTime = fTargetTime - fCurrentTime; } else { dir = -1; delTime = fCurrentTime - fTargetTime; } if (fPath->GetWrap() && delTime > fPath->GetLength() * 0.5f) dir *= -1; if (delTime <= (secs * speed)) fCurrentTime = fTargetTime; else fCurrentTime += (hsScalar)((secs * speed) * dir); if (fPath->GetWrap()) { if (fCurrentTime > fPath->GetLength()) fCurrentTime = (fCurrentTime - fPath->GetLength()); else if (fCurrentTime < 0.0f) fCurrentTime = fPath->GetLength() - fCurrentTime; } if (fFarthest) fPath->SetCurTime((fPath->GetLength() - fCurrentTime), plAnimPath::kCalcPosOnly); else fPath->SetCurTime(fCurrentTime, plAnimPath::kCalcPosOnly); fPath->GetPosition(&fGoal); fPath->SetCurTime(fTargetTime, plAnimPath::kCalcPosOnly); return fGoal; }