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.

1265 lines
43 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==*/
#include "plPXPhysicalController.h"
#include "plSimulationMgr.h"
#include "plPXPhysical.h"
#include "plPXConvert.h"
#include "pnSceneObject/plSimulationInterface.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnMessage/plCorrectionMsg.h"
#include "plAvatar/plArmatureMod.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "plDrawable/plDrawableGenerator.h"
#include "plPhysical/plPhysicalProxy.h"
#include "pnMessage/plSetNetGroupIDMsg.h
#include "plSurface/hsGMaterial.h"
#include "plSurface/plLayerInterface.h""
#include "plMessage/plCollideMsg.h"
#include <NxPhysics.h>
#include <ControllerManager.h>
#include <NxCapsuleController.h>
#include <NxCapsuleShape.h>
#define kPhysxSkinWidth 0.1f
#define kPhysZOffset ((fRadius + (fHeight / 2)) + kPhysxSkinWidth)
#define kSLOPELIMIT (cosf(NxMath::degToRad(55.f)))
//#define kPhysicalHeightFudge 0.4f // this fudge was used for PhysX 2.4
#define kPhysicalHeightFudge 0.0f
//#define STEP_OFFSET 1.0f
#define STEP_OFFSET 0.5f
//#define STEP_OFFSET 0.15f
#ifndef PLASMA_EXTERNAL_RELEASE
#include "plPipeline/plDebugText.h"
hsBool plPXPhysicalController::fDebugDisplay = false;
#endif // PLASMA_EXTERNAL_RELEASE
static ControllerManager gControllerMgr;
static std::vector<plPXPhysicalController*> gControllers;
static bool gRebuildCache = false;
// KLUDGE: From plPXPhysical.cpp
bool CompareMatrices(const hsMatrix44 &matA, const hsMatrix44 &matB, float tolerance);
plPhysicalController* plPhysicalController::Create(plKey ownerSO, hsScalar height, hsScalar width)
{
hsScalar radius = width / 2.f;
//hsScalar realHeight = height - width;
hsScalar realHeight = height - radius + kPhysicalHeightFudge;
return TRACKED_NEW plPXPhysicalController(ownerSO, radius, realHeight);
}
//////////////////////////////////////////////////////////////////////////
plPXPhysicalController* plPXPhysicalController::FindController(NxController* controller)
{
for (int i = 0; i < gControllers.size(); i++)
{
plPXPhysicalController* ac = gControllers[i];
if (ac->fController == controller)
return ac;
}
return nil;
}
plPXPhysicalController* plPXPhysicalController::GetController(NxActor& actor, bool* isController)
{
*isController = false;
for (int i = 0; i < gControllers.size(); i++)
{
plPXPhysicalController* ac = gControllers[i];
if (ac->fController && ac->fController->getActor() == &actor)
{
*isController = true;
return ac;
}
if ( ac->fKinematicActor == &actor)
{
return ac;
}
}
return nil;
}
void plPXPhysicalController::GetWorldSpaceCapsule(NxCapsule& cap)
{
if(fController){
int numshapes=fController->getActor()->getNbShapes();
if (numshapes==1)
{//there should only be one shape on a controller
NxShape* const *shapes=fController->getActor()->getShapes();
//and since it is a capsule controller it better be a capsule;
NxCapsuleShape *capShape = shapes[0]->isCapsule();
if(capShape) capShape->getWorldCapsule(cap);
}
}
}
bool plPXPhysicalController::AnyControllersInThisWorld(plKey world)
{
for (int i = 0; i < gControllers.size(); i++)
{
plPXPhysicalController* ac = gControllers[i];
if (ac->GetSubworld() == world)
return true;
}
return false;
}
int plPXPhysicalController::NumControllers()
{
return gControllers.size();
}
int plPXPhysicalController::GetControllersInThisSubWorld(plKey world, int maxToReturn,plPXPhysicalController** bufferout)
{
int i=0;
for (int j=0;j<gControllers.size();j++)
{
plPXPhysicalController* ac = gControllers[i];
if (ac->GetSubworld()==world)
{
if(i<maxToReturn)
{
bufferout[i]=ac;
i++;
}
}
}
return i;
}
int plPXPhysicalController::GetNumberOfControllersInThisSubWorld(plKey world)
{
int i=0;
for (int j=0;j<gControllers.size();j++)
{
plPXPhysicalController* ac = gControllers[i];
if (ac->GetSubworld()==world)i++;
}
return i;
}
void plPXPhysicalController::Update(bool prestep, hsScalar delSecs)
{
// Apparently the user data field of the controllers is broken
// UInt32 count = gControllerMgr.getNbControllers();
// NxController* controllers = (NxController*)gControllerMgr.getControllers();
//
// for (int i = 0; i < count; i++)
// {
// plPXPhysicalController* ac = (plPXPhysicalController*)controllers[i].getAppData();
for (int i = 0; i < gControllers.size(); i++)
{
plPXPhysicalController* ac = gControllers[i];
hsAssert(ac, "Bad avatar controller");
if (prestep)
{
if (gRebuildCache)
ac->fController->reportSceneChanged();
ac->IApply(delSecs);
}
else
{
gControllerMgr.updateControllers();
ac->ISendUpdates(delSecs);
if (ac->GetSubworldCI())
ac->fPrevSubworldW2L = ac->GetSubworldCI()->GetWorldToLocal();
else
{
if (!ac->fPrevSubworldW2L.IsIdentity())
ac->fPrevSubworldW2L.Reset();
}
}
}
gRebuildCache = false;
}
void plPXPhysicalController::RebuildCache()
{
gRebuildCache = true;
}
void plPXPhysicalController::IInformDetectors(bool entering)
{
static const NxU32 DetectorFlag= 1<<plSimDefs::kGroupDetector;
if (fController)
{
NxScene* scene = plSimulationMgr::GetInstance()->GetScene(fWorldKey);
int kNumofShapesToStore=30;
NxCapsule cap;
GetWorldSpaceCapsule(cap);
NxShape* shapes[30];
int numCollided=scene->overlapCapsuleShapes(cap,NX_ALL_SHAPES,kNumofShapesToStore,shapes,NULL,DetectorFlag,NULL,true);
for (int i=0;i<numCollided;i++)
{
NxActor* myactor=&(shapes[i]->getActor());
if (myactor)
{
plPXPhysical* physical = (plPXPhysical*)myactor->userData;
if (physical)
{
plCollideMsg* msg = TRACKED_NEW plCollideMsg;
msg->fOtherKey = fOwner;
msg->fEntering = entering;
msg->AddReceiver(physical->GetKey());
msg->Send();
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
class PXControllerHitReport : public NxUserControllerHitReport
{
public:
virtual NxControllerAction onShapeHit(const NxControllerShapeHit& hit)
{
plPXPhysicalController* ac = plPXPhysicalController::FindController(hit.controller);
NxActor& actor = hit.shape->getActor();
plPXPhysical* phys = (plPXPhysical*)actor.userData;
static hsScalar SlopeLimit = kSLOPELIMIT;
hsVector3 normal = plPXConvert::Vector(hit.worldNormal);
hsScalar dot = normal * kAvatarUp;
if ( dot < SlopeLimit )
ac->AddSlidingNormal(normal);
else
ac->GroundHit();
#ifndef PLASMA_EXTERNAL_RELEASE
plDbgCollisionInfo info;
info.fNormal = normal;
info.fSO = plSceneObject::ConvertNoRef(phys->GetObjectKey()->ObjectIsLoaded());
info.fOverlap = false;
NxShape* const *shapes = hit.controller->getActor()->getShapes();
int numShapes = hit.controller->getActor()->getNbShapes();
int i;
for (i = 0; i < numShapes; i++)
{
// should only be one capsule shape
const NxCapsuleShape *capShape = shapes[i]->isCapsule();
if (capShape)
{
NxCapsule cap;
capShape->getWorldCapsule(cap);
if (hit.shape->checkOverlapCapsule(cap))
info.fOverlap = true;
}
}
ac->fDbgCollisionInfo.Append(info);
#endif PLASMA_EXTERNAL_RELEASE
// If the avatar hit a movable physical, apply some force to it.
if (actor.isDynamic() )
{
if ( !actor.readBodyFlag(NX_BF_KINEMATIC) && !actor.readBodyFlag(NX_BF_FROZEN))
{
// If this is the local avatar, we need to take ownership of this
// dynamic if we haven't already
if (ac->fLOSDB == plSimDefs::kLOSDBLocalAvatar && !phys->IsLocallyOwned() &&
!phys->GetProperty(plSimulationInterface::kNoOwnershipChange))
{
plSynchedObject* obj = plSynchedObject::ConvertNoRef(phys->GetObjectKey()->ObjectIsLoaded());
obj->SetNetGroupConstant(plNetGroup::kNetGroupLocalPhysicals);
// Tell all the other clients that we own this physical
plSetNetGroupIDMsg* setNetGroupID = TRACKED_NEW plSetNetGroupIDMsg;
setNetGroupID->fId = plNetGroup::kNetGroupRemotePhysicals;
setNetGroupID->SetBCastFlag(plMessage::kNetPropagate | plMessage::kNetForce);
setNetGroupID->SetBCastFlag(plMessage::kLocalPropagate, false);
setNetGroupID->Send(obj->GetKey());
}
plSimulationMgr::GetInstance()->ConsiderSynch(phys, nil);
hsVector3 dir = plPXConvert::Vector(hit.dir);
// We only allow horizontal pushes. Vertical pushes when we stand on
// dynamic objects creates useless stress on the solver.
if (dir.fZ < 0)
{
dir.fZ = 0;
dir.Normalize();
}
if (!dir.IsEmpty())
{
static hsScalar kForceScale = 5.f;
//static hsScalar kForceScale = 4.f;
NxF32 coeff = actor.getMass() * hit.length * kForceScale;
hsPoint3 pos((hsScalar)hit.worldPos.x, (hsScalar)hit.worldPos.y, (hsScalar)hit.worldPos.z);
phys->SetHitForce((dir*coeff), pos);
}
}
}
else // else if the avatar hit a static
{
return NX_ACTION_NONE;
}
if (phys && phys->GetProperty(plSimulationInterface::kAvAnimPushable))
{
hsQuat inverseRotation = ac->fLocalRotation.Inverse();
hsVector3 normal = plPXConvert::Vector(hit.worldNormal);
ac->fPushingPhysical = phys;
ac->fFacingPushingPhysical = (inverseRotation.Rotate(&kAvatarForward).InnerProduct(normal) < 0 ? true : false);
}
return NX_ACTION_NONE;
}
virtual NxControllerAction onControllerHit(const NxControllersHit& hit)
{
return NX_ACTION_NONE;
}
} gMyReport;
//////////////////////////////////////////////////////////////////////////
const hsScalar plPXPhysicalController::kAirTimeThreshold = .1f; // seconds
plPXPhysicalController::plPXPhysicalController(plKey ownerSO, hsScalar radius, hsScalar height)
: fOwner(ownerSO)
, fWorldKey(nil)
, fRadius(radius)
, fHeight(height)
, fController(nil)
, fLinearVelocity(0, 0, 0)
, fAngularVelocity(0)
, fAchievedLinearVelocity(0, 0, 0)
, fLocalPosition(0, 0, 0)
, fLocalRotation(0, 0, 0, 1)
, fEnable(true)
, fEnableChanged(false)
, fLOSDB(plSimDefs::kLOSDBNone)
, fGroundHit(false)
, fFalseGround(false)
, fTimeInAir(0)
, fPushingPhysical(nil)
, fFacingPushingPhysical(false)
, fProxyGen(nil)
, fKinematicActor(nil)
, fKinematic(false)
, fKinematicChanged(false)
, fKinematicEnableNextUpdate(false)
, fHitHead(false)
{
gControllers.push_back(this);
fLastGlobalLoc.Reset();
ICreateController();
Enable(false);
}
plPXPhysicalController::~plPXPhysicalController()
{
IDeleteController();
for (int i = 0; i < gControllers.size(); i++)
{
if (gControllers[i] == this)
{
gControllers.erase(gControllers.begin()+i);
break;
}
}
delete fProxyGen;
}
// WARNING: If this is an armatureMod, it'll have its own idea about when
// physics should be enabled/disabled. Use plArmatureModBase::EnablePhysics() instead.
void plPXPhysicalController::Enable(bool enable)
{
if (fEnable != enable)
{
fEnable = enable;
if (fEnable)
fEnableChanged = true;
else
{
// See ISendUpdates for why we don't re-enable right away
fController->setCollision(fEnable);
}
}
}
void plPXPhysicalController::AddSlidingNormal(hsVector3 vec)
{
// We get lots of duplicates, so check.
int i;
for (i = 0; i < fSlidingNormals.GetCount(); i++)
{
if (hsABS(fSlidingNormals[i].fX - vec.fX) <= .01 &&
hsABS(fSlidingNormals[i].fY - vec.fY) <= .01 &&
hsABS(fSlidingNormals[i].fZ - vec.fZ) <= .01)
{
return;
}
}
fSlidingNormals.Append(vec);
}
void plPXPhysicalController::IGetPositionSim(hsPoint3& pos) const
{
const NxExtendedVec3& nxPos = fController->getPosition();
pos.Set(hsScalar(nxPos.x), hsScalar(nxPos.y), hsScalar(nxPos.z) - kPhysZOffset);
}
void plPXPhysicalController::SetSubworld(plKey world)
{
if (fWorldKey != world)
{
bool wasEnabled = fEnable;
#ifdef USE_PHYSX_CONVEXHULL_WORKAROUND
// PHYSX FIXME - before leaving this world, sending leaving detector events if we are inside a convex hull detector
hsPoint3 pos;
IGetPositionSim(pos);
plSimulationMgr::GetInstance()->UpdateDetectorsInScene(fWorldKey,GetOwner(),pos,false);
#endif // USE_PHYSX_CONVEXHULL_WORKAROUND
//need to inform detectors in the old world that we are leaving
//IInformDetectors(false);
//done informing old world
IDeleteController();
fWorldKey = world;
ICreateController();
if (wasEnabled)
Enable(false);
// need to disable the kinematic also so that it doesn't trip over random detector regions
fKinematicActor->raiseActorFlag(NX_AF_DISABLE_COLLISION);
hsMatrix44 globalLoc = fLastGlobalLoc;
if (GetSubworldCI())
fPrevSubworldW2L = GetSubworldCI()->GetWorldToLocal();
ISetGlobalLoc(globalLoc);
// we need to let the re-enable code put thing in order... so that 0,0,0 is not triggered and ISendUpdates do the enable and update detectors
if (wasEnabled)
Enable(true);
// and then re-enable the kinematic on the next update (ISendUpdates)
fKinematicEnableNextUpdate = true;
plPXPhysicalController::RebuildCache();
}
}
const plCoordinateInterface* plPXPhysicalController::GetSubworldCI() const
{
if (fWorldKey)
{
plSceneObject* so = plSceneObject::ConvertNoRef(fWorldKey->ObjectIsLoaded());
if (so)
return so->GetCoordinateInterface();
}
return nil;
}
void plPXPhysicalController::GetState(hsPoint3& pos, float& zRot)
{
// Temporarily use the position point while we get the z rotation
fLocalRotation.NormalizeIfNeeded();
fLocalRotation.GetAngleAxis(&zRot, (hsVector3*)&pos);
if (pos.fZ < 0)
zRot = (2 * hsScalarPI) - zRot; // axis is backwards, so reverse the angle too
pos = fLocalPosition;
}
void plPXPhysicalController::SetState(const hsPoint3& pos, float zRot)
{
plSceneObject* so = plSceneObject::ConvertNoRef(fOwner->ObjectIsLoaded());
if (so)
{
hsQuat worldRot;
hsVector3 zAxis(0.f, 0.f, 1.f);
worldRot.SetAngleAxis(zRot, zAxis);
hsMatrix44 l2w, w2l;
worldRot.MakeMatrix(&l2w);
l2w.SetTranslate(&pos);
// Localize new position and rotation to global coords if we're in a subworld
const plCoordinateInterface* ci = GetSubworldCI();
if (ci)
{
const hsMatrix44& subworldL2W = ci->GetLocalToWorld();
l2w = subworldL2W * l2w;
}
l2w.GetInverse(&w2l);
so->SetTransform(l2w, w2l);
so->FlushTransform();
}
}
void plPXPhysicalController::ISetGlobalLoc(const hsMatrix44& l2w)
{
fLastGlobalLoc = l2w;
// Update our subworld position and rotation
const plCoordinateInterface* subworldCI = GetSubworldCI();
if (subworldCI)
{
const hsMatrix44& w2s = fPrevSubworldW2L;
hsMatrix44 l2s = w2s * l2w;
l2s.GetTranslate(&fLocalPosition);
fLocalRotation.SetFromMatrix44(l2s);
}
else
{
l2w.GetTranslate(&fLocalPosition);
fLocalRotation.SetFromMatrix44(l2w);
}
hsMatrix44 w2l;
l2w.GetInverse(&w2l);
if (fProxyGen)
fProxyGen->SetTransform(l2w, w2l);
// Update the physical position
NxExtendedVec3 nxPos(fLocalPosition.fX, fLocalPosition.fY, fLocalPosition.fZ + kPhysZOffset);
fController->setPosition(nxPos);
IMatchKinematicToController();
}
void plPXPhysicalController::IMatchKinematicToController()
{
if ( fKinematicActor)
{
NxExtendedVec3 cPos = fController->getPosition();
NxVec3 prevKinPos = fKinematicActor->getGlobalPosition();
NxVec3 kinPos;
kinPos.x = (NxReal)cPos.x;
kinPos.y = (NxReal)cPos.y;
kinPos.z = (NxReal)cPos.z;
if (plSimulationMgr::fExtraProfile)
SimLog("Match setting kinematic from %f,%f,%f to %f,%f,%f",prevKinPos.x,prevKinPos.y,prevKinPos.z,kinPos.x,kinPos.y,kinPos.z );
fKinematicActor->setGlobalPosition(kinPos);
}
}
void plPXPhysicalController::IMoveKinematicToController(hsPoint3& pos)
{
if ( fKinematicActor)
{
NxVec3 kinPos = fKinematicActor->getGlobalPosition();
if ( abs(kinPos.x-pos.fX) + abs(kinPos.y-pos.fY) + (abs(kinPos.z-pos.fZ-kPhysZOffset)) > 0.0001f)
{
NxVec3 newPos;
newPos.x = (NxReal)pos.fX;
newPos.y = (NxReal)pos.fY;
newPos.z = (NxReal)pos.fZ+kPhysZOffset;
if (fEnable || fKinematic)
{
if (plSimulationMgr::fExtraProfile)
SimLog("Moving kinematic from %f,%f,%f to %f,%f,%f",pos.fX,pos.fY,pos.fZ+kPhysZOffset,kinPos.x,kinPos.y,kinPos.z );
// use the position
fKinematicActor->moveGlobalPosition(newPos);
}
else
{
if (plSimulationMgr::fExtraProfile)
SimLog("Setting kinematic from %f,%f,%f to %f,%f,%f",pos.fX,pos.fY,pos.fZ+kPhysZOffset,kinPos.x,kinPos.y,kinPos.z );
fKinematicActor->setGlobalPosition(newPos);
}
}
}
}
void plPXPhysicalController::ISetKinematicLoc(const hsMatrix44& l2w)
{
hsPoint3 kPos;
// Update our subworld position and rotation
const plCoordinateInterface* subworldCI = GetSubworldCI();
if (subworldCI)
{
const hsMatrix44& w2s = subworldCI->GetWorldToLocal();
hsMatrix44 l2s = w2s * l2w;
l2s.GetTranslate(&kPos);
}
else
{
l2w.GetTranslate(&kPos);
}
hsMatrix44 w2l;
l2w.GetInverse(&w2l);
if (fProxyGen)
fProxyGen->SetTransform(l2w, w2l);
// add z offset
kPos.fZ += kPhysZOffset;
// Update the physical position of kinematic
if (fEnable || fKinematic)
fKinematicActor->moveGlobalPosition(plPXConvert::Point(kPos));
else
fKinematicActor->setGlobalPosition(plPXConvert::Point(kPos));
}
void plPXPhysicalController::Kinematic(bool state)
{
if (fKinematic != state)
{
fKinematic = state;
if (fKinematic)
{
// See ISendUpdates for why we don't re-enable right away
fController->setCollision(false);
#ifdef PHYSX_KINEMATIC_IS_DISABLED
fKinematicActor->clearActorFlag(NX_AF_DISABLE_COLLISION);
#endif
}
else
{
fKinematicChanged = true;
}
}
}
bool plPXPhysicalController::IsKinematic()
{
if (fKinematicActor)
{
#ifdef PHYSX_KINEMATIC_IS_DISABLED
if (!fKinematicActor->readActorFlag(NX_AF_DISABLE_COLLISION))
return true;
#else
return fKinematic;
#endif
}
return false;
}
void plPXPhysicalController::GetKinematicPosition(hsPoint3& pos)
{
pos.Set(-1,-1,-1);
if ( fKinematicActor )
{
NxVec3 klPos = fKinematicActor->getGlobalPosition();
pos.Set(hsScalar(klPos.x), hsScalar(klPos.y), hsScalar(klPos.z) - kPhysZOffset);
}
}
void plPXPhysicalController::IApply(hsScalar delSecs)
{
/*static const UInt32 collideFlags =
1<<plSimDefs::kGroupStatic |
1<<plSimDefs::kGroupAvatarBlocker |
1<<plSimDefs::kGroupDynamic;*/
UInt32 collideFlags =
1<<plSimDefs::kGroupStatic |
1<<plSimDefs::kGroupAvatarBlocker |
1<<plSimDefs::kGroupDynamic;
if(!fSeeking)
{
collideFlags|=(1<<plSimDefs::kGroupExcludeRegion);
}
int i;
if (fKinematic)
{
// first apply sceneobject update to the kinematic
plSceneObject* so = plSceneObject::ConvertNoRef(fOwner->ObjectIsLoaded());
if (so)
{
// If we've been moved since the last physics update (somebody warped us),
// update the physics before we apply velocity.
const hsMatrix44& l2w = so->GetCoordinateInterface()->GetLocalToWorld();
if (!CompareMatrices(l2w, fLastGlobalLoc, .0001f))
{
ISetKinematicLoc(l2w);
}
}
// then jump out
return;
}
if (!fEnable)
return;
bool gotGroundHit = fGroundHit;
fGroundHit = false;
fPushingPhysical = nil;
fFacingPushingPhysical = false;
plSceneObject* so = plSceneObject::ConvertNoRef(fOwner->ObjectIsLoaded());
if (so)
{
// If we've been moved since the last physics update (somebody warped us),
// update the physics before we apply velocity.
const hsMatrix44& l2w = so->GetCoordinateInterface()->GetLocalToWorld();
if (!CompareMatrices(l2w, fLastGlobalLoc, .0001f))
ISetGlobalLoc(l2w);
// Convert our avatar relative velocity to subworld relative
if (!fLinearVelocity.IsEmpty())
{
fLinearVelocity = l2w * fLinearVelocity;
const plCoordinateInterface* subworldCI = GetSubworldCI();
if (subworldCI)
fLinearVelocity = subworldCI->GetWorldToLocal() * fLinearVelocity;
}
// Add in gravity if the avatar's z velocity isn't being set explicitly
// (Add in a little fudge factor, since the animations usually add a
// tiny bit of z.)
if (hsABS(fLinearVelocity.fZ) < 0.001f)
{
static const float kGravity = -32.f;
// 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 = fAchievedLinearVelocity.fZ;
if (IsOnGround())
prevZVel = hsMinimum(prevZVel, 0.f);
hsScalar grav = kGravity * delSecs;
// If our gravity contribution isn't high enough this frame, we won't
// report a collision even when standing on solid ground.
hsScalar maxGrav = -.001f / delSecs;
if (grav > maxGrav)
grav = maxGrav;
fLinearVelocity.fZ = prevZVel + grav;
// Technically this is nonsensical and wrong, capping our velocity to
// an accelleration constant. But no one seems to really mind.
if (fLinearVelocity.fZ < kGravity)
fLinearVelocity.fZ = kGravity;
}
// If we're airborne and the velocity isn't set, use the velocity from
// the last frame so we maintain momentum.
if (!IsOnGround() && fLinearVelocity.fX == 0.f && fLinearVelocity.fY == 0.f)
{
fLinearVelocity.fX = fAchievedLinearVelocity.fX;
fLinearVelocity.fY = fAchievedLinearVelocity.fY;
}
if (!IsOnGround() || IsOnFalseGround())
{
// 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.f, 0.f, 0.f);
for (i = 0; i < fSlidingNormals.GetCount(); i++)
{
offset += fSlidingNormals[i];
hsVector3 velNorm = fLinearVelocity;
if (velNorm.MagnitudeSquared() > 0)
velNorm.Normalize();
if (velNorm * fSlidingNormals[i] < 0)
{
hsVector3 proj = (velNorm % fSlidingNormals[i]) % fSlidingNormals[i];
if (velNorm * proj < 0)
proj *= -1.f;
fLinearVelocity = fLinearVelocity.Magnitude() * proj;
}
}
if (offset.MagnitudeSquared() > 0)
{
// 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();
fLinearVelocity += offset * 5;
}
}
// Scale the velocity to our actual step size (by default it's feet/sec)
NxVec3 vel(fLinearVelocity.fX * delSecs, fLinearVelocity.fY * delSecs, fLinearVelocity.fZ * delSecs);
NxU32 colFlags = 0;
fGroundHit = false;
fFalseGround = false;
fSlidingNormals.Swap(fPrevSlidingNormals);
fSlidingNormals.SetCount(0);
#ifndef PLASMA_EXTERNAL_RELEASE
fDbgCollisionInfo.SetCount(0);
#endif // PLASMA_EXTERNAL_RELEASE
fController->move(vel, collideFlags, 0.0001, colFlags);
ICheckForFalseGround();
/*If the Physx controller thinks we have a collision from below, need to make sure we
have at least have false ground, otherwise Autostepping can send us into the air, and we will some times
float/panic link. For some reason the NxControllerHitReport does not always send messages
regarding Controller contact with ground plane, but will (almost) always return NXCC_COLLISION_DOWN
with the move method.
*/
if(((colFlags&NXCC_COLLISION_DOWN )==NXCC_COLLISION_DOWN )&&(fGroundHit==false))
{
fFalseGround=true;
}
/*
The top sphere half was hit, but the ControllerHit Report doesn't know
In IUpdate fHitHead will be used to keep from gaining unrealistic velocity in the x&y Direction
*/
if(colFlags&NXCC_COLLISION_UP)
{
fHitHead=true;
}
#ifndef PLASMA_EXTERNAL_RELEASE
if (fDebugDisplay)
IDrawDebugDisplay();
#endif // PLASMA_EXTERNAL_RELEASE
}
}
void plPXPhysicalController::ISendUpdates(hsScalar delSecs)
{
if (!fEnable || fKinematic)
{
// When we're in non-phys or a behavior we can't go through the rest of the function
// so we need to get out early, but we need to update the current position if we're
// in a subworld.
plSceneObject* so = plSceneObject::ConvertNoRef(fOwner->ObjectIsLoaded());
const plCoordinateInterface* ci = GetSubworldCI();
if (ci && so)
{
const hsMatrix44& soL2W = so->GetCoordinateInterface()->GetLocalToWorld();
const hsMatrix44& ciL2W = ci->GetLocalToWorld();
hsMatrix44 l2w = fPrevSubworldW2L * soL2W;
l2w = ciL2W * l2w;
hsMatrix44 w2l;
l2w.GetInverse(&w2l);
((plCoordinateInterface*)so->GetCoordinateInterface())->SetTransform(l2w, w2l);
((plCoordinateInterface*)so->GetCoordinateInterface())->FlushTransform();
ISetGlobalLoc(l2w);
}
return;
}
// PhysX loves to cache stuff. However, it doesn't like to update it (see
// the RebuildCache crap above). Say the avatar is disabled and sitting at
// point 0,0,0. We warp him to some other position and enable him. If you
// do the enable before the sim step is done, regardless of whether you move
// him first, he will send out a penetration with any detector at 0,0,0. As
// far as I can tell there's no way around this, and I tried a lot of things.
// The only solution I found is to move the avatar, run the sim step, then
// enable him. This means he won't trigger any detectors at his new position
// until the next frame, but hopefully that won't be too noticeable.
if (fEnableChanged)
{
fEnableChanged = false;
fController->setCollision(fEnable);
#ifdef USE_PHYSX_CONVEXHULL_WORKAROUND
// PHYSX FIXME - after re-enabling check to see if we are inside any convex hull detector regions
hsPoint3 pos;
IGetPositionSim(pos);
plSimulationMgr::GetInstance()->UpdateDetectorsInScene(fWorldKey,GetOwner(),pos,fEnable);
#endif // USE_PHYSX_CONVEXHULL_WORKAROUND
//IInformDetectors(true);
}
if (fKinematicChanged)
{
fKinematicChanged = false;
fController->setCollision(true);
#ifdef PHYSX_KINEMATIC_IS_DISABLED
fKinematicActor->raiseActorFlag(NX_AF_DISABLE_COLLISION);
#endif // PHYSX_KINEMATIC_IS_DISABLED
}
if (fKinematicEnableNextUpdate)
{
fKinematicActor->clearActorFlag(NX_AF_DISABLE_COLLISION);
fKinematicEnableNextUpdate = false;
}
if (!fGroundHit && !fFalseGround)
fTimeInAir += delSecs;
else
fTimeInAir = 0.f;
plSceneObject* so = plSceneObject::ConvertNoRef(fOwner->ObjectIsLoaded());
if (so)
{
// Get the current position of the physical
hsPoint3 curLocalPos;
IGetPositionSim(curLocalPos);
IMoveKinematicToController(curLocalPos);
{
const hsMatrix44& w2l = so->GetCoordinateInterface()->GetLocalToWorld();
fAchievedLinearVelocity = hsVector3(curLocalPos - fLocalPosition);
fAchievedLinearVelocity /= delSecs;
}
/*if we hit our head the sweep api might try to
move us laterally to go as high as we requested kind of like autostep, to top it off the
way the NxCharacter and the sweep api work as a whole NxControllerHitReport::OnShapeHit
wont be called regarding the head blow.
if we are airborne: with out this we will gain large amounts of velocity in the x y plane
and on account of fAchievedLinearVelocity being used in the next step we will fly sideways
*/
bool headhit=fHitHead;
fHitHead=false;
if(headhit&&!(fGroundHit||fFalseGround))
{
//we have hit our head and we don't have anything beneath our feet
//not really friction just a way to make it seem more realistic keep between 0 and 1
hsScalar headFriction=0.0;
fAchievedLinearVelocity.fX=(1.0-headFriction)*fLinearVelocity.fX;
fAchievedLinearVelocity.fY=(1.0-headFriction)*fLinearVelocity.fY;
//only clamping when hitting head and going upwards, if going down leave it be
// this should only occur when going down stairwells with low ceilings like in cleft
//kitchen area
if(fAchievedLinearVelocity.fZ>0.0)
{
fAchievedLinearVelocity.fZ=0.0;
}
}
// Apply angular velocity
if (fAngularVelocity != 0.f)
{
hsScalar angle;
hsVector3 axis;
fLocalRotation.NormalizeIfNeeded();
fLocalRotation.GetAngleAxis(&angle, &axis);
// adjust it (quaternions are weird...)
if (axis.fZ < 0)
angle = (2*hsScalarPI) - angle; // axis is backwards, so reverse the angle too
angle += fAngularVelocity * delSecs;
// make sure we wrap around
if (angle < 0)
angle = (2*hsScalarPI) + angle; // angle is -, so this works like a subtract
if (angle >= (2*hsScalarPI))
angle = angle - (2*hsScalarPI);
// and set the new angle
fLocalRotation.SetAngleAxis(angle, hsVector3(0,0,1));
}
// We can't only send updates when the physical position changes because the
// world relative position may be changing all the time if we're in a subworld.
// if (curLocalPos != fLocalPosition || fAngularVelocity != 0.f)
{
fLocalPosition = curLocalPos;
// Apply rotation and translation
fLocalRotation.MakeMatrix(&fLastGlobalLoc);
fLastGlobalLoc.SetTranslate(&fLocalPosition);
// Localize to global coords if in a subworld
const plCoordinateInterface* ci = GetSubworldCI();
if (ci)
{
const hsMatrix44& l2w = ci->GetLocalToWorld();
fLastGlobalLoc = l2w * fLastGlobalLoc;
}
plCorrectionMsg* corrMsg = TRACKED_NEW plCorrectionMsg;
corrMsg->fLocalToWorld = fLastGlobalLoc;
corrMsg->fLocalToWorld.GetInverse(&corrMsg->fWorldToLocal);
corrMsg->fDirtySynch = true;
hsMatrix44 w2l;
fLastGlobalLoc.GetInverse(&w2l);
//if (fProxyGen)
// fProxyGen->SetTransform(fLastGlobalLoc, w2l);
// Send the new position to the plArmatureMod and the scene object
const plArmatureMod* armMod = plArmatureMod::ConvertNoRef(so->GetModifierByType(plArmatureMod::Index()));
if (armMod)
corrMsg->AddReceiver(armMod->GetKey());
corrMsg->AddReceiver(fOwner);
corrMsg->Send();
}
}
fLinearVelocity.Set(0, 0, 0);
fAngularVelocity = 0;
}
void plPXPhysicalController::ICheckForFalseGround()
{
if (fGroundHit)
return; // Already collided with "real" ground.
// 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.
int i, j;
const hsScalar threshold = hsScalarDegToRad(240);
int numContacts = fSlidingNormals.GetCount() + fPrevSlidingNormals.GetCount();
if (numContacts >= 2)
{
// For extra fun... PhysX will actually report some collisions every other frame, as though
// we're bouncing back and forth between the two (or more) objects blocking us. So it's not
// enough to look at this frame's collisions, we have to check previous frames too.
hsTArray<hsScalar> fCollisionAngles;
fCollisionAngles.SetCount(numContacts);
int angleIdx = 0;
for (i = 0; i < fSlidingNormals.GetCount(); i++, angleIdx++)
{
fCollisionAngles[angleIdx] = hsATan2(fSlidingNormals[i].fY, fSlidingNormals[i].fX);
}
for (i = 0; i < fPrevSlidingNormals.GetCount(); i++, angleIdx++)
{
fCollisionAngles[angleIdx] = hsATan2(fPrevSlidingNormals[i].fY, fPrevSlidingNormals[i].fX);
}
// 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])
{
hsScalar 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 * hsScalarPI)))
fFalseGround = true;
}
}
}
void plPXPhysicalController::ICreateController()
{
NxScene* scene = plSimulationMgr::GetInstance()->GetScene(fWorldKey);
NxCapsuleControllerDesc desc;
desc.position.x = 0;
desc.position.y = 0;
desc.position.z = 0;
desc.upDirection = NX_Z;
desc.slopeLimit = kSLOPELIMIT;
desc.skinWidth = kPhysxSkinWidth;
desc.stepOffset = STEP_OFFSET;
desc.callback = &gMyReport;
desc.userData = this;
desc.radius = fRadius;
desc.height = fHeight;
desc.interactionFlag = NXIF_INTERACTION_EXCLUDE;
//desc.interactionFlag = NXIF_INTERACTION_INCLUDE;
fController = (NxCapsuleController*)gControllerMgr.createController(scene, desc);
// Change the avatars shape groups. The avatar doesn't actually use these when
// it's determining collision, but if you're standing still and an object runs
// into you, it'll pass through without this.
NxActor* actor = fController->getActor();
NxShape* shape = actor->getShapes()[0];
shape->setGroup(plSimDefs::kGroupAvatar);
// need to create the non-bouncing object that can be used to trigger things while the avatar is doing behaviors.
NxActorDesc actorDesc;
NxCapsuleShapeDesc capDesc;
capDesc.radius = fRadius;
capDesc.height = fHeight;
capDesc.group = plSimDefs::kGroupAvatar;
actorDesc.shapes.pushBack(&capDesc);
NxBodyDesc bodyDesc;
bodyDesc.mass = 1.f;
actorDesc.body = &bodyDesc;
bodyDesc.flags |= NX_BF_KINEMATIC;
actorDesc.name = "AvatarTriggerKinematicGuy";
fSeeking=false;
try
{
fKinematicActor = scene->createActor(actorDesc);
} catch (...)
{
hsAssert(false, "Actor creation crashed");
}
#ifdef PHYSX_KINEMATIC_IS_DISABLED
// initially start as in-active
fKinematicActor->raiseActorFlag(NX_AF_DISABLE_COLLISION);
#endif
// set the matrix to be the same as the controller's actor... that should orient it to be the same
fKinematicActor->setGlobalPose(actor->getGlobalPose());
// the proxy for the debug display
//hsAssert(!fProxyGen, "Already have proxy gen, double read?");
hsColorRGBA physColor;
hsScalar opac = 1.0f;
// local avatar is light purple and transparent
physColor.Set(.2f, .1f, .2f, 1.f);
opac = 0.8f;
// the avatar proxy doesn't seem to work... not sure why?
fProxyGen = TRACKED_NEW plPhysicalProxy(hsColorRGBA().Set(0,0,0,1.f), physColor, opac);
fProxyGen->Init(this);
}
void plPXPhysicalController::IDeleteController()
{
if (fController)
{
gControllerMgr.releaseController(*fController);
fController = nil;
if (fKinematicActor)
{
NxScene* scene = plSimulationMgr::GetInstance()->GetScene(fWorldKey);
scene->releaseActor(*fKinematicActor);
fKinematicActor = nil;
}
plSimulationMgr::GetInstance()->ReleaseScene(fWorldKey);
}
}
// Make a visible object that can be viewed by users for debugging purposes.
plDrawableSpans* plPXPhysicalController::CreateProxy(hsGMaterial* mat, hsTArray<UInt32>& idx, plDrawableSpans* addTo)
{
plDrawableSpans* myDraw = addTo;
hsBool blended = ((mat->GetLayer(0)->GetBlendFlags() & hsGMatState::kBlendMask));
float radius = fRadius;
myDraw = plDrawableGenerator::GenerateSphericalDrawable(fLocalPosition, radius,
mat, fLastGlobalLoc, blended,
nil, &idx, myDraw);
/*
plSceneObject* so = plSceneObject::ConvertNoRef(fOwner->ObjectIsLoaded());
if (so)
{
hsBool blended = ((mat->GetLayer(0)->GetBlendFlags() & hsGMatState::kBlendMask));
myDraw = plDrawableGenerator::GenerateConicalDrawable(fRadius*10, fHeight*10,
mat, so->GetLocalToWorld(), blended,
nil, &idx, myDraw);
}
*/
return myDraw;
}
#ifndef PLASMA_EXTERNAL_RELEASE
void plPXPhysicalController::IDrawDebugDisplay()
{
plDebugText &debugTxt = plDebugText::Instance();
char strBuf[ 2048 ];
int lineHeight = debugTxt.GetFontSize() + 4;
UInt32 scrnWidth, scrnHeight;
debugTxt.GetScreenSize( &scrnWidth, &scrnHeight );
int y = 10;
int x = 10;
sprintf(strBuf, "Controller Count: %d", gControllers.size());
debugTxt.DrawString(x, y, strBuf);
y += lineHeight;
debugTxt.DrawString(x, y, "Avatar Collisions:");
y += lineHeight;
int i;
for (i = 0; i < fDbgCollisionInfo.GetCount(); i++)
{
hsVector3 normal = fDbgCollisionInfo[i].fNormal;
char *overlapStr = fDbgCollisionInfo[i].fOverlap ? "yes" : "no";
hsScalar angle = hsScalarRadToDeg(hsACosine(normal * hsVector3(0, 0, 1)));
sprintf(strBuf, " Obj: %s, Normal: (%.2f, %.2f, %.2f), Angle(%.1f), Overlap(%3s)",
fDbgCollisionInfo[i].fSO->GetKeyName(),
normal.fX, normal.fY, normal.fZ, angle, overlapStr);
debugTxt.DrawString(x, y, strBuf);
y += lineHeight;
}
}
#endif PLASMA_EXTERNAL_RELEASE