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.
828 lines
24 KiB
828 lines
24 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/>. |
|
|
|
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 "plPhysicalControllerCore.h" |
|
|
|
#include "plArmatureMod.h" |
|
#include "plSwimRegion.h" |
|
#include "plMatrixChannel.h" |
|
#include "../pnSceneObject/plCoordinateInterface.h" |
|
#include "../../NucleusLib/inc/plPhysical.h" |
|
#include "../../NucleusLib/pnMessage/plCorrectionMsg.h" |
|
|
|
// Gravity constants |
|
#define kGravity -32.174f |
|
#define kTerminalVelocity kGravity |
|
|
|
static inline hsVector3 GetYAxis(hsMatrix44 &mat) { return hsVector3(mat.fMap[1][0], mat.fMap[1][1], mat.fMap[1][2]); } |
|
static float AngleRad2d(float x1, float y1, float x3, float y3); |
|
|
|
bool CompareMatrices(const hsMatrix44 &matA, const hsMatrix44 &matB, float tolerance); |
|
|
|
|
|
// plPhysicalControllerCore |
|
plPhysicalControllerCore::plPhysicalControllerCore(plKey OwnerSceneObject, hsScalar height, hsScalar radius) |
|
: fOwner(OwnerSceneObject), |
|
fWorldKey(nil), |
|
fHeight(height), |
|
fRadius(radius), |
|
fLOSDB(plSimDefs::kLOSDBNone), |
|
fMovementStrategy(nil), |
|
fSimLength(0.0f), |
|
fLocalRotation(0.0f, 0.0f, 0.0f, 1.0f), |
|
fLocalPosition(0.0f, 0.0f, -2000.0f), |
|
fLastLocalPosition(0.0f, 0.0f, 0.0f), |
|
fLinearVelocity(0.0f, 0.0f, 0.0f), |
|
fAchievedLinearVelocity(0.0f, 0.0f, 0.0f), |
|
fPushingPhysical(nil), |
|
fFacingPushingPhysical(false), |
|
fSeeking(false), |
|
fEnabled(false), |
|
fEnableChanged(false) |
|
{ |
|
fLastGlobalLoc.Reset(); |
|
fPrevSubworldW2L.Reset(); |
|
} |
|
|
|
const plCoordinateInterface* plPhysicalControllerCore::GetSubworldCI() |
|
{ |
|
if (fWorldKey) |
|
{ |
|
plSceneObject* so = plSceneObject::ConvertNoRef(fWorldKey->ObjectIsLoaded()); |
|
if (so) |
|
return so->GetCoordinateInterface(); |
|
} |
|
|
|
return nil; |
|
} |
|
|
|
void plPhysicalControllerCore::IncrementAngle(hsScalar deltaAngle) |
|
{ |
|
hsVector3 axis; |
|
hsScalar angle; |
|
|
|
fLocalRotation.NormalizeIfNeeded(); |
|
fLocalRotation.GetAngleAxis(&angle, &axis); |
|
if (axis.fZ < 0) |
|
angle = (2.0f * hsScalarPI) - angle; // axis is backwards, so reverse the angle too |
|
|
|
angle += deltaAngle; |
|
|
|
// make sure we wrap around |
|
if (angle < 0.0f) |
|
angle = (2.0f * hsScalarPI) + angle; // angle is -, so this works like a subtract |
|
if (angle >= (2.0f * hsScalarPI)) |
|
angle = angle - (2.0f * hsScalarPI); |
|
|
|
// set the new angle |
|
axis.Set(0.0f, 0.0f, 1.0f); |
|
fLocalRotation.SetAngleAxis(angle, axis); |
|
} |
|
|
|
void plPhysicalControllerCore::IApply(hsScalar delSecs) |
|
{ |
|
fSimLength = delSecs; |
|
|
|
// Match controller to owner if transform has changed since the last frame |
|
plSceneObject* so = plSceneObject::ConvertNoRef(fOwner->ObjectIsLoaded()); |
|
const hsMatrix44& l2w = so->GetCoordinateInterface()->GetLocalToWorld(); |
|
if (!CompareMatrices(fLastGlobalLoc, l2w, 0.0001f)) |
|
SetGlobalLoc(l2w); |
|
|
|
if (fEnabled) |
|
{ |
|
// Convert velocity from avatar to world space |
|
if (!fLinearVelocity.IsEmpty()) |
|
{ |
|
fLinearVelocity = l2w * fLinearVelocity; |
|
|
|
const plCoordinateInterface* subworldCI = GetSubworldCI(); |
|
if (subworldCI) |
|
fLinearVelocity = subworldCI->GetWorldToLocal() * fLinearVelocity; |
|
} |
|
|
|
fMovementStrategy->Apply(delSecs); |
|
} |
|
} |
|
void plPhysicalControllerCore::IUpdate(int numSubSteps, hsScalar alpha) |
|
{ |
|
if (fEnabled) |
|
{ |
|
// Update local position and acheived velocity |
|
fLastLocalPosition = fLocalPosition; |
|
GetPositionSim(fLocalPosition); |
|
hsVector3 displacement = (hsVector3)(fLocalPosition - fLastLocalPosition); |
|
fAchievedLinearVelocity = displacement / fSimLength; |
|
|
|
displacement /= (hsScalar)numSubSteps; |
|
fLastLocalPosition = fLocalPosition - displacement; |
|
hsPoint3 interpLocalPos = fLastLocalPosition + (displacement * alpha); |
|
|
|
// Update global location |
|
fLocalRotation.MakeMatrix(&fLastGlobalLoc); |
|
fLastGlobalLoc.SetTranslate(&interpLocalPos); |
|
const plCoordinateInterface* subworldCI = GetSubworldCI(); |
|
if (subworldCI) |
|
{ |
|
const hsMatrix44& subL2W = subworldCI->GetLocalToWorld(); |
|
fLastGlobalLoc = subL2W * fLastGlobalLoc; |
|
fPrevSubworldW2L = subworldCI->GetWorldToLocal(); |
|
} |
|
|
|
fMovementStrategy->Update(fSimLength); |
|
ISendCorrectionMessages(true); |
|
} |
|
else |
|
{ |
|
fAchievedLinearVelocity.Set(0.0f, 0.0f, 0.0f); |
|
|
|
// Update global location if in a subworld |
|
const plCoordinateInterface* subworldCI = GetSubworldCI(); |
|
if (subworldCI) |
|
{ |
|
hsMatrix44 l2s = fPrevSubworldW2L * fLastGlobalLoc; |
|
const hsMatrix44& subL2W = subworldCI->GetLocalToWorld(); |
|
fLastGlobalLoc = subL2W * l2s; |
|
fPrevSubworldW2L = subworldCI->GetWorldToLocal(); |
|
|
|
ISendCorrectionMessages(); |
|
} |
|
} |
|
|
|
if (fEnableChanged) |
|
IHandleEnableChanged(); |
|
} |
|
void plPhysicalControllerCore::IUpdateNonPhysical(hsScalar alpha) |
|
{ |
|
// Update global location if owner transform hasn't changed. |
|
plSceneObject* so = plSceneObject::ConvertNoRef(fOwner->ObjectIsLoaded()); |
|
const hsMatrix44& l2w = so->GetCoordinateInterface()->GetLocalToWorld(); |
|
if (CompareMatrices(fLastGlobalLoc, l2w, 0.0001f)) |
|
{ |
|
if (fEnabled) |
|
{ |
|
hsVector3 displacement = (hsVector3)(fLocalPosition - fLastLocalPosition); |
|
hsPoint3 interpLocalPos = fLastLocalPosition + (displacement * alpha); |
|
|
|
fLocalRotation.MakeMatrix(&fLastGlobalLoc); |
|
fLastGlobalLoc.SetTranslate(&interpLocalPos); |
|
const plCoordinateInterface* subworldCI = GetSubworldCI(); |
|
if (subworldCI) |
|
{ |
|
const hsMatrix44& subL2W = subworldCI->GetLocalToWorld(); |
|
fLastGlobalLoc = subL2W * fLastGlobalLoc; |
|
fPrevSubworldW2L = subworldCI->GetWorldToLocal(); |
|
} |
|
|
|
ISendCorrectionMessages(); |
|
} |
|
else |
|
{ |
|
// Update global location if in a subworld |
|
const plCoordinateInterface* subworldCI = GetSubworldCI(); |
|
if (subworldCI) |
|
{ |
|
hsMatrix44 l2s = fPrevSubworldW2L * fLastGlobalLoc; |
|
const hsMatrix44& subL2W = subworldCI->GetLocalToWorld(); |
|
fLastGlobalLoc = subL2W * l2s; |
|
fPrevSubworldW2L = subworldCI->GetWorldToLocal(); |
|
|
|
ISendCorrectionMessages(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void plPhysicalControllerCore::ISendCorrectionMessages(bool dirtySynch) |
|
{ |
|
plCorrectionMsg* corrMsg = TRACKED_NEW plCorrectionMsg(); |
|
corrMsg->fLocalToWorld = fLastGlobalLoc; |
|
corrMsg->fLocalToWorld.GetInverse(&corrMsg->fWorldToLocal); |
|
corrMsg->fDirtySynch = dirtySynch; |
|
corrMsg->AddReceiver(fOwner); |
|
corrMsg->Send(); |
|
} |
|
|
|
|
|
// Movement Strategy |
|
plMovementStrategy::plMovementStrategy(plPhysicalControllerCore* controller) |
|
: fController(controller) |
|
{ |
|
} |
|
|
|
void plMovementStrategy::Reset(bool newAge) { fController->SetMovementStrategy(this); } |
|
|
|
|
|
// Animated Movement Strategy |
|
plAnimatedMovementStrategy::plAnimatedMovementStrategy(plAGApplicator* rootApp, plPhysicalControllerCore* controller) |
|
: plMovementStrategy(controller), |
|
fRootApp(rootApp), |
|
fAnimLinearVel(0.0f, 0.0f, 0.0f), |
|
fAnimAngularVel(0.0f), |
|
fTurnStr(0.0f) |
|
{ |
|
} |
|
|
|
void plAnimatedMovementStrategy::RecalcVelocity(double timeNow, hsScalar elapsed, hsBool useAnim) |
|
{ |
|
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(timeNow - elapsed, true); |
|
hsMatrix44 curMat = ((plMatrixChannel *)fRootApp->GetChannel())->Value(timeNow, true); |
|
|
|
IRecalcLinearVelocity(elapsed, prevMat, curMat); |
|
IRecalcAngularVelocity(elapsed, prevMat, curMat); |
|
} |
|
else |
|
{ |
|
fAnimLinearVel.Set(0.0f, 0.0f, 0.0f); |
|
fAnimAngularVel = 0.0f; |
|
} |
|
|
|
// Update controller rotation |
|
hsScalar zRot = fAnimAngularVel + fTurnStr; |
|
if (hsABS(zRot) > 0.0001f) |
|
fController->IncrementAngle(zRot * elapsed); |
|
|
|
// Update controller velocity |
|
fController->SetLinearVelocity(fAnimLinearVel); |
|
} |
|
|
|
void plAnimatedMovementStrategy::IRecalcLinearVelocity(float elapsed, hsMatrix44 &prevMat, hsMatrix44 &curMat) |
|
{ |
|
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) |
|
{ |
|
fAnimLinearVel.Set(0.f, 0.f, 0.f); |
|
} |
|
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 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; |
|
hsBool valid = xfabs < maxVel && yfabs < maxVel && zfabs < maxVel; |
|
|
|
if (valid) |
|
{ |
|
fAnimLinearVel = prev2Now; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void plAnimatedMovementStrategy::IRecalcAngularVelocity(float elapsed, hsMatrix44 &prevMat, hsMatrix44 &curMat) |
|
{ |
|
fAnimAngularVel = 0.0f; |
|
hsScalar appliedVelocity = 0.0f; |
|
hsVector3 prevForward = GetYAxis(prevMat); |
|
hsVector3 curForward = GetYAxis(curMat); |
|
|
|
hsScalar angleSincePrev = AngleRad2d(curForward.fX, curForward.fY, prevForward.fX, prevForward.fY); |
|
hsBool sincePrevSign = angleSincePrev > 0.0f; |
|
if (angleSincePrev > hsScalarPI) |
|
angleSincePrev = angleSincePrev - TWO_PI; |
|
|
|
const hsVector3 startForward = hsVector3(0.0f, -1.0f, 0.0f); // the Y orientation of a "resting" armature.... |
|
hsScalar angleSinceStart = AngleRad2d(curForward.fX, curForward.fY, startForward.fX, startForward.fY); |
|
hsBool sinceStartSign = angleSinceStart > 0.0f; |
|
if (angleSinceStart > hsScalarPI) |
|
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) |
|
{ |
|
fAnimAngularVel = appliedVelocity; |
|
} |
|
} |
|
} |
|
|
|
|
|
// Walking Strategy |
|
plWalkingStrategy::plWalkingStrategy(plAGApplicator* rootApp, plPhysicalControllerCore* controller) |
|
: plAnimatedMovementStrategy(rootApp, controller), |
|
fSlidingNormals(), |
|
fImpactVelocity(0.0f, 0.0f, 0.0f), |
|
fImpactTime(0.0f), |
|
fTimeInAir(0.0f), |
|
fControlledFlightTime(0.0f), |
|
fControlledFlight(0), |
|
fGroundHit(false), |
|
fFalseGround(false), |
|
fHeadHit(false), |
|
fClearImpact(false), |
|
fHitGroundInThisAge(false) |
|
{ |
|
} |
|
|
|
void plWalkingStrategy::Apply(hsScalar delSecs) |
|
{ |
|
hsVector3 velocity = fController->GetLinearVelocity(); |
|
hsVector3 achievedVelocity = fController->GetAchievedLinearVelocity(); |
|
|
|
// Add in gravity if the avatar's z velocity isn't being set explicitly |
|
if (hsABS(velocity.fZ) < 0.001f) |
|
{ |
|
// Get our previous z velocity. If we're on the ground, clamp it to zero at |
|
// the largest, so we won't launch into the air if we're running uphill. |
|
hsScalar prevZVel = achievedVelocity.fZ; |
|
if (IsOnGround()) |
|
prevZVel = hsMinimum(prevZVel, 0.0f); |
|
|
|
velocity.fZ = prevZVel + (kGravity * delSecs); |
|
} |
|
|
|
// If we're airborne and the velocity isn't set, use the velocity from |
|
// the last frame so we maintain momentum. |
|
if (!IsOnGround() && velocity.fX == 0.0f && velocity.fY == 0.0f) |
|
{ |
|
velocity.fX = achievedVelocity.fX; |
|
velocity.fY = achievedVelocity.fY; |
|
} |
|
|
|
if (!fGroundHit && fSlidingNormals.Count()) |
|
{ |
|
// We're not on solid ground, so we should be sliding against whatever |
|
// we're hitting (like a rock cliff). Each vector in fSlidingNormals is |
|
// the surface normal of a collision that's too steep to be ground, so |
|
// we project our current velocity onto that plane and slide along the |
|
// wall. |
|
// |
|
// Also, sometimes PhysX reports a bunch of collisions from the wall, |
|
// but nothing from underneath (when there should be). So if we're not |
|
// touching ground, we offset the avatar in the direction of the |
|
// surface normal(s). This doesn't fix the issue 100%, but it's a hell |
|
// of a lot better than nothing, and suitable duct tape until a future |
|
// PhysX revision fixes the issue. |
|
// |
|
// Yes, there's room for optimization here if we care. |
|
hsVector3 offset(0.0f, 0.0f, 0.0f); |
|
for (int i = 0; i < fSlidingNormals.GetCount(); i++) |
|
{ |
|
offset += fSlidingNormals[i]; |
|
hsVector3 velNorm = velocity; |
|
|
|
if (velNorm.MagnitudeSquared() > 0.0f) |
|
velNorm.Normalize(); |
|
|
|
if (velNorm * fSlidingNormals[i] < 0.0f) |
|
{ |
|
hsVector3 proj = (velNorm % fSlidingNormals[i]) % fSlidingNormals[i]; |
|
if (velNorm * proj < 0.0f) |
|
proj *= -1.0f; |
|
|
|
velocity = velocity.Magnitude() * proj; |
|
} |
|
} |
|
if (offset.MagnitudeSquared() > 0.0f) |
|
{ |
|
// 5 ft/sec is roughly the speed we walk backwards. |
|
// The higher the value, the less likely you'll trip |
|
// the bug, and this seems reasonable. |
|
offset.Normalize(); |
|
velocity += offset * 5.0f; |
|
} |
|
} |
|
|
|
if (velocity.fZ < kTerminalVelocity) |
|
velocity.fZ = kTerminalVelocity; |
|
|
|
// Convert to a displacement vector |
|
hsVector3 displacement = velocity * delSecs; |
|
|
|
// Reset vars and move the controller |
|
fController->SetPushingPhysical(nil); |
|
fController->SetFacingPushingPhysical(false); |
|
fGroundHit = fFalseGround = fHeadHit = false; |
|
fSlidingNormals.SetCount(0); |
|
|
|
unsigned int collideResults = 0; |
|
unsigned int collideFlags = 1<<plSimDefs::kGroupStatic | 1<<plSimDefs::kGroupAvatarBlocker | 1<<plSimDefs::kGroupDynamic; |
|
if (!fController->IsSeeking()) |
|
collideFlags |= (1<<plSimDefs::kGroupExcludeRegion); |
|
|
|
fController->Move(displacement, collideFlags, collideResults); |
|
|
|
if ((!fGroundHit) && (collideResults & kBottom)) |
|
fFalseGround = true; |
|
|
|
if (collideResults & kTop) |
|
fHeadHit = true; |
|
} |
|
void plWalkingStrategy::Update(hsScalar delSecs) |
|
{ |
|
if (fGroundHit || fFalseGround) |
|
fTimeInAir = 0.0f; |
|
else |
|
{ |
|
fTimeInAir += delSecs; |
|
if (fHeadHit) |
|
{ |
|
// If we're airborne and hit our head, override achieved velocity to avoid being shoved sideways |
|
hsVector3 velocity = fController->GetLinearVelocity(); |
|
hsVector3 achievedVelocity = fController->GetAchievedLinearVelocity(); |
|
|
|
achievedVelocity.fX = velocity.fX; |
|
achievedVelocity.fY = velocity.fY; |
|
if (achievedVelocity.fZ > 0.0f) |
|
achievedVelocity.fZ = 0.0f; |
|
|
|
fController->OverrideAchievedLinearVelocity(achievedVelocity); |
|
} |
|
} |
|
|
|
hsVector3 zeroVelocity(0.f, 0.f, 0.f); |
|
fController->SetLinearVelocity(zeroVelocity); |
|
|
|
if (!fHitGroundInThisAge && IsOnGround()) |
|
fHitGroundInThisAge = true; |
|
|
|
if (fClearImpact) |
|
{ |
|
fImpactTime = 0.0f; |
|
fImpactVelocity.Set(0.0f, 0.0f, 0.0f); |
|
} |
|
|
|
if (IsOnGround()) |
|
fClearImpact = true; |
|
else |
|
{ |
|
fImpactTime = fTimeInAir; |
|
fImpactVelocity = fController->GetAchievedLinearVelocity(); |
|
// convert orientation from subworld to avatar-local coordinates |
|
fImpactVelocity = (hsVector3)fController->GetLocalRotation().Rotate(&fImpactVelocity); |
|
fClearImpact = false; |
|
} |
|
} |
|
|
|
void plWalkingStrategy::AddContactNormals(hsVector3& vec) |
|
{ |
|
hsScalar dot = vec * kAvatarUp; |
|
if (dot >= 0.5f) |
|
fGroundHit = true; |
|
else |
|
fSlidingNormals.Append(vec); |
|
} |
|
|
|
void plWalkingStrategy::Reset(bool newAge) |
|
{ |
|
plMovementStrategy::Reset(newAge); |
|
fImpactVelocity.Set(0.0f, 0.0f, 0.0f); |
|
fImpactTime = 0.0f; |
|
if (newAge) |
|
{ |
|
fTimeInAir = 0.0f; |
|
fClearImpact = true; |
|
fHitGroundInThisAge = false; |
|
fSlidingNormals.SetCount(0); |
|
} |
|
} |
|
|
|
void plWalkingStrategy::RecalcVelocity(double timeNow, hsScalar elapsed, hsBool useAnim) |
|
{ |
|
if (fControlledFlight != 0) |
|
{ |
|
if (IsOnGround()) |
|
fControlledFlightTime = fTimeInAir; |
|
|
|
if (fControlledFlightTime > kControlledFlightThreshold) |
|
EnableControlledFlight(false); |
|
} |
|
|
|
plAnimatedMovementStrategy::RecalcVelocity(timeNow, elapsed, useAnim); |
|
} |
|
|
|
bool plWalkingStrategy::EnableControlledFlight(bool status) |
|
{ |
|
if (status) |
|
{ |
|
if (fControlledFlight == 0) |
|
fControlledFlightTime = 0.0f; |
|
|
|
++fControlledFlight; |
|
} |
|
else |
|
fControlledFlight = __max(--fControlledFlight, 0); |
|
|
|
return status; |
|
} |
|
|
|
plPhysical* plWalkingStrategy::GetPushingPhysical() const { return fController->GetPushingPhysical(); } |
|
bool plWalkingStrategy::GetFacingPushingPhysical() const { return fController->GetFacingPushingPhysical(); } |
|
|
|
const hsScalar plWalkingStrategy::kAirTimeThreshold = 0.1f; |
|
const hsScalar plWalkingStrategy::kControlledFlightThreshold = 1.0f; |
|
|
|
|
|
// Swim Strategy |
|
plSwimStrategy::plSwimStrategy(plAGApplicator* rootApp, plPhysicalControllerCore* controller) |
|
: plAnimatedMovementStrategy(rootApp, controller), |
|
fBuoyancy(0.0f), |
|
fSurfaceHeight(0.0f), |
|
fCurrentRegion(nil), |
|
fOnGround(false), |
|
fHadContacts(false) |
|
{ |
|
} |
|
|
|
void plSwimStrategy::Apply(hsScalar delSecs) |
|
{ |
|
hsVector3 velocity = fController->GetLinearVelocity(); |
|
hsVector3 achievedVelocity = fController->GetAchievedLinearVelocity(); |
|
|
|
IAdjustBuoyancy(); |
|
|
|
//trying to dampen the oscillations |
|
hsScalar retardent = 0.0f; |
|
static hsScalar finalBobSpeed = 0.5f; |
|
if ((achievedVelocity.fZ > finalBobSpeed) || (achievedVelocity.fZ < -finalBobSpeed)) |
|
retardent = achievedVelocity.fZ * -0.90f; |
|
|
|
hsScalar zacc = (1.0f - fBuoyancy) * kGravity + retardent; |
|
velocity.fZ += (zacc * delSecs); |
|
|
|
velocity.fZ += achievedVelocity.fZ; |
|
|
|
// Water Current |
|
if (fCurrentRegion != nil) |
|
{ |
|
hsScalar angCurrent = 0.0f; |
|
hsVector3 linCurrent(0.0f, 0.0f, 0.0f); |
|
fCurrentRegion->GetCurrent(fController, linCurrent, angCurrent, delSecs); |
|
|
|
if (hsABS(angCurrent) > 0.0001f) |
|
fController->IncrementAngle(angCurrent * delSecs); |
|
|
|
velocity += linCurrent; |
|
|
|
if (velocity.fZ > fCurrentRegion->fMaxUpwardVel) |
|
velocity.fZ = fCurrentRegion->fMaxUpwardVel; |
|
} |
|
|
|
if (velocity.fZ < kTerminalVelocity) |
|
velocity.fZ = kTerminalVelocity; |
|
|
|
// Convert to displacement vector |
|
hsVector3 displacement = velocity * delSecs; |
|
|
|
// Reset vars and move controller // |
|
fController->SetPushingPhysical(nil); |
|
fController->SetFacingPushingPhysical(false); |
|
fHadContacts = fOnGround = false; |
|
|
|
unsigned int collideResults = 0; |
|
unsigned int collideFlags = 1<<plSimDefs::kGroupStatic | 1<<plSimDefs::kGroupAvatarBlocker | 1<<plSimDefs::kGroupDynamic; |
|
if (!fController->IsSeeking()) |
|
collideFlags |= (1<<plSimDefs::kGroupExcludeRegion); |
|
|
|
fController->Move(displacement, collideFlags, collideResults); |
|
|
|
if ((collideResults & kBottom) || (collideResults & kSides)) |
|
fHadContacts = true; |
|
} |
|
|
|
void plSwimStrategy::AddContactNormals(hsVector3& vec) |
|
{ |
|
hsScalar dot = vec * kAvatarUp; |
|
if (dot >= kSlopeLimit) |
|
fOnGround = true; |
|
} |
|
|
|
void plSwimStrategy::SetSurface(plSwimRegionInterface *region, hsScalar surfaceHeight) |
|
{ |
|
fCurrentRegion = region; |
|
fSurfaceHeight = surfaceHeight; |
|
} |
|
void plSwimStrategy::IAdjustBuoyancy() |
|
{ |
|
// "surface depth" refers to the depth our handle object should be below |
|
// the surface for the avatar to be "at the surface" |
|
static const float surfaceDepth = 4.0f; |
|
// 1.0 = neutral buoyancy |
|
// 0 = no buoyancy (normal gravity) |
|
// 2.0 = opposite of gravity, floating upwards |
|
static const float buoyancyAtSurface = 1.0f; |
|
|
|
if (fCurrentRegion == nil) |
|
{ |
|
fBuoyancy = 0.f; |
|
return; |
|
} |
|
|
|
hsPoint3 posSim; |
|
fController->GetPositionSim(posSim); |
|
float depth = fSurfaceHeight - posSim.fZ; |
|
|
|
// this isn't a smooth transition but hopefully it won't be too obvious |
|
if (depth <= 0.0) //all the away above water |
|
fBuoyancy = 0.f; // Same as being above ground. Plain old gravity. |
|
else if (depth >= 5.0f) |
|
fBuoyancy = 3.0f; //completely Submereged |
|
else |
|
fBuoyancy = depth / surfaceDepth; |
|
} |
|
|
|
|
|
// Dynamic Walking Strategy |
|
plDynamicWalkingStrategy::plDynamicWalkingStrategy(plAGApplicator* rootApp, plPhysicalControllerCore* controller) |
|
: plWalkingStrategy(rootApp, controller) |
|
{ |
|
} |
|
|
|
void plDynamicWalkingStrategy::Apply(hsScalar delSecs) |
|
{ |
|
hsVector3 velocity = fController->GetLinearVelocity(); |
|
hsVector3 achievedVelocity = fController->GetAchievedLinearVelocity(); |
|
|
|
// Add in gravity if the avatar's z velocity isn't being set explicitly |
|
if (hsABS(velocity.fZ) < 0.001f) |
|
{ |
|
// Get our previous z velocity. If we're on the ground, clamp it to zero at |
|
// the largest, so we won't launch into the air if we're running uphill. |
|
hsScalar prevZVel = achievedVelocity.fZ; |
|
if (IsOnGround()) |
|
prevZVel = hsMinimum(prevZVel, 0.f); |
|
|
|
velocity.fZ = prevZVel + (kGravity * delSecs); |
|
} |
|
|
|
if (velocity.fZ < kTerminalVelocity) |
|
velocity.fZ = kTerminalVelocity; |
|
|
|
fController->SetPushingPhysical(nil); |
|
fController->SetFacingPushingPhysical(false); |
|
fGroundHit = fFalseGround = false; |
|
|
|
hsScalar groundZVelocity; |
|
if (ICheckForGround(groundZVelocity)) |
|
velocity.fZ += groundZVelocity; |
|
|
|
fController->SetLinearVelocitySim(velocity); |
|
} |
|
|
|
bool plDynamicWalkingStrategy::ICheckForGround(hsScalar& zVelocity) |
|
{ |
|
std::vector<plControllerSweepRecord> groundHits; |
|
UInt32 collideFlags = 1<<plSimDefs::kGroupStatic | 1<<plSimDefs::kGroupAvatarBlocker | 1<<plSimDefs::kGroupDynamic; |
|
|
|
hsPoint3 startPos; |
|
fController->GetPositionSim(startPos); |
|
hsPoint3 endPos = startPos; |
|
|
|
// Set sweep length |
|
startPos.fZ += 0.05f; |
|
endPos.fZ -= 0.05f; |
|
|
|
int possiblePlatformCount = fController->SweepControllerPath(startPos, endPos, true, true, collideFlags, groundHits); |
|
if (possiblePlatformCount) |
|
{ |
|
zVelocity = -FLT_MAX; |
|
|
|
std::vector<plControllerSweepRecord>::iterator curRecord; |
|
for (curRecord = groundHits.begin(); curRecord != groundHits.end(); ++curRecord) |
|
{ |
|
if (curRecord->ObjHit != nil) |
|
{ |
|
hsVector3 objVelocity; |
|
curRecord->ObjHit->GetLinearVelocitySim(objVelocity); |
|
if (objVelocity.fZ > zVelocity) |
|
zVelocity = objVelocity.fZ; |
|
|
|
fGroundHit = true; |
|
} |
|
} |
|
} |
|
|
|
return fGroundHit; |
|
} |
|
|
|
|
|
////////////////////////////////////////////////////////////////////////// |
|
|
|
/* |
|
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; |
|
} |
|
|
|
|