|
|
|
/*==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==*/
|
|
|
|
#include "plAvCallbackAction.h"
|
|
|
|
#include "plMessage/plLOSHitMsg.h"
|
|
|
|
|
|
|
|
#include "plArmatureMod.h" // for LOS enum type
|
|
|
|
#include "plMatrixChannel.h"
|
|
|
|
#include "hsTimer.h"
|
|
|
|
#include "plPhysicalControllerCore.h"
|
|
|
|
|
|
|
|
// Generic geom utils.
|
|
|
|
static bool LinearVelocity(hsVector3 &outputV, float elapsed, hsMatrix44 &prevMat, hsMatrix44 &curMat);
|
|
|
|
static void AngularVelocity(float &outputV, float elapsed, hsMatrix44 &prevMat, hsMatrix44 &curMat);
|
|
|
|
static float AngleRad2d (float x1, float y1, float x3, float y3);
|
|
|
|
inline hsVector3 GetYAxis(hsMatrix44 &mat)
|
|
|
|
{
|
|
|
|
return hsVector3(mat.fMap[1][0], mat.fMap[1][1], mat.fMap[1][2]);
|
|
|
|
}
|
|
|
|
|
|
|
|
plAnimatedController::plAnimatedController(plSceneObject* rootObject, plAGApplicator* rootApp, plPhysicalControllerCore* controller)
|
|
|
|
: fRootObject(rootObject)
|
|
|
|
, fRootApp(rootApp)
|
|
|
|
, fController(controller)
|
|
|
|
, fTurnStr(0.f)
|
|
|
|
, fAnimAngVel(0.f)
|
|
|
|
, fAnimPosVel(0.f, 0.f, 0.f)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void plAnimatedController::RecalcVelocity(double timeNow, double timePrev, bool useAnim /* = true */)
|
|
|
|
{
|
|
|
|
if (useAnim)
|
|
|
|
{
|
|
|
|
// while you may think it would be correct to cache this,
|
|
|
|
// what we're actually asking is "what would the animation's
|
|
|
|
// position be at the previous time given its *current*
|
|
|
|
// parameters (particularly blends)"
|
|
|
|
hsMatrix44 prevMat = ((plMatrixChannel *)fRootApp->GetChannel())->Value(timePrev, true);
|
|
|
|
hsMatrix44 curMat = ((plMatrixChannel *)fRootApp->GetChannel())->Value(timeNow, true);
|
|
|
|
|
|
|
|
// If we get a valid linear velocity (ie, we didn't wrap around in the anim),
|
|
|
|
// use it. Otherwise just reuse the previous frames velocity.
|
|
|
|
hsVector3 linearVel;
|
|
|
|
if (LinearVelocity(linearVel, (float)(timeNow - timePrev), prevMat, curMat))
|
|
|
|
fAnimPosVel = linearVel;
|
|
|
|
|
|
|
|
// Automatically sets fAnimAngVel
|
|
|
|
AngularVelocity(fAnimAngVel, (float)(timeNow - timePrev), prevMat, curMat);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fAnimPosVel.Set(0.f, 0.f, 0.f);
|
|
|
|
fAnimAngVel = 0.f;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fController)
|
|
|
|
fController->SetVelocities(fAnimPosVel, fAnimAngVel + fTurnStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
const float plWalkingController::kControlledFlightThreshold = 1.f; // seconds
|
|
|
|
|
|
|
|
plWalkingController::plWalkingController(plSceneObject* rootObject, plAGApplicator* rootApp, plPhysicalControllerCore* controller)
|
|
|
|
: plAnimatedController(rootObject, rootApp, controller)
|
|
|
|
, fHitGroundInThisAge(false)
|
|
|
|
, fWaitingForGround(false)
|
|
|
|
, fControlledFlightTime(0)
|
|
|
|
, fControlledFlight(0)
|
|
|
|
, fImpactTime(0.f)
|
|
|
|
, fImpactVelocity(0.f, 0.f, 0.f)
|
|
|
|
, fClearImpact(false)
|
|
|
|
, fGroundLastFrame(false)
|
|
|
|
{
|
|
|
|
if (fController)
|
|
|
|
{
|
|
|
|
fWalkingStrategy= new plWalkingStrategy(fController);
|
|
|
|
fController->SetMovementSimulationInterface(fWalkingStrategy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
fWalkingStrategy = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plWalkingController::RecalcVelocity(double timeNow, double timePrev, bool useAnim)
|
|
|
|
{
|
|
|
|
if (!fHitGroundInThisAge && fController && fController->IsEnabled() && fWalkingStrategy->IsOnGround())
|
|
|
|
fHitGroundInThisAge = true; // if we're not pinned and we're not in an age yet, we are now.
|
|
|
|
|
|
|
|
if (fClearImpact)
|
|
|
|
{
|
|
|
|
fImpactTime = 0.f;
|
|
|
|
fImpactVelocity.Set(0.f, 0.f, 0.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fController && !fWalkingStrategy->IsOnGround())
|
|
|
|
{
|
|
|
|
// PhysX Hack: AchievedLinearVelocity is a Cyanic fix for superjumps. LinearVelocity is
|
|
|
|
// always (0,0,0) outside of the controller update proc
|
|
|
|
fImpactTime = fWalkingStrategy->GetAirTime();
|
|
|
|
fImpactVelocity = fController->GetAchievedLinearVelocity();
|
|
|
|
// convert orientation from subworld to avatar-local coordinates
|
|
|
|
fImpactVelocity = (hsVector3)fController->GetLocalRotation().Rotate(&fImpactVelocity);
|
|
|
|
fClearImpact = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
fClearImpact = true;
|
|
|
|
|
|
|
|
if (IsControlledFlight())
|
|
|
|
{
|
|
|
|
if (fWalkingStrategy && fWalkingStrategy->IsOnGround())
|
|
|
|
fControlledFlightTime = fWalkingStrategy->GetAirTime();
|
|
|
|
if(fGroundLastFrame&&(fWalkingStrategy && !fWalkingStrategy->IsOnGround()))
|
|
|
|
{
|
|
|
|
//we have started to leave the ground tell the movement strategy in case it cares
|
|
|
|
fWalkingStrategy->StartJump();
|
|
|
|
}
|
|
|
|
if (fControlledFlightTime > kControlledFlightThreshold)
|
|
|
|
EnableControlledFlight(false);
|
|
|
|
}
|
|
|
|
if (fWalkingStrategy)
|
|
|
|
fGroundLastFrame = fWalkingStrategy->IsOnGround();
|
|
|
|
else
|
|
|
|
fGroundLastFrame=false;
|
|
|
|
plAnimatedController::RecalcVelocity(timeNow, timePrev, useAnim);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plWalkingController::Reset(bool newAge)
|
|
|
|
{
|
|
|
|
|
|
|
|
ActivateController();
|
|
|
|
if (newAge)
|
|
|
|
{
|
|
|
|
if (fWalkingStrategy)
|
|
|
|
fWalkingStrategy->ResetAirTime();
|
|
|
|
fHitGroundInThisAge = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void plWalkingController::ActivateController()
|
|
|
|
{
|
|
|
|
if (fWalkingStrategy)
|
|
|
|
{
|
|
|
|
fWalkingStrategy->RefreshConnectionToControllerCore();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fWalkingStrategy= new plWalkingStrategy(fController);
|
|
|
|
fWalkingStrategy->RefreshConnectionToControllerCore();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plWalkingController::EnableControlledFlight(bool status)
|
|
|
|
{
|
|
|
|
if (status)
|
|
|
|
{
|
|
|
|
if (fControlledFlight == 0)
|
|
|
|
fControlledFlightTime = 0.f;
|
|
|
|
|
|
|
|
++fControlledFlight;
|
|
|
|
fWaitingForGround = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
fControlledFlight = max(--fControlledFlight, 0);
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
plWalkingController::~plWalkingController()
|
|
|
|
{
|
|
|
|
delete fWalkingStrategy;
|
|
|
|
if (fController)
|
|
|
|
fController->SetMovementSimulationInterface(nil);
|
|
|
|
}
|
|
|
|
#if 0
|
|
|
|
void plWalkingController::Update()
|
|
|
|
{
|
|
|
|
// double elapsed = time.asDouble() - getRefresh().asDouble();
|
|
|
|
// setRefresh(time);
|
|
|
|
//
|
|
|
|
// bool isPhysical = !fPhysical->GetProperty(plSimulationInterface::kPinned);
|
|
|
|
// const Havok::Vector3 straightUp(0.0f, 0.0f, 1.0f);
|
|
|
|
// bool alreadyInAge = fHitGroundInThisAge;
|
|
|
|
//
|
|
|
|
// int numContacts = fPhysical->GetNumContacts();
|
|
|
|
// bool ground = false;
|
|
|
|
// fPushingPhysical = nil;
|
|
|
|
// int i, j;
|
|
|
|
|
|
|
|
/* for(i = 0; i < numContacts; i++)
|
|
|
|
{
|
|
|
|
plHKPhysical *contactPhys = fPhysical->GetContactPhysical(i);
|
|
|
|
if (!contactPhys)
|
|
|
|
continue; // Physical no longer exists. Skip it.
|
|
|
|
|
|
|
|
const Havok::ContactPoint *contact = fPhysical->GetContactPoint(i);
|
|
|
|
float dotUp = straightUp.dot(contact->m_normal);
|
|
|
|
if (dotUp > .5)
|
|
|
|
ground = true;
|
|
|
|
else if (contactPhys->GetProperty(plSimulationInterface::kAvAnimPushable))
|
|
|
|
{
|
|
|
|
hsPoint3 position;
|
|
|
|
hsQuat rotation;
|
|
|
|
fPhysical->GetPositionAndRotationSim(&position, &rotation);
|
|
|
|
|
|
|
|
hsQuat inverseRotation = rotation.Inverse();
|
|
|
|
hsVector3 normal(contact->m_normal.x, contact->m_normal.y, contact->m_normal.z);
|
|
|
|
fFacingPushingPhysical = (inverseRotation.Rotate(&kAvatarForward).InnerProduct(normal) < 0 ? true : false);
|
|
|
|
|
|
|
|
fPushingPhysical = contactPhys;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need to check for the case where the avatar hasn't collided with "ground", but is colliding
|
|
|
|
// with a few other objects so that he's not actually falling (wedged in between some slopes).
|
|
|
|
// We do this by answering the following question (in 2d top-down space): "If you sort the contact
|
|
|
|
// normals by angle, is there a large enough gap between normals?"
|
|
|
|
//
|
|
|
|
// If you think in terms of geometry, this means a collection of surfaces are all pushing on you.
|
|
|
|
// If they're pushing from all sides, you have nowhere to go, and you won't fall. There needs to be
|
|
|
|
// a gap, so that you're pushed out and have somewhere to fall. This is the same as finding a gap
|
|
|
|
// larger than 180 degrees between sorted normals.
|
|
|
|
//
|
|
|
|
// The problem is that on top of that, the avatar needs enough force to shove him out that gap (he
|
|
|
|
// has to overcome friction). I deal with that by making the threshold (360 - (180 - 60) = 240). I've
|
|
|
|
// seen up to 220 reached in actual gameplay in a situation where we'd want this to take effect.
|
|
|
|
// This is the same running into 2 walls where the angle between them is 60.
|
|
|
|
const float threshold = hsDegreesToRadians(240);
|
|
|
|
if (!ground && numContacts >= 2)
|
|
|
|
{
|
|
|
|
// Can probably do a special case for exactly 2 contacts. Not sure if it's worth it...
|
|
|
|
|
|
|
|
fCollisionAngles.SetCount(numContacts);
|
|
|
|
for (i = 0; i < numContacts; i++)
|
|
|
|
{
|
|
|
|
const Havok::ContactPoint *contact = fPhysical->GetContactPoint(i);
|
|
|
|
fCollisionAngles[i] = atan2(contact->m_normal.y, contact->m_normal.x);
|
|
|
|
}
|
|
|
|
|
|
|
|
// numContacts is rarely larger than 6, so let's do a simple bubble sort.
|
|
|
|
for (i = 0; i < numContacts; i++)
|
|
|
|
{
|
|
|
|
for (j = i + 1; j < numContacts; j++)
|
|
|
|
{
|
|
|
|
if (fCollisionAngles[i] > fCollisionAngles[j])
|
|
|
|
{
|
|
|
|
float tempAngle = fCollisionAngles[i];
|
|
|
|
fCollisionAngles[i] = fCollisionAngles[j];
|
|
|
|
fCollisionAngles[j] = tempAngle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sorted, now we check.
|
|
|
|
for (i = 1; i < numContacts; i++)
|
|
|
|
{
|
|
|
|
if (fCollisionAngles[i] - fCollisionAngles[i - 1] >= threshold)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == numContacts)
|
|
|
|
{
|
|
|
|
// We got to the end. Check the last with the first and make your decision.
|
|
|
|
if (!(fCollisionAngles[0] - fCollisionAngles[numContacts - 1] >= (threshold - 2 * M_PI)))
|
|
|
|
ground = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool ground = fController ? fController->GotGroundHit() : true;
|
|
|
|
bool isPhysical = true;
|
|
|
|
|
|
|
|
if (!fHitGroundInThisAge && isPhysical)
|
|
|
|
fHitGroundInThisAge = true; // if we're not pinned and we're not in an age yet, we are now.
|
|
|
|
|
|
|
|
if (IsControlledFlight())
|
|
|
|
fControlledFlightTime += (float)elapsed;
|
|
|
|
if (fControlledFlightTime > kControlledFlightThreshold && numContacts > 0)
|
|
|
|
EnableControlledFlight(false);
|
|
|
|
|
|
|
|
if (ground || !isPhysical)
|
|
|
|
{
|
|
|
|
if (!IsControlledFlight() && !IsOnGround())
|
|
|
|
{
|
|
|
|
// The first ground contact in an age doesn't count.
|
|
|
|
// if (alreadyInAge)
|
|
|
|
// {
|
|
|
|
// hsVector3 vel;
|
|
|
|
// fPhysical->GetLinearVelocitySim(vel);
|
|
|
|
// fImpactVel = vel.fZ;
|
|
|
|
// fTimeInAirPeak = (float)(fTimeInAir + elapsed);
|
|
|
|
// }
|
|
|
|
|
|
|
|
fWaitingForGround = false;
|
|
|
|
}
|
|
|
|
fTimeInAir = 0;
|
|
|
|
}
|
|
|
|
else if (elapsed < plSimulationMgr::GetInstance()->GetMaxDelta())
|
|
|
|
{
|
|
|
|
// If the simultation skipped a huge chunk of time, we didn't process the
|
|
|
|
// collisions, which could trick us into thinking we've just gone a long
|
|
|
|
// time without hitting ground. So we only count the time if this wasn't
|
|
|
|
// the case.
|
|
|
|
fTimeInAir += (float)elapsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Tweakage so that we still fall under the right conditions.
|
|
|
|
// If we're in controlled flight, or standing still with ground solidly under us (probe hit). We only use anim velocity.
|
|
|
|
// if (!IsControlledFlight() && !(ground && fProbeHitGround && fAnimPosVel.fX == 0 && fAnimPosVel.fY == 0))
|
|
|
|
// {
|
|
|
|
// hsVector3 curV;
|
|
|
|
// fPhysical->GetLinearVelocitySim(curV);
|
|
|
|
// fAnimPosVel.fZ = curV.fZ;
|
|
|
|
//
|
|
|
|
// // Prevents us from going airborn from running up bumps/inclines.
|
|
|
|
// if (IsOnGround() && fAnimPosVel.fZ > 0.f)
|
|
|
|
// fAnimPosVel.fZ = 0.f;
|
|
|
|
//
|
|
|
|
// // Unless we're on the ground and moving, or standing still with a probe hit, we use the sim's other axes too.
|
|
|
|
// if (!(IsOnGround() && (fProbeHitGround || fAnimPosVel.fX != 0 || fAnimPosVel.fY != 0)))
|
|
|
|
// {
|
|
|
|
// fAnimPosVel.fX = curV.fX;
|
|
|
|
// fAnimPosVel.fY = curV.fY;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// fPhysical->SetLinearVelocitySim(fAnimPosVel);
|
|
|
|
// fPhysical->SetSpin(fAnimAngVel + fTurnStr, hsVector3(0.0f, 0.0f, 1.0f));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
plSimDefs::ActionType plHorizontalFreezeAction::GetType()
|
|
|
|
{
|
|
|
|
return plSimDefs::kHorizontalFreeze;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plHorizontalFreezeAction::apply(Havok::Subspace &s, Havok::hkTime time)
|
|
|
|
{
|
|
|
|
double elapsed = time.asDouble() - getRefresh().asDouble();
|
|
|
|
setRefresh(time);
|
|
|
|
|
|
|
|
int numContacts = fPhysical->GetNumContacts();
|
|
|
|
bool ground = false;
|
|
|
|
const Havok::Vector3 straightUp(0.0f, 0.0f, 1.0f);
|
|
|
|
int i;
|
|
|
|
for(i = 0; i < numContacts; i++)
|
|
|
|
{
|
|
|
|
const Havok::ContactPoint *contact = fPhysical->GetContactPoint(i);
|
|
|
|
float dotUp = straightUp.dot(contact->m_normal);
|
|
|
|
if (dotUp > .5)
|
|
|
|
ground = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsVector3 vel;
|
|
|
|
fPhysical->GetLinearVelocitySim(vel);
|
|
|
|
vel.fX = 0.0;
|
|
|
|
vel.fY = 0.0;
|
|
|
|
if (ground)
|
|
|
|
vel.fZ = 0;
|
|
|
|
fPhysical->SetLinearVelocitySim(vel);
|
|
|
|
fPhysical->ClearContacts();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
plSwimmingController::plSwimmingController(plSceneObject* rootObject, plAGApplicator* rootApp, plPhysicalControllerCore* controller)
|
|
|
|
:plAnimatedController(rootObject,rootApp,controller)
|
|
|
|
{
|
|
|
|
if (controller)
|
|
|
|
fSwimmingStrategy= new plSwimStrategy(controller);
|
|
|
|
else
|
|
|
|
fSwimmingStrategy = nil;
|
|
|
|
}
|
|
|
|
plSwimmingController::~plSwimmingController()
|
|
|
|
{
|
|
|
|
delete fSwimmingStrategy;
|
|
|
|
}
|
|
|
|
|
|
|
|
plRidingAnimatedPhysicalController::plRidingAnimatedPhysicalController(plSceneObject* rootObject, plAGApplicator* rootApp, plPhysicalControllerCore* controller)
|
|
|
|
: plWalkingController(rootObject, rootApp, controller)
|
|
|
|
{
|
|
|
|
if(controller)
|
|
|
|
fWalkingStrategy = new plRidingAnimatedPhysicalStrategy(controller);
|
|
|
|
else
|
|
|
|
fWalkingStrategy = nil;
|
|
|
|
}
|
|
|
|
plRidingAnimatedPhysicalController::~plRidingAnimatedPhysicalController()
|
|
|
|
{
|
|
|
|
delete fWalkingStrategy;
|
|
|
|
fWalkingStrategy=nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Purpose:
|
|
|
|
|
|
|
|
ANGLE_RAD_2D returns the angle in radians swept out between two rays in 2D.
|
|
|
|
|
|
|
|
Discussion:
|
|
|
|
|
|
|
|
Except for the zero angle case, it should be true that
|
|
|
|
|
|
|
|
ANGLE_RAD_2D(X1,Y1,X2,Y2,X3,Y3)
|
|
|
|
+ ANGLE_RAD_2D(X3,Y3,X2,Y2,X1,Y1) = 2 * PI
|
|
|
|
|
|
|
|
Modified:
|
|
|
|
|
|
|
|
19 April 1999
|
|
|
|
|
|
|
|
Author:
|
|
|
|
|
|
|
|
John Burkardt
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
Input, float X1, Y1, X2, Y2, X3, Y3, define the rays
|
|
|
|
( X1-X2, Y1-Y2 ) and ( X3-X2, Y3-Y2 ) which in turn define the
|
|
|
|
angle, counterclockwise from ( X1-X2, Y1-Y2 ).
|
|
|
|
|
|
|
|
Output, float ANGLE_RAD_2D, the angle swept out by the rays, measured
|
|
|
|
in radians. 0 <= ANGLE_DEG_2D < 2 PI. If either ray has zero length,
|
|
|
|
then ANGLE_RAD_2D is set to 0.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static float AngleRad2d ( float x1, float y1, float x3, float y3 )
|
|
|
|
{
|
|
|
|
float value;
|
|
|
|
float x;
|
|
|
|
float y;
|
|
|
|
|
|
|
|
x = ( x1 ) * ( x3 ) + ( y1 ) * ( y3 );
|
|
|
|
y = ( x1 ) * ( y3 ) - ( y1 ) * ( x3 );
|
|
|
|
|
|
|
|
if ( x == 0.0 && y == 0.0 ) {
|
|
|
|
value = 0.0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
value = atan2 ( y, x );
|
|
|
|
|
|
|
|
if ( value < 0.0 )
|
|
|
|
{
|
|
|
|
value = (float)(value + TWO_PI);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool LinearVelocity(hsVector3 &outputV, float elapsed, hsMatrix44 &prevMat, hsMatrix44 &curMat)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
|
|
|
|
hsPoint3 startPos(0.0f, 0.0f, 0.0f); // default position (at start of anim)
|
|
|
|
hsPoint3 prevPos = prevMat.GetTranslate(); // position previous frame
|
|
|
|
hsPoint3 nowPos = curMat.GetTranslate(); // position current frame
|
|
|
|
|
|
|
|
hsVector3 prev2Now = (hsVector3)(nowPos - prevPos); // frame-to-frame delta
|
|
|
|
|
|
|
|
if (fabs(prev2Now.fX) < 0.0001f && fabs(prev2Now.fY) < 0.0001f && fabs(prev2Now.fZ) < 0.0001f)
|
|
|
|
{
|
|
|
|
outputV.Set(0.f, 0.f, 0.f);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
hsVector3 start2Now = (hsVector3)(nowPos - startPos); // start-to-frame delta
|
|
|
|
|
|
|
|
float prev2NowMagSqr = prev2Now.MagnitudeSquared();
|
|
|
|
float start2NowMagSqr = start2Now.MagnitudeSquared();
|
|
|
|
|
|
|
|
float dot = prev2Now.InnerProduct(start2Now);
|
|
|
|
|
|
|
|
// HANDLING ANIMATION WRAPPING:
|
|
|
|
// the vector from the animation origin to the current frame should point in roughly
|
|
|
|
// the same direction as the vector from the previous animation position to the
|
|
|
|
// current animation position.
|
|
|
|
//
|
|
|
|
// If they don't agree (dot < 0,) then we probably mpst wrapped around.
|
|
|
|
// The right answer would be to compare the current frame to the start of
|
|
|
|
// the anim loop, but it's cheaper to cheat and return false,
|
|
|
|
// telling the caller to use the previous frame's velocity.
|
|
|
|
if (dot > 0.0f)
|
|
|
|
{
|
|
|
|
prev2Now /= elapsed;
|
|
|
|
|
|
|
|
float xfabs = fabs(prev2Now.fX);
|
|
|
|
float yfabs = fabs(prev2Now.fY);
|
|
|
|
float zfabs = fabs(prev2Now.fZ);
|
|
|
|
static const float maxVel = 20.0f;
|
|
|
|
bool valid = xfabs < maxVel && yfabs < maxVel && zfabs < maxVel;
|
|
|
|
|
|
|
|
if (valid)
|
|
|
|
{
|
|
|
|
outputV = prev2Now;
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void AngularVelocity(float &outputV, float elapsed, hsMatrix44 &prevMat, hsMatrix44 &curMat)
|
|
|
|
{
|
|
|
|
outputV = 0.f;
|
|
|
|
float appliedVelocity = 0.0f;
|
|
|
|
hsVector3 prevForward = GetYAxis(prevMat);
|
|
|
|
hsVector3 curForward = GetYAxis(curMat);
|
|
|
|
|
|
|
|
float angleSincePrev = AngleRad2d(curForward.fX, curForward.fY, prevForward.fX, prevForward.fY);
|
|
|
|
bool sincePrevSign = angleSincePrev > 0.0f;
|
|
|
|
if (angleSincePrev > M_PI)
|
|
|
|
angleSincePrev = angleSincePrev - TWO_PI;
|
|
|
|
|
|
|
|
const hsVector3 startForward = hsVector3(0, -1.0, 0); // the Y orientation of a "resting" armature....
|
|
|
|
float angleSinceStart = AngleRad2d(curForward.fX, curForward.fY, startForward.fX, startForward.fY);
|
|
|
|
bool sinceStartSign = angleSinceStart > 0.0f;
|
|
|
|
if (angleSinceStart > M_PI)
|
|
|
|
angleSinceStart = angleSinceStart - TWO_PI;
|
|
|
|
|
|
|
|
// HANDLING ANIMATION WRAPPING:
|
|
|
|
// under normal conditions, the angle from rest to the current frame will have the same
|
|
|
|
// sign as the angle from the previous frame to the current frame.
|
|
|
|
// if it does not, we have (most likely) wrapped the motivating animation from frame n back
|
|
|
|
// to frame zero, creating a large angle from the previous frame to the current one
|
|
|
|
if (sincePrevSign == sinceStartSign)
|
|
|
|
{
|
|
|
|
// signs are the same; didn't wrap; use the frame-to-frame angle difference
|
|
|
|
appliedVelocity = angleSincePrev / elapsed; // rotation / time
|
|
|
|
if (fabs(appliedVelocity) < 3)
|
|
|
|
{
|
|
|
|
outputV = appliedVelocity;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|