You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
623 lines
19 KiB
623 lines
19 KiB
/*==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/>. |
|
|
|
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); |
|
hsScalar angle = hsATan2(lookAt.fY - pos.fY, lookAt.fX - pos.fX) + hsScalarPI / 2; |
|
fSeekRot.SetAngleAxis(angle, up); |
|
} |
|
|
|
// Start ----------------------------------------------------------------------------------------- |
|
// ------ |
|
hsBool plAvTaskSeek::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar 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 = TRACKED_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, hsScalar 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, hsScalar 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 = TRACKED_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 = TRACKED_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 = hsSquareRoot(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, hsScalar 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, hsScalar 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, hsScalar 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, hsScalar 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) |
|
{ |
|
hsScalar theta; /* angle between A and B */ |
|
hsScalar 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. |
|
hsScalar epsilon = 0.00001; |
|
if (hsABS(cos_t - 1.f) < epsilon) |
|
return 0; |
|
|
|
theta = hsACosine(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 |
|
} |
|
|
|
|
|
|