/*==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 "plPXPhysicalControllerCore.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 "../plMessage/plCollideMsg.h"
#include "../plModifier/plDetectorLog.h"
//#include "NxVecExtendedVec3.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
hsBool plPXPhysicalControllerCore::fDebugDisplay = false;
#endif // PLASMA_EXTERNAL_RELEASE
int plPXPhysicalControllerCore::fPXControllersMax = 0;

static ControllerManager gControllerMgr;
static std::vector<plPXPhysicalControllerCore*> gControllers;
static bool gRebuildCache=false;

#define AvatarMass 200.f

class PXControllerHitReportWalk : public NxUserControllerHitReport
{
public:
    virtual NxControllerAction onShapeHit(const NxControllerShapeHit& hit)
    {
        plPXPhysicalControllerCore* ac = plPXPhysicalControllerCore::FindController(hit.controller);
        NxActor& actor = hit.shape->getActor();
        plPXPhysical* phys = (plPXPhysical*)actor.userData;
        static hsScalar SlopeLimit = kSLOPELIMIT;
        hsVector3 normal = plPXConvert::Vector(hit.worldNormal);
        ac->fMovementInterface->IAddContactNormals(normal);
#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.
        hsVector3 dir = plPXConvert::Vector(hit.dir);
        float dirdotup=dir.fZ;
        hsPoint3 pos((hsScalar)hit.worldPos.x, (hsScalar)hit.worldPos.y, (hsScalar)hit.worldPos.z);
        NxExtendedVec3 controllerPos=hit.controller->getPosition();
        hsVector3 bottomOfTheCapsule((hsScalar)controllerPos.x,(hsScalar)controllerPos.y,(hsScalar)controllerPos.z);
        bottomOfTheCapsule.fZ=bottomOfTheCapsule.fZ-(ac->fHeight/2.0f + ac->fRadius);
        if (actor.isDynamic() )
        {
            if((hit.worldPos.z- bottomOfTheCapsule.fZ)<=ac->fRadius)//bottom hemisphere
            {
                //onTopOfSlopeLimit
                if (phys && phys->GetProperty(plSimulationInterface::kPhysAnim))
                {
                    if(normal.fZ>=0)
                    {//we consider this ground
                        ac->fMovementInterface->AddOnTopOfObject(phys);
                    }
                }
            }
            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);
                // We only allow horizontal pushes. Vertical pushes when we stand on
                // dynamic objects creates useless stress on the solver.
                
                hsVector3 vel=ac->GetLinearVelocity()- plPXConvert::Vector( actor.getLinearVelocity());
                if(dirdotup>=0)vel.fZ=0.001f;
                else vel.fZ=0.0f;
                static hsScalar kAvieMass = 140.f/32.f;         
                if (!vel.IsEmpty())
                {
                    static hsScalar kForceScale = 140.0f;
                    NxF32 coeff;
                    NxExtendedVec3 norm2=hit.controller->getPosition();
                    norm2.x=hit.worldPos.x-bottomOfTheCapsule.fX;
                    norm2.y=hit.worldPos.y-bottomOfTheCapsule.fY;
                    if((hit.worldPos.z- bottomOfTheCapsule.fZ)<ac->fRadius)//bottom hemisphere
                    {
                        norm2.normalize();
                        norm2.z=0.01f;
                    }
                    else if((hit.worldPos.z- bottomOfTheCapsule.fZ)<(ac->fRadius+ac->fHeight))
                    {
                        norm2.z=0.0f;
                        norm2.normalize();
                    }
                    else
                    {//must be the top so the normal is displacement from the pos - center
                        //of top hemisphere
                        norm2.z=hit.worldPos.z - ((ac->fRadius+ac->fHeight + bottomOfTheCapsule.fZ));
                        norm2.normalize();
                    }
                    
                    
                    float proj=(float)(norm2.x*dir.fX+dir.fY*norm2.y+dir.fZ*norm2.z);
                    coeff =abs(proj*kForceScale*vel.Magnitude());
                    vel.fZ=(hsScalar)norm2.z;
                    vel.fY=(hsScalar)norm2.y;
                    vel.fX=(hsScalar)norm2.x;
                    phys->SetHitForce(vel*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->SetPushingPhysical( phys);
            ac->SetFacingPushingPhysical((inverseRotation.Rotate(&kAvatarForward).InnerProduct(normal) < 0 ? true : false));
        }
        return NX_ACTION_NONE;
    }
    virtual NxControllerAction onControllerHit(const NxControllersHit& hit)
    {
        return NX_ACTION_NONE;
    }

} gMyReport;


plPhysicalControllerCore* plPhysicalControllerCore::Create(plKey ownerSO, hsScalar height, hsScalar width)
{
    // Test to see how many controller there already is
    if ( !plPXPhysicalControllerCore::fPXControllersMax || plPXPhysicalControllerCore::NumControllers() < plPXPhysicalControllerCore::fPXControllersMax )
    {
        hsScalar radius = width / 2.f;
        hsScalar realHeight = height - width + kPhysicalHeightFudge;
        return TRACKED_NEW plPXPhysicalControllerCore(ownerSO, realHeight,radius);
    }
    return nil;
}

//Static Helper Func
plPXPhysicalControllerCore* plPXPhysicalControllerCore::FindController(NxController* controller)
{
    for (int i = 0; i < gControllers.size(); i++)
    {
        plPXPhysicalControllerCore* ac = gControllers[i];
        if (ac->fController == controller)
            return ac;
    }
    return nil;
}
void plPXPhysicalControllerCore::RebuildCache(){gRebuildCache=true;}

plPXPhysicalControllerCore* plPXPhysicalControllerCore::GetController(NxActor& actor, bool* isController)
{
    *isController = false;
    for (int i = 0; i < gControllers.size(); i++)
    {
        plPXPhysicalControllerCore* ac = gControllers[i];
        if (ac->fController && ac->fController->getActor() == &actor)
        {
            *isController = true;
            return ac;
        }
        if ( ac->fKinematicActor == &actor)
        {
            return ac;
        }
    }

    return nil;
}
void plPXPhysicalControllerCore::GetWorldSpaceCapsule(NxCapsule& cap) const
{
    if(this->fKinematicActor)
    {
        int numshapes=fKinematicActor->getNbShapes();
        if (numshapes==1) 
        {
            //there should only be one shape on a controller
            NxShape* const *shapes=fKinematicActor->getShapes();
            //and since it is a capsule controller it better be a capsule;
            NxCapsuleShape *capShape = shapes[0]->isCapsule();
            if(capShape) capShape->getWorldCapsule(cap);
        }
    }
}
bool plPXPhysicalControllerCore::AnyControllersInThisWorld(plKey world)
{
    for (int i = 0; i < gControllers.size(); i++)
    {
        plPXPhysicalControllerCore* ac = gControllers[i];
        if (ac->GetSubworld() == world)
            return true;
    }
    return false;
}

int plPXPhysicalControllerCore::NumControllers()
{
    return gControllers.size();
}
int plPXPhysicalControllerCore::GetControllersInThisSubWorld(plKey world, int maxToReturn,plPXPhysicalControllerCore** bufferout)
{
    int i=0;
    for (int j=0;j<gControllers.size();j++)
    {
        plPXPhysicalControllerCore* ac = gControllers[i];
        if (ac->GetSubworld()==world)
        {
            if(i<maxToReturn)
            {
                bufferout[i]=ac;
                i++;
            }
        }
    }
    return i;

}
int plPXPhysicalControllerCore::GetNumberOfControllersInThisSubWorld(plKey world)
{
    int i=0;
    for (int j=0;j<gControllers.size();j++)
    {
        plPXPhysicalControllerCore* ac = gControllers[i];
        if (ac->GetSubworld()==world)i++;
    }
    return i;
}
//
plPXPhysicalControllerCore::plPXPhysicalControllerCore(plKey ownerSO, hsScalar height, hsScalar radius)
    : plPhysicalControllerCore(ownerSO,height,radius)
    , fController(nil)
    , fProxyGen(nil)
    , fKinematicActor(nil)
    ,fPreferedRadius(radius)
    ,fPreferedHeight(height)
    , fBehavingLikeAnimatedPhys(true)
{
    fLocalPosition.Set(0, 0, 0);
    fLocalRotation.Set(0, 0, 0, 1);
    gControllers.push_back(this);
    fLastGlobalLoc.Reset();
    ICreateController();
    Enable(false);
}

void plPXPhysicalControllerCore::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();
}
plPXPhysicalControllerCore::~plPXPhysicalControllerCore()
{
    IDeleteController();
    //need to make sure my queued messages are released
    for(int j=0;j<fQueuedCollideMsgs.GetCount();j++)
    {
        delete fQueuedCollideMsgs[j];
        fQueuedCollideMsgs[j]=nil;
    }
    fQueuedCollideMsgs.SetCount(0);
    for (int i = 0; i < gControllers.size(); i++)
    {
        if (gControllers[i] == this)
        {
            gControllers.erase(gControllers.begin()+i);
            break;
        }
    }
    delete fProxyGen;
}
void plPXPhysicalControllerCore::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 plPXPhysicalControllerCore::UpdateControllerAndPhysicalRep()
{
    if ( fKinematicActor)
    {
        if(this->fBehavingLikeAnimatedPhys)
        {//this means we are moving the controller and then synchnig the kin
            NxExtendedVec3 ControllerPos= fController->getPosition();
            NxVec3 NewKinPos((NxReal)ControllerPos.x, (NxReal)ControllerPos.y, (NxReal)ControllerPos.z);
            if (fEnabled || fKinematic)
            {
                if (plSimulationMgr::fExtraProfile)
                    SimLog("Moving kinematic to %f,%f,%f",NewKinPos.x, NewKinPos.y, NewKinPos.z );
                // use the position
                fKinematicActor->moveGlobalPosition(NewKinPos);

            }
            else
            {
                if (plSimulationMgr::fExtraProfile)
                    SimLog("Setting kinematic to %f,%f,%f", NewKinPos.x, NewKinPos.y, NewKinPos.z );
                fKinematicActor->setGlobalPosition(NewKinPos);
            }

        }
        else
        {
            NxVec3 KinPos= fKinematicActor->getGlobalPosition();
            NxExtendedVec3 NewControllerPos(KinPos.x, KinPos.y, KinPos.z);
            if (plSimulationMgr::fExtraProfile)
                    SimLog("Setting Controller to %f,%f,%f", NewControllerPos.x, NewControllerPos.y, NewControllerPos.z );
            fController->setPosition(NewControllerPos);
        }
        hsPoint3 curLocalPos;   
        GetPositionSim(curLocalPos);
        fLocalPosition = curLocalPos;
    }
}
void plPXPhysicalControllerCore::MoveKinematicToController(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 ((fEnabled || fKinematic) && fBehavingLikeAnimatedPhys)
            {
                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 plPXPhysicalControllerCore::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 (fEnabled|| fKinematic)
        fKinematicActor->moveGlobalPosition(plPXConvert::Point(kPos));
    else
        fKinematicActor->setGlobalPosition(plPXConvert::Point(kPos));
}
void plPXPhysicalControllerCore::IGetPositionSim(hsPoint3& pos) const
{
    
    if(this->fBehavingLikeAnimatedPhys)
    {
        const NxExtendedVec3& nxPos = fController->getPosition();
        pos.Set(hsScalar(nxPos.x), hsScalar(nxPos.y), hsScalar(nxPos.z) - kPhysZOffset);
    }
    else
    {
        NxVec3 Pos = fKinematicActor->getGlobalPosition();
        pos.Set(hsScalar(Pos.x), hsScalar(Pos.y), hsScalar(Pos.z) - kPhysZOffset);
    }
}
void plPXPhysicalControllerCore::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;
    capDesc.materialIndex= plSimulationMgr::GetInstance()->GetMaterialIdx(scene, 0.0,0.0);
    actorDesc.shapes.pushBack(&capDesc);
    NxBodyDesc bodyDesc;
    bodyDesc.mass = AvatarMass;//1.f;
    actorDesc.body = &bodyDesc;
    bodyDesc.flags = NX_BF_KINEMATIC;
    bodyDesc.flags |=NX_BF_DISABLE_GRAVITY ;
    
    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 plPXPhysicalControllerCore::ICreateController(const hsPoint3& pos)
{
    NxScene* scene = plSimulationMgr::GetInstance()->GetScene(fWorldKey);
    NxCapsuleControllerDesc desc;
    desc.position.x     = pos.fX;
    desc.position.y     = pos.fY;
    desc.position.z     = pos.fZ;
    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);
    capDesc.materialIndex= plSimulationMgr::GetInstance()->GetMaterialIdx(scene, 0.0,0.0);
    actorDesc.globalPose=actor->getGlobalPose();
    NxBodyDesc bodyDesc;
    bodyDesc.mass = AvatarMass;
    actorDesc.body = &bodyDesc;
    bodyDesc.flags = NX_BF_KINEMATIC;
    bodyDesc.flags |=NX_BF_DISABLE_GRAVITY ;
    actorDesc.name = "AvatarTriggerKinematicGuy";
    fSeeking=false;
    try
    {
        fKinematicActor = scene->createActor(actorDesc);
    }
    catch (...)
    {
        hsAssert(false, "Actor creation crashed");
    }

    // 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 plPXPhysicalControllerCore::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);
    }
}

void plPXPhysicalControllerCore::IInformDetectors(bool entering,bool deferUntilNextSim=true)
{
    static const NxU32 DetectorFlag= 1<<plSimDefs::kGroupDetector;
    if (fController)
    {
#ifndef PLASMA_EXTERNAL_RELEASE
        DetectorLog("Informing from plPXPhysicalControllerCore::IInformDetectors");
#endif  
        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)
                {
                    bool doReport = physical->DoReportOn(plSimDefs::kGroupAvatar);
                    if(doReport)
                    {
                        plCollideMsg* msg = TRACKED_NEW plCollideMsg;
                        msg->fOtherKey = fOwner;
                        msg->fEntering = entering;
                        msg->AddReceiver(physical->GetObjectKey());
                        if(!deferUntilNextSim)
                        {
#ifndef PLASMA_EXTERNAL_RELEASE
                        DetectorLog("Sending an %s msg to %s" , entering? "entering":"exit", physical->GetObjectKey()->GetName());
#endif                      
                        msg->Send();
                        }
                        else
                        {
#ifndef PLASMA_EXTERNAL_RELEASE
                        DetectorLog("Queuing an %s msg to %s, which will be sent after the next simstep" , entering? "entering":"exit", physical->GetObjectKey()->GetName());
#endif                      
                        //these will be fired in update prestep on the next lap
                        fQueuedCollideMsgs.Append(msg);
                        }
                    }
                }
            }
        }
#ifndef PLASMA_EXTERNAL_RELEASE
        DetectorLog("Done informing from plPXPhysicalControllerCore::IInformDetectors");
#endif
    }
}
void plPXPhysicalControllerCore::Move(hsVector3 displacement, unsigned int collideWith, unsigned int &collisionResults)
{
    collisionResults=0;
    if(fController)
    {
        NxVec3 dis(displacement.fX,displacement.fY,displacement.fZ);
        NxU32 colFlags = 0;
        this->fController->move(dis,collideWith,.00001,colFlags);
        if(colFlags&NXCC_COLLISION_DOWN)collisionResults|=kBottom;
        if(colFlags&NXCC_COLLISION_UP)collisionResults|=kTop;
        if(colFlags&&NXCC_COLLISION_SIDES)collisionResults|=kSides;
    }
    return; 
}
void plPXPhysicalControllerCore::Enable(bool enable)
{
    if (fEnabled != enable)
    {
        fEnabled = enable;
        if (fEnabled)
            fEnableChanged = true;
        else
        {
            // See ISendUpdates for why we don't re-enable right away
            fController->setCollision(fEnabled);
        }
    }
}

void plPXPhysicalControllerCore::SetSubworld(plKey world) 
{   
    if (fWorldKey != world)
    {
        bool wasEnabled = fEnabled;
#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
        SimLog("Changing subworlds!");
        IDeleteController();
        SimLog("Deleted old controller");
        fWorldKey = world;
        if (GetSubworldCI())
            fPrevSubworldW2L = GetSubworldCI()->GetWorldToLocal();
        // Update our subworld position and rotation
        const plCoordinateInterface* subworldCI = GetSubworldCI();
        if (subworldCI)
        {
            const hsMatrix44& w2s = fPrevSubworldW2L;
            hsMatrix44 l2s = w2s * fLastGlobalLoc;
            l2s.GetTranslate(&fLocalPosition);
            fLocalRotation.SetFromMatrix44(l2s);
        }
        else
        {
            fLastGlobalLoc.GetTranslate(&fLocalPosition);
            fLocalRotation.SetFromMatrix44(fLastGlobalLoc);
        }
        hsMatrix44 w2l;
        fLastGlobalLoc.GetInverse(&w2l);
        if (fProxyGen)
            fProxyGen->SetTransform(fLastGlobalLoc, w2l);
        // Update the physical position
        SimLog("creating new controller");
        hsPoint3 PositionPlusOffset=fLocalPosition;
        PositionPlusOffset.fZ +=kPhysZOffset;
        //placing new controller and kinematic in the appropriate location
        ICreateController(PositionPlusOffset);
        RebuildCache();
    }
}
const plCoordinateInterface* plPXPhysicalControllerCore::GetSubworldCI() const 
{
    if (fWorldKey)
    {
        plSceneObject* so = plSceneObject::ConvertNoRef(fWorldKey->ObjectIsLoaded());
        if (so)
            return so->GetCoordinateInterface();
    }
    return nil;
}
// For the avatar SDL only
void plPXPhysicalControllerCore::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 plPXPhysicalControllerCore::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();
    }
}
// kinematic stuff .... should be just for when playing a behavior...
void plPXPhysicalControllerCore::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 plPXPhysicalControllerCore::IsKinematic()
{
    return fKinematic;
}
void plPXPhysicalControllerCore::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 plPXPhysicalControllerCore::UpdatePoststep( 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++)
    {
        plPXPhysicalControllerCore* ac = gControllers[i];

        hsAssert(ac, "Bad avatar controller");
    
            gControllerMgr.updateControllers();
            if(ac->fMovementInterface)
                ac->Update(delSecs);
                
            if (ac->GetSubworldCI())
                ac->fPrevSubworldW2L = ac->GetSubworldCI()->GetWorldToLocal();
            else
            {
                if (!ac->fPrevSubworldW2L.IsIdentity())
                    ac->fPrevSubworldW2L.Reset();
            }
    }
}
void plPXPhysicalControllerCore::UpdatePrestep(hsScalar delSecs)
{
    for (int i = 0; i < gControllers.size(); i++)
    {
        plPXPhysicalControllerCore* ac = gControllers[i];

        hsAssert(ac, "Bad avatar controller");
        //FIXME
#ifndef PLASMA_EXTERNAL_RELEASE
        ac->fDbgCollisionInfo.SetCount(0);
#endif // PLASMA_EXTERNAL_RELEASE

        if (gRebuildCache&&ac->fController) 
            ac->fController->reportSceneChanged();
        //hsAssert(ac->fMovementInterface,"Updating a controller with out a movement strategy");
        if(ac)
        {   
            if(ac->fNeedsResize)ac->IHandleResize();
            int storedCollideMsgs=ac->fQueuedCollideMsgs.GetCount();
            if(storedCollideMsgs)
            {
                plSimulationMgr* simMgr=plSimulationMgr::GetInstance();
                for(int j=0; j<storedCollideMsgs;j++)
                {
                    simMgr->AddCollisionMsg(ac->fQueuedCollideMsgs[j]);
                }
                ac->fQueuedCollideMsgs.SetCount(0);
            }
            ac->Apply(delSecs);
        }
#ifndef PLASMA_EXTERNAL_RELEASE
        if (fDebugDisplay)
            ac->IDrawDebugDisplay();
#endif // PLASMA_EXTERNAL_RELEASE
    }
    gRebuildCache = false;
}
void plPXPhysicalControllerCore::UpdatePostSimStep(hsScalar delSecs)
{
    for (int i = 0; i < gControllers.size(); i++)
    {
        plPXPhysicalControllerCore* ac = gControllers[i];
        hsAssert(ac, "Bad avatar controller");
        ac->PostStep(delSecs);
        
    }
}
void plPXPhysicalControllerCore::HandleEnableChanged()
{
        fEnableChanged = false;
        if(this->fBehavingLikeAnimatedPhys)
        {
            fController->setCollision(fEnabled);
        }
        else
        {
            fController->setCollision(false);
        }
#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,fEnabled);
#endif  // USE_PHYSX_CONVEXHULL_WORKAROUND
        //IInformDetectors(true);
}

void plPXPhysicalControllerCore::HandleKinematicChanged()
{
        fKinematicChanged = false;
        if(this->fBehavingLikeAnimatedPhys)
        {
            fController->setCollision(true);
        }
        else
        {
            fController->setCollision(false);
        }
#ifdef PHYSX_KINEMATIC_IS_DISABLED
        fKinematicActor->raiseActorFlag(NX_AF_DISABLE_COLLISION);
#endif  // PHYSX_KINEMATIC_IS_DISABLED
}
void plPXPhysicalControllerCore::HandleKinematicEnableNextUpdate()
{
    fKinematicActor->clearActorFlag(NX_AF_DISABLE_COLLISION);
        fKinematicEnableNextUpdate = false;
}
void plPXPhysicalControllerCore::IHandleResize()
{

    UInt32 collideFlags =
        1<<plSimDefs::kGroupStatic |
        1<<plSimDefs::kGroupAvatarBlocker |
        1<<plSimDefs::kGroupDynamic;
    if(!IsSeeking())
    {
        collideFlags|=(1<<plSimDefs::kGroupExcludeRegion);
    }
    NxScene* myscene = plSimulationMgr::GetInstance()->GetScene(this->fWorldKey);
//  NxShape** response=TRACKED_NEW NxShape*[2];
    
    NxVec3 center(fLocalPosition.fX,fLocalPosition.fY,fLocalPosition.fZ+fPreferedRadius);
    NxSegment Seg(center,center);
    const NxCapsule newCap(Seg,fPreferedRadius);
    int numintersect =myscene->checkOverlapCapsule(newCap,NX_ALL_SHAPES,collideFlags);
    //with new capsule dimensions check for overlap
    //with objects we would collide with
    
    if(numintersect==0)
    {
        fHeight=fPreferedHeight;
        fRadius=fPreferedRadius;
        fController->setRadius(fRadius);
        fController->setHeight(fHeight);
        
        fNeedsResize=false;
    }

//  delete[] response;
}
void plPXPhysicalControllerCore::SetControllerDimensions(hsScalar radius, hsScalar height)
{
    fNeedsResize=false;
    if(fRadius!=radius)
    {
        fNeedsResize=true;
    }
    if(fHeight!=height)
    {
        fNeedsResize=true;
    }
    fPreferedRadius=radius;
    fPreferedHeight=height;
}

void plPXPhysicalControllerCore::LeaveAge()
{
    SetPushingPhysical(nil);
    if(fWorldKey) this->SetSubworld(nil);
    this->fMovementInterface->LeaveAge();
}
int plPXPhysicalControllerCore::SweepControllerPath(const hsPoint3& startPos, const hsPoint3& endPos, hsBool vsDynamics, hsBool vsStatics, 
                            UInt32& vsSimGroups, std::multiset< plControllerSweepRecord >& WhatWasHitOut)
{
    NxCapsule tempCap;
    tempCap.p0 =plPXConvert::Point( startPos);
    tempCap.p0.z = tempCap.p0.z + fPreferedRadius;
    tempCap.radius = fPreferedRadius ;
    tempCap.p1 = tempCap.p0;
    tempCap.p1.z = tempCap.p1.z + fPreferedHeight;

    NxVec3 vec;
    vec.x = endPos.fX - startPos.fX;
    vec.y = endPos.fY - startPos.fY;
    vec.z = endPos.fZ - startPos.fZ;

    int numberofHits = 0;
    int HitsReturned = 0;
    WhatWasHitOut.clear();
    NxScene *myscene = plSimulationMgr::GetInstance()->GetScene(fWorldKey);
    NxSweepQueryHit whatdidIhit[10];
    unsigned int flags = NX_SF_ALL_HITS;
    if(vsDynamics)
        flags |= NX_SF_DYNAMICS;
    if(vsStatics)
        flags |= NX_SF_STATICS;
    numberofHits = myscene->linearCapsuleSweep(tempCap, vec, flags, nil, 10, whatdidIhit, nil, vsSimGroups);
    if(numberofHits)
    {//we hit a dynamic object lets make sure it is not animatable
        for(int i=0; i<numberofHits; i++)
        {
            plControllerSweepRecord CurrentHit;
            CurrentHit.ObjHit=(plPhysical*)whatdidIhit[i].hitShape->getActor().userData;
            CurrentHit.Norm.fX = whatdidIhit[i].normal.x;
            CurrentHit.Norm.fY = whatdidIhit[i].normal.y;
            CurrentHit.Norm.fZ = whatdidIhit[i].normal.z;
            if(CurrentHit.ObjHit != nil)
            {
                hsPoint3 where;
                where.fX = whatdidIhit[i].point.x;
                where.fY = whatdidIhit[i].point.y;
                where.fZ = whatdidIhit[i].point.z;
                CurrentHit.locHit = where;
                CurrentHit.TimeHit = whatdidIhit[i].t ;
                WhatWasHitOut.insert(CurrentHit);
                HitsReturned++;
            }
        }
    }

    return HitsReturned;
}
void plPXPhysicalControllerCore::BehaveLikeAnimatedPhysical(hsBool actLikeAnAnimatedPhys)
{
    hsAssert(fKinematicActor, "Changing behavior, but plPXPhysicalControllerCore has no Kinematic actor associated with it");
    if(fBehavingLikeAnimatedPhys!=actLikeAnAnimatedPhys)
    {
        fBehavingLikeAnimatedPhys=actLikeAnAnimatedPhys;
        if(fKinematicActor)
        {
            if(actLikeAnAnimatedPhys)
            {
                //need to set BX Kinematic if true and kill any rotation
                fController->setCollision(fEnabled);
                fKinematicActor->raiseBodyFlag(NX_BF_KINEMATIC);
                fKinematicActor->clearBodyFlag(NX_BF_FROZEN_ROT);
                fKinematicActor->raiseBodyFlag(NX_BF_DISABLE_GRAVITY);
            }
            else
            {
                //don't really use the controller now don't bother with collisions 
                fController->setCollision(false);
                fKinematicActor->clearBodyFlag(NX_BF_KINEMATIC);
                fKinematicActor->raiseBodyFlag(NX_BF_FROZEN_ROT_X);
                fKinematicActor->raiseBodyFlag(NX_BF_FROZEN_ROT_Y);
                fKinematicActor->clearBodyFlag(NX_BF_DISABLE_GRAVITY);
                

            }
        }
    }
}

hsBool plPXPhysicalControllerCore::BehavingLikeAnAnimatedPhysical()
{
    hsAssert(fKinematicActor, "plPXPhysicalControllerCore is missing a kinematic actor");
    return fBehavingLikeAnimatedPhys;
}

void plPXPhysicalControllerCore::SetLinearVelocity(const hsVector3& linearVel)
{
    plPhysicalControllerCore::SetLinearVelocity(linearVel);
    if(fKinematicActor && !fBehavingLikeAnimatedPhys)
    {
        NxVec3 vel= plPXConvert::Vector(linearVel);
        fKinematicActor->setLinearVelocity(vel);
    }
}
void plPXPhysicalControllerCore::SetAngularVelocity(const hsScalar angvel)
{
    plPhysicalControllerCore::SetAngularVelocity(angvel);
    if(fKinematicActor && !fBehavingLikeAnimatedPhys)
    {
        NxVec3 vel(0.0f, 0.0f, angvel);
        fKinematicActor->setAngularVelocity(vel);
    }
}
void plPXPhysicalControllerCore::SetVelocities(const hsVector3& linearVel, hsScalar angVel)
{
    SetLinearVelocity(linearVel);
    SetAngularVelocity(angVel);
}

void plPXPhysicalControllerCore::IMatchControllerToKinematic()
{
    NxExtendedVec3 newpos;
    NxVec3 pos=fKinematicActor->getGlobalPosition();
    newpos.x=pos.x;
    newpos.y=pos.y;
    newpos.z=pos.z;
    fController->setPosition(newpos);
}
const hsVector3& plPXPhysicalControllerCore::GetLinearVelocity()
{
    if(BehavingLikeAnAnimatedPhysical())
        return fLinearVelocity;
    else
    {
        fLinearVelocity = plPXConvert::Vector(fKinematicActor->getLinearVelocity());
        return fLinearVelocity;
    }
}

#include "../plSurface/hsGMaterial.h"
#include "../plSurface/plLayerInterface.h"

// Make a visible object that can be viewed by users for debugging purposes.
plDrawableSpans* plPXPhysicalControllerCore::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
#include "../plPipeline/plDebugText.h"

void plPXPhysicalControllerCore::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