|
|
|
/*==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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Additional permissions under GNU GPL version 3 section 7
|
|
|
|
|
|
|
|
If you modify this Program, or any covered work, by linking or
|
|
|
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
|
|
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
|
|
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
|
|
|
(or a modified version of those libraries),
|
|
|
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
|
|
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
|
|
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
|
|
|
licensors of this Program grant you additional
|
|
|
|
permission to convey the resulting work. Corresponding Source for a
|
|
|
|
non-source form of such a combination shall include the source code for
|
|
|
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
|
|
|
work.
|
|
|
|
|
|
|
|
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==*/
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// INCLUDES
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// singular
|
|
|
|
#include "plAvTaskSeek.h"
|
|
|
|
|
|
|
|
// local
|
|
|
|
#include "plAvBrainHuman.h"
|
|
|
|
#include "plAGAnim.h"
|
|
|
|
#include "plArmatureMod.h"
|
|
|
|
#include "plAvatarMgr.h"
|
|
|
|
#include "plAvCallbackAction.h"
|
|
|
|
|
|
|
|
// other
|
|
|
|
#include "plMessage/plAvatarMsg.h"
|
|
|
|
#include "pnMessage/plCameraMsg.h"
|
|
|
|
#include "pnInputCore/plControlEventCodes.h"
|
|
|
|
#include "plPipeline/plDebugText.h"
|
|
|
|
#include "plStatusLog/plStatusLog.h"
|
|
|
|
#include "pnSceneObject/plCoordinateInterface.h"
|
|
|
|
#include "hsTimer.h"
|
|
|
|
#include "plgDispatch.h"
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// PROTOTYPES
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
float QuatAngleDiff(const hsQuat &a, const hsQuat &b);
|
|
|
|
void MakeMatrixUpright(hsMatrix44 &mat);
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// DEFINES
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
#define kSeekTimeout 5.0f
|
|
|
|
#define kRotSpeed 1.0f // normal rotation speed is 1.0 radians per second
|
|
|
|
#define kFloatSpeed 3.0f
|
|
|
|
#define kMaxRadiansPerSecond 1.5
|
|
|
|
|
|
|
|
#define kDefaultShuffleRange 0.5f
|
|
|
|
#define kDefaultMaxSidleRange 4.0f
|
|
|
|
#define kDefaultMaxSidleAngle 0.2f
|
|
|
|
|
|
|
|
hsBool plAvTaskSeek::fLogProcess = false;
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// IMPLEMENTATION
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void plAvTaskSeek::IInitDefaults()
|
|
|
|
{
|
|
|
|
fSeekObject = nil;
|
|
|
|
fMovingTarget = false;
|
|
|
|
fAlign = kAlignHandle;
|
|
|
|
fAnimName = nil;
|
|
|
|
fPosGoalHit = false;
|
|
|
|
fRotGoalHit = false;
|
|
|
|
fStillPositioning = true;
|
|
|
|
fStillRotating = true;
|
|
|
|
fShuffleRange = kDefaultShuffleRange;
|
|
|
|
fMaxSidleRange = kDefaultMaxSidleRange;
|
|
|
|
fMaxSidleAngle = kDefaultMaxSidleAngle;
|
|
|
|
fFlags = kSeekFlagForce3rdPersonOnStart;
|
|
|
|
fState = kSeekRunNormal;
|
|
|
|
fNotifyFinishedKey = nil;
|
|
|
|
}
|
|
|
|
// plAvTaskSeek ------------
|
|
|
|
// -------------
|
|
|
|
plAvTaskSeek::plAvTaskSeek() {}
|
|
|
|
|
|
|
|
plAvTaskSeek::plAvTaskSeek(plAvSeekMsg *msg)
|
|
|
|
{
|
|
|
|
IInitDefaults();
|
|
|
|
|
|
|
|
fAlign = msg->fAlignType;
|
|
|
|
fAnimName = msg->fAnimName;
|
|
|
|
|
|
|
|
plKey &target = msg->fSeekPoint;
|
|
|
|
if (target)
|
|
|
|
SetTarget(target);
|
|
|
|
else
|
|
|
|
SetTarget(msg->fTargetPos, msg->fTargetLookAt);
|
|
|
|
|
|
|
|
if (msg->UnForce3rdPersonOnFinish())
|
|
|
|
fFlags |= kSeekFlagUnForce3rdPersonOnFinish;
|
|
|
|
else
|
|
|
|
fFlags &= ~kSeekFlagUnForce3rdPersonOnFinish;
|
|
|
|
|
|
|
|
if (msg->Force3rdPersonOnStart())
|
|
|
|
fFlags |= kSeekFlagForce3rdPersonOnStart;
|
|
|
|
else
|
|
|
|
fFlags &= ~kSeekFlagForce3rdPersonOnStart;
|
|
|
|
|
|
|
|
if (msg->NoWarpOnTimeout())
|
|
|
|
fFlags |= kSeekFlagNoWarpOnTimeout;
|
|
|
|
else
|
|
|
|
fFlags &= ~kSeekFlagNoWarpOnTimeout;
|
|
|
|
|
|
|
|
if (msg->RotationOnly())
|
|
|
|
{
|
|
|
|
fFlags |= kSeekFlagRotationOnly;
|
|
|
|
fStillPositioning = false;
|
|
|
|
fPosGoalHit = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
fFlags &= ~kSeekFlagRotationOnly;
|
|
|
|
|
|
|
|
fNotifyFinishedKey = msg->fFinishKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
// plAvTaskSeek ------------------------
|
|
|
|
// -------------
|
|
|
|
plAvTaskSeek::plAvTaskSeek(plKey target)
|
|
|
|
{
|
|
|
|
IInitDefaults();
|
|
|
|
|
|
|
|
SetTarget(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
// plAvTaskSeek -------------------------------------------------------------------------------------------
|
|
|
|
// -------------
|
|
|
|
plAvTaskSeek::plAvTaskSeek(plKey target, plAvAlignment align, const char *animName, bool moving)
|
|
|
|
{
|
|
|
|
IInitDefaults();
|
|
|
|
|
|
|
|
fMovingTarget = moving;
|
|
|
|
fAlign = align;
|
|
|
|
fAnimName = animName;
|
|
|
|
|
|
|
|
SetTarget(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plAvTaskSeek::SetTarget(plKey target)
|
|
|
|
{
|
|
|
|
hsAssert(target, "Bad key to seek task");
|
|
|
|
if(target)
|
|
|
|
{
|
|
|
|
fSeekObject = plSceneObject::ConvertNoRef(target->ObjectIsLoaded());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fSeekObject = nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plAvTaskSeek::SetTarget(hsPoint3 &pos, hsPoint3 &lookAt)
|
|
|
|
{
|
|
|
|
fSeekPos = pos;
|
|
|
|
hsVector3 up(0.f, 0.f, 1.f);
|
|
|
|
float angle = atan2(lookAt.fY - pos.fY, lookAt.fX - pos.fX) + M_PI / 2;
|
|
|
|
fSeekRot.SetAngleAxis(angle, up);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start -----------------------------------------------------------------------------------------
|
|
|
|
// ------
|
|
|
|
hsBool plAvTaskSeek::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
|
|
|
|
{
|
|
|
|
plAvBrainHuman *huBrain = plAvBrainHuman::ConvertNoRef(brain);
|
|
|
|
hsAssert(huBrain, "Seek task only works on human brains");
|
|
|
|
|
|
|
|
plAvatarMgr::GetInstance()->GetLog()->AddLine("Starting SMART SEEK");
|
|
|
|
//controller needs to know we are seeking. prevents controller from interacting with exclusion regions
|
|
|
|
|
|
|
|
if (avatar->GetController() )
|
|
|
|
avatar->GetController()->SetSeek(true);
|
|
|
|
fStartTime = time;
|
|
|
|
if(huBrain)
|
|
|
|
{
|
|
|
|
avatar->SuspendInput(); // stop accepting input from the user, but queue any messages
|
|
|
|
// ...and save our current input state.
|
|
|
|
|
|
|
|
ILimitPlayersInput(avatar);
|
|
|
|
|
|
|
|
if (plAvOneShotTask::fForce3rdPerson && avatar->IsLocalAvatar() && (fFlags & plAvSeekMsg::kSeekFlagForce3rdPersonOnStart))
|
|
|
|
{
|
|
|
|
// create message
|
|
|
|
plCameraMsg* pMsg = new plCameraMsg;
|
|
|
|
pMsg->SetBCastFlag(plMessage::kBCastByExactType);
|
|
|
|
pMsg->SetBCastFlag(plMessage::kNetPropagate, false);
|
|
|
|
pMsg->SetCmd(plCameraMsg::kResponderSetThirdPerson);
|
|
|
|
plgDispatch::MsgSend( pMsg ); // whoosh... off it goes
|
|
|
|
}
|
|
|
|
|
|
|
|
huBrain->IdleOnly(); // Makes sure to kill jumps too. Just calling ClearInputFlags isn't enough
|
|
|
|
IUpdateObjective(avatar);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process -------------------------------------------------------------------------------------------
|
|
|
|
// --------
|
|
|
|
hsBool plAvTaskSeek::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
|
|
|
|
{
|
|
|
|
if (fState == kSeekAbort)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
plAvBrainHuman *uBrain = plAvBrainHuman::ConvertNoRef(brain);
|
|
|
|
if (uBrain)
|
|
|
|
{
|
|
|
|
if (fMovingTarget)
|
|
|
|
{
|
|
|
|
IUpdateObjective(avatar);
|
|
|
|
}
|
|
|
|
|
|
|
|
IAnalyze(avatar);
|
|
|
|
hsBool result = IMoveTowardsGoal(avatar, uBrain, time, elapsed);
|
|
|
|
if (fLogProcess)
|
|
|
|
DumpToAvatarLog(avatar);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finish ---------------------------------------------------------------------------------------
|
|
|
|
// -------
|
|
|
|
void plAvTaskSeek::Finish(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
|
|
|
|
{
|
|
|
|
plAvBrainHuman *huBrain = plAvBrainHuman::ConvertNoRef(brain);
|
|
|
|
|
|
|
|
if(huBrain)
|
|
|
|
{
|
|
|
|
// this will process any queued input messages so if the user pressed or released a key while we were busy, we'll note it now.
|
|
|
|
avatar->ResumeInput();
|
|
|
|
IUndoLimitPlayersInput(avatar);
|
|
|
|
|
|
|
|
if (plAvOneShotTask::fForce3rdPerson && avatar->IsLocalAvatar() && (fFlags & plAvSeekMsg::kSeekFlagUnForce3rdPersonOnFinish))
|
|
|
|
{
|
|
|
|
// create message
|
|
|
|
plCameraMsg* pMsg = new plCameraMsg;
|
|
|
|
pMsg->SetBCastFlag(plMessage::kBCastByExactType);
|
|
|
|
pMsg->SetBCastFlag(plMessage::kNetPropagate, false);
|
|
|
|
pMsg->SetCmd(plCameraMsg::kResponderUndoThirdPerson);
|
|
|
|
plgDispatch::MsgSend( pMsg ); // whoosh... off it goes
|
|
|
|
}
|
|
|
|
|
|
|
|
avatar->SynchIfLocal(hsTimer::GetSysSeconds(), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fNotifyFinishedKey)
|
|
|
|
{
|
|
|
|
plAvTaskSeekDoneMsg *msg = new plAvTaskSeekDoneMsg(avatar->GetKey(), fNotifyFinishedKey);
|
|
|
|
msg->fAborted = (fState == kSeekAbort);
|
|
|
|
msg->Send();
|
|
|
|
}
|
|
|
|
plAvatarMgr::GetInstance()->GetLog()->AddLine("Finished SMART SEEK");
|
|
|
|
//inform controller we are done seeking
|
|
|
|
if (avatar->GetController())
|
|
|
|
avatar->GetController()->SetSeek(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plAvTaskSeek::LeaveAge(plArmatureMod *avatar)
|
|
|
|
{
|
|
|
|
fSeekObject = nil;
|
|
|
|
fState = kSeekAbort;
|
|
|
|
}
|
|
|
|
|
|
|
|
// IAnalyze ----------------------------------------
|
|
|
|
// ---------
|
|
|
|
hsBool plAvTaskSeek::IAnalyze(plArmatureMod *avatar)
|
|
|
|
{
|
|
|
|
avatar->GetPositionAndRotationSim(&fPosition, &fRotation);
|
|
|
|
fGoalVec.Set(&(hsScalarTriple)(fSeekPos - fPosition));
|
|
|
|
hsVector3 normalizedGoalVec(fGoalVec);
|
|
|
|
normalizedGoalVec.Normalize();
|
|
|
|
|
|
|
|
fDistance = sqrt(fGoalVec.fX * fGoalVec.fX + fGoalVec.fY * fGoalVec.fY);
|
|
|
|
|
|
|
|
if(fDistance < 3.0f)
|
|
|
|
{
|
|
|
|
// we're in "near target" mode
|
|
|
|
fMinFwdAngle = .5f; // walk forward if target's in 90' cone straight ahead
|
|
|
|
fMaxBackAngle = -.2f; // walk backward if target's in a 144' cone behind
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// we're in "far target" mode
|
|
|
|
fMinFwdAngle = .2f; // walk forward if target's in a 144' cone ahead
|
|
|
|
fMaxBackAngle = -2.0; // disable backing up if goal is far out (-1 is the minimum usable value here)
|
|
|
|
}
|
|
|
|
|
|
|
|
hsQuat invRot = fRotation.Conjugate();
|
|
|
|
hsPoint3 globFwd = invRot.Rotate(&kAvatarForward);
|
|
|
|
hsPoint3 globRight = invRot.Rotate(&kAvatarRight);
|
|
|
|
|
|
|
|
hsPoint3 locGoalVec = fRotation.Rotate(&fGoalVec);
|
|
|
|
|
|
|
|
fDistForward = -(locGoalVec.fY);
|
|
|
|
fDistRight = -(locGoalVec.fX);
|
|
|
|
|
|
|
|
fAngForward = globFwd.InnerProduct(normalizedGoalVec);
|
|
|
|
fAngRight = globRight.InnerProduct(normalizedGoalVec);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// IMoveTowardsGoal --------------------------------------------------------------
|
|
|
|
// -----------------
|
|
|
|
hsBool plAvTaskSeek::IMoveTowardsGoal(plArmatureMod *avatar, plAvBrainHuman *brain,
|
|
|
|
double time, float elapsed)
|
|
|
|
{
|
|
|
|
bool stillRunning = true;
|
|
|
|
avatar->ClearInputFlags(false, false);
|
|
|
|
|
|
|
|
double duration = time - fStartTime;
|
|
|
|
|
|
|
|
if(duration > kSeekTimeout)
|
|
|
|
{
|
|
|
|
if (fFlags & kSeekFlagNoWarpOnTimeout)
|
|
|
|
{
|
|
|
|
fState = kSeekAbort;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
fSeekRot.Normalize();
|
|
|
|
avatar->SetPositionAndRotationSim(&fSeekPos, &fSeekRot);
|
|
|
|
IAnalyze(avatar); // Recalcs fPosition, fDistance, etc.
|
|
|
|
hsStatusMessage("Timing out on smart seek - jumping to target.");
|
|
|
|
stillRunning = false;
|
|
|
|
|
|
|
|
// We just set the pos/rot, so we know these are hit.
|
|
|
|
fPosGoalHit = true;
|
|
|
|
fRotGoalHit = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(fDistance > fShuffleRange))
|
|
|
|
fPosGoalHit = true;
|
|
|
|
|
|
|
|
if (!fPosGoalHit)
|
|
|
|
{
|
|
|
|
bool right = fAngRight > 0.0f;
|
|
|
|
bool inSidleRange = fDistance < fMaxSidleRange;
|
|
|
|
|
|
|
|
bool sidling = fabs(fDistRight) > fabs(fDistForward) && inSidleRange;
|
|
|
|
|
|
|
|
if(sidling)
|
|
|
|
{
|
|
|
|
if(right)
|
|
|
|
avatar->SetStrafeRightKeyDown();
|
|
|
|
else
|
|
|
|
avatar->SetStrafeLeftKeyDown();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(fAngForward < fMaxBackAngle)
|
|
|
|
avatar->SetBackwardKeyDown();
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(fAngForward > fMinFwdAngle)
|
|
|
|
avatar->SetForwardKeyDown();
|
|
|
|
|
|
|
|
if(right)
|
|
|
|
avatar->SetTurnRightKeyDown();
|
|
|
|
else
|
|
|
|
avatar->SetTurnLeftKeyDown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!(QuatAngleDiff(fRotation, fSeekRot) > .1))
|
|
|
|
fRotGoalHit = true;
|
|
|
|
|
|
|
|
if (!fRotGoalHit)
|
|
|
|
{
|
|
|
|
hsQuat invRot = fSeekRot.Conjugate();
|
|
|
|
hsPoint3 globFwd = invRot.Rotate(&kAvatarForward);
|
|
|
|
globFwd = fRotation.Rotate(&globFwd);
|
|
|
|
|
|
|
|
if (globFwd.fX < 0)
|
|
|
|
avatar->SetTurnRightKeyDown();
|
|
|
|
else
|
|
|
|
avatar->SetTurnLeftKeyDown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fPosGoalHit && fRotGoalHit)
|
|
|
|
stillRunning = ITryFinish(avatar, brain, time, elapsed);
|
|
|
|
|
|
|
|
return stillRunning;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ITRYFINISH
|
|
|
|
bool plAvTaskSeek::ITryFinish(plArmatureMod *avatar, plAvBrainHuman *brain, double time, float elapsed)
|
|
|
|
{
|
|
|
|
hsBool animsDone = brain->IsMovementZeroBlend();
|
|
|
|
|
|
|
|
hsPoint3 newPosition = fPosition;
|
|
|
|
hsQuat newRotation = fRotation;
|
|
|
|
|
|
|
|
if (!(fFlags & kSeekFlagRotationOnly) && (fStillPositioning || !animsDone))
|
|
|
|
fStillPositioning = IFinishPosition(newPosition, avatar, brain, time, elapsed);
|
|
|
|
if (fStillRotating || !animsDone)
|
|
|
|
fStillRotating = IFinishRotation(newRotation, avatar, brain, time, elapsed);
|
|
|
|
|
|
|
|
newRotation.Normalize();
|
|
|
|
if (hsCheckBits(fFlags, kSeekFlagRotationOnly))
|
|
|
|
avatar->SetPositionAndRotationSim(nil, &newRotation);
|
|
|
|
else
|
|
|
|
avatar->SetPositionAndRotationSim(&newPosition, &newRotation);
|
|
|
|
|
|
|
|
return fStillPositioning || fStillRotating || !animsDone;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plAvTaskSeek::IFinishPosition(hsPoint3 &newPosition,
|
|
|
|
plArmatureMod *avatar, plAvBrainHuman *brain,
|
|
|
|
double time, float elapsed)
|
|
|
|
{
|
|
|
|
// While warping, we might be hovering just above the ground. Don't want that to
|
|
|
|
// trigger any falling behavior.
|
|
|
|
if(brain&&brain->fCallbackAction)
|
|
|
|
{
|
|
|
|
|
|
|
|
brain->fCallbackAction->ResetAirTime();
|
|
|
|
}
|
|
|
|
// how far will we translate this frame?
|
|
|
|
float thisDist = kFloatSpeed * elapsed;
|
|
|
|
// what percentage of the remaining distance will we cover?
|
|
|
|
float thisPct = (fDistance ? thisDist / fDistance : 1.f);
|
|
|
|
|
|
|
|
if(thisPct > 0.9f)
|
|
|
|
{
|
|
|
|
// we're pretty much done; just hop the rest of the way
|
|
|
|
newPosition = fSeekPos;
|
|
|
|
return false; // we're done
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// move incrementally toward the target position
|
|
|
|
hsVector3 thisMove = fGoalVec * thisPct;
|
|
|
|
newPosition = fPosition + thisMove;
|
|
|
|
return true; // we're still processing
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// IFinishRotation --------------------------------------
|
|
|
|
// ----------------
|
|
|
|
hsBool plAvTaskSeek::IFinishRotation(hsQuat &newRotation,
|
|
|
|
plArmatureMod *avatar, plAvBrainHuman *brain,
|
|
|
|
double time, float elapsed)
|
|
|
|
{
|
|
|
|
// we're pretty much done; just hop the rest of the way
|
|
|
|
newRotation = fSeekRot;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// IUpdateObjective ----------------------------------------
|
|
|
|
// -----------------
|
|
|
|
hsBool plAvTaskSeek::IUpdateObjective(plArmatureMod *avatar)
|
|
|
|
{
|
|
|
|
// This is an entirely valid case. It just means our goal is fixed.
|
|
|
|
if (fSeekObject == nil)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// goal here is to express the target matrix in the avatar's PHYSICAL space
|
|
|
|
hsMatrix44 targL2W = fSeekObject->GetLocalToWorld();
|
|
|
|
const plCoordinateInterface* subworldCI = nil;
|
|
|
|
if (avatar->GetController())
|
|
|
|
subworldCI = avatar->GetController()->GetSubworldCI();
|
|
|
|
if (subworldCI)
|
|
|
|
targL2W = subworldCI->GetWorldToLocal() * targL2W;
|
|
|
|
|
|
|
|
MakeMatrixUpright(targL2W);
|
|
|
|
|
|
|
|
switch(fAlign)
|
|
|
|
{
|
|
|
|
// match our handle to the target matrix at the end of the given animation
|
|
|
|
// This case isn't currently used but will be important someday. The idea
|
|
|
|
// is that you have a target point and an animation, and you want to seek
|
|
|
|
// the avatar to a point where he can start playing the animation and wind
|
|
|
|
// up, after the animation completes, at the target location.
|
|
|
|
// Hence "AlignHandleAnimEnd" = "align the avatar so the animation will
|
|
|
|
// end on the target."
|
|
|
|
case kAlignHandleAnimEnd:
|
|
|
|
{
|
|
|
|
hsMatrix44 adjustment;
|
|
|
|
plAGAnim *anim = avatar->FindCustomAnim(fAnimName);
|
|
|
|
// don't need to do this every frame; the animation doesn't change.
|
|
|
|
// *** cache the adjustment;
|
|
|
|
GetStartToEndTransform(anim, nil, &adjustment, "Handle"); // actually getting end-to-start
|
|
|
|
// ... but we do still need to multiply by the (potentially changed) target
|
|
|
|
targL2W = targL2W * adjustment;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kAlignHandle: // targetMat is already correct
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
|
|
|
|
GetPositionAndRotation(targL2W, &fSeekPos, &fSeekRot);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// DumpDebug -----------------------------------------------------------------------------------------------------
|
|
|
|
// ----------
|
|
|
|
void plAvTaskSeek::DumpDebug(const char *name, int &x, int&y, int lineHeight, char *strBuf, plDebugText &debugTxt)
|
|
|
|
{
|
|
|
|
sprintf(strBuf, "duration: %.2f pos: (%.3f, %.3f, %.3f) goalPos: (%.3f, %.3f, %.3f) ",
|
|
|
|
hsTimer::GetSysSeconds() - fStartTime,
|
|
|
|
fPosition.fX, fPosition.fY, fPosition.fZ, fSeekPos.fX, fSeekPos.fY, fSeekPos.fZ);
|
|
|
|
debugTxt.DrawString(x, y, strBuf);
|
|
|
|
y += lineHeight;
|
|
|
|
|
|
|
|
sprintf(strBuf, "positioning: %d rotating %d goalVec: (%.3f, %.3f, %.3f) dist: %.3f angFwd: %.3f angRt: %.3f",
|
|
|
|
fStillPositioning, fStillRotating, fGoalVec.fX, fGoalVec.fY, fGoalVec.fZ, fDistance, fAngForward, fAngRight);
|
|
|
|
debugTxt.DrawString(x, y, strBuf);
|
|
|
|
y += lineHeight;
|
|
|
|
|
|
|
|
sprintf(strBuf, " distFwd: %.3f distRt: %.3f shufRange: %.3f sidAngle: %.3f sidRange: %.3f, fMinWalk: %.3f",
|
|
|
|
fDistForward, fDistRight, fShuffleRange, fMaxSidleAngle, fMaxSidleRange, fMinFwdAngle);
|
|
|
|
debugTxt.DrawString(x, y, strBuf);
|
|
|
|
y += lineHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plAvTaskSeek::DumpToAvatarLog(plArmatureMod *avatar)
|
|
|
|
{
|
|
|
|
plStatusLog *log = plAvatarMgr::GetInstance()->GetLog();
|
|
|
|
char strBuf[256];
|
|
|
|
avatar->GetMoveKeyString(strBuf);
|
|
|
|
log->AddLine(strBuf);
|
|
|
|
|
|
|
|
sprintf(strBuf, " duration: %.2f pos: (%.3f, %.3f, %.3f) goalPos: (%.3f, %.3f, %.3f) ",
|
|
|
|
hsTimer::GetSysSeconds() - fStartTime,
|
|
|
|
fPosition.fX, fPosition.fY, fPosition.fZ, fSeekPos.fX, fSeekPos.fY, fSeekPos.fZ);
|
|
|
|
log->AddLine(strBuf);
|
|
|
|
|
|
|
|
sprintf(strBuf, " positioning: %d rotating %d goalVec: (%.3f, %.3f, %.3f) dist: %.3f angFwd: %.3f angRt: %.3f",
|
|
|
|
fStillPositioning, fStillRotating, fGoalVec.fX, fGoalVec.fY, fGoalVec.fZ, fDistance, fAngForward, fAngRight);
|
|
|
|
log->AddLine(strBuf);
|
|
|
|
|
|
|
|
sprintf(strBuf, " distFwd: %.3f distRt: %.3f shufRange: %.3f sidAngle: %.3f sidRange: %.3f, fMinWalk: %.3f",
|
|
|
|
fDistForward, fDistRight, fShuffleRange, fMaxSidleAngle, fMaxSidleRange, fMinFwdAngle);
|
|
|
|
log->AddLine(strBuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// LOCALS
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// QuatAngleDiff ------------------------------------
|
|
|
|
// --------------
|
|
|
|
float QuatAngleDiff(const hsQuat &a, const hsQuat &b)
|
|
|
|
{
|
|
|
|
float theta; /* angle between A and B */
|
|
|
|
float cos_t; /* sine, cosine of theta */
|
|
|
|
|
|
|
|
/* cosine theta = dot product of A and B */
|
|
|
|
cos_t = a.Dot(b);
|
|
|
|
|
|
|
|
/* if B is on opposite hemisphere from A, use -B instead */
|
|
|
|
if (cos_t < 0.0)
|
|
|
|
{
|
|
|
|
cos_t = -cos_t;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calling acos on 1.0 is returning an undefined value. Need to check for it.
|
|
|
|
float epsilon = 0.00001;
|
|
|
|
if (hsABS(cos_t - 1.f) < epsilon)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
theta = acos(cos_t);
|
|
|
|
return theta;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MakeMatrixUpright -------------------------------------------
|
|
|
|
// ------------------
|
|
|
|
// ensure that the z axis of the given matrix points at the sky.
|
|
|
|
// does not orthonormalize
|
|
|
|
// man, I could have sworn I did this somewhere else...
|
|
|
|
void MakeMatrixUpright(hsMatrix44 &mat)
|
|
|
|
{
|
|
|
|
mat.fMap[0][2] = 0.0f; // eliminate any z in the x axis
|
|
|
|
mat.fMap[1][2] = 0.0f; // eliminate any z in the y axis
|
|
|
|
mat.fMap[2][0] = 0.0f; mat.fMap[2][1] = 0.0f; mat.fMap[2][2] = 1.0f; // z axis = pure sky
|
|
|
|
}
|
|
|
|
|
|
|
|
|