/*==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 . 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<IsSeeking()) collideFlags |= (1<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<IsSeeking()) collideFlags |= (1<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 groundHits; UInt32 collideFlags = 1<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::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; }