/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "plSimulationMgr.h"

#include <NxPhysics.h>

#include "plgDispatch.h"
#include "hsTimer.h"
#include "plProfile.h"
#include "plPXPhysical.h"
#include "plPXPhysicalControllerCore.h"
#include "plPXConvert.h"
#include "plLOSDispatch.h"
#include "plPhysical/plPhysicsSoundMgr.h"
#include "plStatusLog/plStatusLog.h"
#include "pnSceneObject/plSimulationInterface.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "pnNetCommon/plSDLTypes.h"
#include "plMessage/plCollideMsg.h"
#include "plMessage/plAgeLoadedMsg.h"

#include "plModifier/plDetectorLog.h"

#ifndef PLASMA_EXTERNAL_RELEASE
#include "plPipeline/plDebugText.h"
bool plSimulationMgr::fDisplayAwakeActors=false;
#endif //PLASMA_EXTERNAL_RELEASE
// This gets called by PhysX whenever a trigger gets penetrated.  This is used
// for any Plasma detectors.
class SensorReport : public NxUserTriggerReport
{
    virtual void onTrigger(NxShape& triggerShape, NxShape& otherShape, NxTriggerFlag status)
    {
        // Get our trigger physical.  This should definitely have a plPXPhysical
        plPXPhysical* triggerPhys = (plPXPhysical*)triggerShape.getActor().userData;
        hsBool doReport = false;

        // Get the triggerer.  This may be an avatar, which doesn't have a
        // plPXPhysical, so we have to extract the necessary info.
        plKey otherKey = nil;
        hsPoint3 otherPos = plPXConvert::Point(otherShape.getGlobalPosition());

        if (plSimulationMgr::fExtraProfile)
            DetectorLogRed("-->%s %s (status=%x) other@(%f,%f,%f)",triggerPhys->GetObjectKey()->GetName().c_str(),status & NX_TRIGGER_ON_ENTER ? "enter" : "exit",status,otherPos.fX,otherPos.fY,otherPos.fZ);

        plPXPhysical* otherPhys = (plPXPhysical*)otherShape.getActor().userData;
        if (otherPhys)
        {
            otherKey = otherPhys->GetObjectKey();
            doReport = triggerPhys->DoReportOn((plSimDefs::Group)otherPhys->GetGroup());
            if (!doReport)
            {
                if (plSimulationMgr::fExtraProfile)
                    DetectorLogRed("<--Kill collision %s :failed group. US=%x OTHER=(%s)%x",triggerPhys->GetObjectKey()->GetName().c_str(),triggerPhys->GetGroup(),otherPhys->GetObjectKey()->GetName().c_str(),otherPhys->GetGroup());
            }
        }
        else
        {
            bool isController;
            plPXPhysicalControllerCore* controller = plPXPhysicalControllerCore::GetController(otherShape.getActor(),&isController);
            if (controller)
            {
                if (isController)
                {
#ifdef PHYSX_ONLY_TRIGGER_FROM_KINEMATIC
                    if (plSimulationMgr::fExtraProfile)
                        DetectorLogRed("<--Kill collision %s : ignoring controller events.",triggerPhys->GetObjectKey()->GetName().c_str());
                    return;
#else // else if trigger on both controller and kinematic
                    // only suppress controller collision 'enters' when disabled but let 'exits' continue
                    // ...this is because there are detector regions that are on the edge on ladders that the exit gets missed.
                    if ( ( !controller->IsEnabled() /*&& (status & NX_TRIGGER_ON_ENTER)*/ ) || controller->IsKinematic() )
                    {
                        if (plSimulationMgr::fExtraProfile)
                            DetectorLogRed("<--Kill collision %s : controller is not enabled.",triggerPhys->GetObjectKey()->GetName().c_str());
                        return;
                    }
#endif  // PHYSX_ONLY_TRIGGER_FROM_KINEMATIC
                }
#ifndef PHYSX_ONLY_TRIGGER_FROM_KINEMATIC  // if triggering only kinematics, then all should trigger
                else
                {
                    // only suppress kinematic collision 'enters' when disabled but let 'exits' continue
                    // ...this is because there are detector regions that are on the edge on ladders that the exit gets missed.
                    if ( !controller->IsKinematic() /*&& (status & NX_TRIGGER_ON_ENTER) */ )
                    {
                        if (plSimulationMgr::fExtraProfile)
                            DetectorLogRed("<--Kill collision %s : kinematic is not enabled.",triggerPhys->GetObjectKey()->GetName().c_str());
                        return;
                    }
                }
#endif  // PHYSX_ONLY_TRIGGER_FROM_KINEMATIC
                otherKey = controller->GetOwner();
                doReport = triggerPhys->DoReportOn(plSimDefs::kGroupAvatar);
                if (plSimulationMgr::fExtraProfile )
                {
                    if (!doReport)
                    {
                        DetectorLogRed("<--Kill collision %s :failed group. US=%x OTHER=(NotAvatar)",triggerPhys->GetObjectKey()->GetName().c_str(),triggerPhys->GetGroup());
                    }
                    else
                    {
                        hsPoint3 avpos;
                        controller->GetPositionSim(avpos);
                        DetectorLogRed("-->Avatar at (%f,%f,%f)",avpos.fX,avpos.fY,avpos.fZ);
                    }
                }
            }
        }

        if (doReport)
        {
#ifdef USE_PHYSX_CONVEXHULL_WORKAROUND
            if ( triggerPhys->DoDetectorHullWorkaround() )
            {
                if (status & NX_TRIGGER_ON_ENTER && triggerPhys->Should_I_Trigger(status & NX_TRIGGER_ON_ENTER, otherPos) )
                {
                    if (plSimulationMgr::fExtraProfile)
                        DetectorLogRed("-->Send Collision (CH) %s %s",triggerPhys->GetObjectKey()->GetName().c_str(),status & NX_TRIGGER_ON_ENTER ? "enter" : "exit");
                    plSimulationMgr::GetInstance()->AddCollisionMsg(triggerPhys->GetObjectKey(), otherKey, true);
                }
                else if (status & NX_TRIGGER_ON_ENTER)
                {
                    if (plSimulationMgr::fExtraProfile)
                        DetectorLogRed("<--Kill collision %s :failed Should I trigger",triggerPhys->GetObjectKey()->GetName().c_str());
                }
                if (status & NX_TRIGGER_ON_LEAVE && triggerPhys->Should_I_Trigger(status & NX_TRIGGER_ON_ENTER, otherPos) )
                {
                    if (plSimulationMgr::fExtraProfile)
                        DetectorLogRed("-->Send Collision (CH) %s %s",triggerPhys->GetObjectKey()->GetName().c_str(),status & NX_TRIGGER_ON_ENTER ? "enter" : "exit");
                    plSimulationMgr::GetInstance()->AddCollisionMsg(triggerPhys->GetObjectKey(), otherKey, false);
                }
                else if (status & NX_TRIGGER_ON_LEAVE)
                {
                    if (plSimulationMgr::fExtraProfile)
                        DetectorLogRed("<--Kill collision %s :failed Should I trigger",triggerPhys->GetObjectKey()->GetName().c_str());
                }
                if (!(status & NX_TRIGGER_ON_ENTER) && !(status & NX_TRIGGER_ON_LEAVE) )
                {
                    if (plSimulationMgr::fExtraProfile)
                        DetectorLogRed("<--Kill collision %s :failed event(CH)",triggerPhys->GetObjectKey()->GetName().c_str());
                }
            }
            else
            {
#endif  // USE_PHYSX_CONVEXHULL_WORKAROUND
                if (status & NX_TRIGGER_ON_ENTER)
                {
                    if (plSimulationMgr::fExtraProfile)
                        DetectorLogRed("-->Send Collision %s %s",triggerPhys->GetObjectKey()->GetName().c_str(),status & NX_TRIGGER_ON_ENTER ? "enter" : "exit");
                    plSimulationMgr::GetInstance()->AddCollisionMsg(triggerPhys->GetObjectKey(), otherKey, true);
                }
                if (status & NX_TRIGGER_ON_LEAVE)
                {
                    if (plSimulationMgr::fExtraProfile)
                        DetectorLogRed("-->Send Collision %s %s",triggerPhys->GetObjectKey()->GetName().c_str(),status & NX_TRIGGER_ON_ENTER ? "enter" : "exit");
                    plSimulationMgr::GetInstance()->AddCollisionMsg(triggerPhys->GetObjectKey(), otherKey, false);
                }
                if (!(status & NX_TRIGGER_ON_ENTER) && !(status & NX_TRIGGER_ON_LEAVE) )
                {
                    if (plSimulationMgr::fExtraProfile)
                        DetectorLogRed("<--Kill collision %s :failed event",triggerPhys->GetObjectKey()->GetName().c_str());
                }
#ifdef USE_PHYSX_CONVEXHULL_WORKAROUND
            }
#endif  // USE_PHYSX_CONVEXHULL_WORKAROUND
        }
    }
} gSensorReport;

// This gets called by PhysX whenever two actor groups that are set to report
// have a collision.  We enable this for when a dynamic collides with anything.
class ContactReport : public NxUserContactReport
{
    virtual void onContactNotify(NxContactPair& pair, NxU32 events)
    {
        plPXPhysical* phys1 = (plPXPhysical*)pair.actors[0]->userData;
        plPXPhysical* phys2 = (plPXPhysical*)pair.actors[1]->userData;

        // Normally, these are always valid because the avatar (who doesn't have
        // a physical) will push other physicals away before they actually touch
        // his actor.  However, if the avatar is warped to a new position he may
        // collide with the object for a few frames.  We just ignore it.
        if (!phys1 || !phys2)
            return;

        plSimulationMgr::GetInstance()->ConsiderSynch(phys1, phys2);

        if (phys1->GetSoundGroup() && phys2->GetSoundGroup())
        {
            hsPoint3 contactPoint(0, 0, 0);

            // Just grab the last contact point
            NxContactStreamIterator i(pair.stream);
            while (i.goNextPair())
            {
                while (i.goNextPatch())
                {
                    const NxVec3& contactNormal = i.getPatchNormal();
                    while (i.goNextPoint())
                    {
                        contactPoint = plPXConvert::Point(i.getPoint());
                    }
                }
            }

            plSimulationMgr::GetInstance()->fSoundMgr->AddContact(
                phys1, phys2, contactPoint,
                plPXConvert::Vector(pair.sumNormalForce));
        }
    }
} gContactReport;

// This directs any errors or warnings from PhysX to the simulation log.
class ErrorStream : public NxUserOutputStream
{
    virtual void reportError(NxErrorCode e, const char* message, const char* file, int line)
    {
        const char* errorType = nil;
        switch (e)
        {
        case NXE_INVALID_PARAMETER: errorType = "invalid parameter";    break;
        case NXE_INVALID_OPERATION: errorType = "invalid operation";    break;
        case NXE_OUT_OF_MEMORY:     errorType = "out of memory";        break;
        case NXE_DB_INFO:           errorType = "info";                 break;
        case NXE_DB_WARNING:        errorType = "warning";              break;
        default:                    errorType = "unknown error";
        }

        plSimulationMgr::Log("%s(%d) : %s: %s", file, line, errorType, message);
    }

    virtual NxAssertResponse reportAssertViolation(const char* message, const char* file, int line)
    {
        plSimulationMgr::Log("access violation : %s (%s(%d))", message, file, line);
        hsAssert(0, "PhysX assert, see simulation log for details");
        return NX_AR_CONTINUE;
    }

    virtual void print(const char* message)
    {
        plSimulationMgr::Log(message);
    }
} gErrorStream;


/////////////////////////////////////////////////////////////////
//
// DEFAULTS
//
/////////////////////////////////////////////////////////////////

#define kDefaultMaxDelta    0.1         // if the step is greater than .1 seconds, clamp to that
#define kDefaultStepSize    1.f / 60.f  // default simulation freqency is 60hz

/////////////////////////////////////////////////////////////////
//
// CONSTRUCTION, INITIALIZATION, DESTRUCTION
//
/////////////////////////////////////////////////////////////////

// 
// Alloc all the sim timers here so they make a nice pretty display
//
//plProfile_CreateTimer(    "ClearContacts", "Simulation", ClearContacts);
plProfile_CreateTimer(  "Step", "Simulation", Step);
// plProfile_CreateTimer(   "  Broadphase", "Simulation", Broadphase);
plProfile_CreateCounter("  Awake", "Simulation", Awake);
plProfile_CreateCounter("  Contacts", "Simulation", Contacts);
plProfile_CreateCounter("  DynActors", "Simulation", DynActors);
plProfile_CreateCounter("  DynShapes", "Simulation", DynShapes);
plProfile_CreateCounter("  StaticShapes", "Simulation", StaticShapes);
plProfile_CreateCounter("  Actors", "Simulation", Actors);
plProfile_CreateCounter("  PhyScenes", "Simulation", Scenes);

// plProfile_CreateCounter("  Broadphase Rejected", "Simulation", BroadphaseReject);
// plProfile_CreateCounter("  Broadphase Accepted", "Simulation", BroadphaseAccept);
// plProfile_CreateCounter("  Impact", "Simulation", Impact);
// plProfile_CreateCounter("  Penetration", "Simulation", Penetration);
// plProfile_CreateTimer(   "Narrowphase", "Simulation", Narrowphase);
// plProfile_CreateTimer(   "ProcessInterpenetration", "Simulation", ProcessInterpenetration);
plProfile_CreateTimer(  "LineOfSight", "Simulation", LineOfSight);
plProfile_CreateTimer(  "ProcessSyncs", "Simulation", ProcessSyncs);
plProfile_CreateTimer(  "UpdateContexts", "Simulation", UpdateContexts);
// plProfile_CreateCounter("  ContextUpdates", "Simulation", ContextUpdates);
plProfile_CreateCounter("  MaySendLocation", "Simulation", MaySendLocation);
plProfile_CreateCounter("  LocationsSent", "Simulation", LocationsSent);
plProfile_CreateTimer(  "  PhysicsUpdates","Simulation",PhysicsUpdates);
// plProfile_CreateTimer(   "EntityCleanup", "Simulation", EntityCleanup);
plProfile_CreateCounter("SetTransforms Accepted", "Simulation", SetTransforms);
plProfile_CreateCounter("AnimatedPhysicals", "Simulation", AnimatedPhysicals);
plProfile_CreateCounter("AnimatedActivators", "Simulation", AnimatedActivators);
plProfile_CreateCounter("Controllers", "Simulation", Controllers);
// plProfile_CreateCounter("NumSteps", "Simulation", NumSteps);
plProfile_CreateCounter("StepLength", "Simulation", StepLen);

// declared at file scope so that both GetInstance and the destructor can access it.
static plSimulationMgr* gTheInstance = NULL;
bool plSimulationMgr::fExtraProfile = false;
bool plSimulationMgr::fSubworldOptimization = false;
bool plSimulationMgr::fDoClampingOnStep=true;

void plSimulationMgr::Init()
{
    hsAssert(!gTheInstance, "Initializing the sim when it's already been done");
    gTheInstance = new plSimulationMgr();
    if (gTheInstance->InitSimulation())
    {
        gTheInstance->RegisterAs(kSimulationMgr_KEY);
        plgDispatch::Dispatch()->RegisterForExactType(plAgeLoadedMsg::Index(), gTheInstance->GetKey());
    }
    else
    {
        // There was an error when creating the PhysX simulation
        // ...then get rid of the simulation instance
        delete gTheInstance; // clean up the memory we allocated
        gTheInstance = nil;
    }
}

// when the app is going away completely
void plSimulationMgr::Shutdown()
{
    hsAssert(gTheInstance, "Simulation manager missing during shutdown.");
    if (gTheInstance)
    {
        plgDispatch::Dispatch()->UnRegisterForExactType(plAgeLoadedMsg::Index(), gTheInstance->GetKey());
        gTheInstance->UnRegisterAs(kSimulationMgr_KEY);     // this will destroy the instance
        gTheInstance = nil;
    }
}

plSimulationMgr* plSimulationMgr::GetInstance()
{
    return gTheInstance;
}

//////////////////////////////////////////////////////////////////////////

plSimulationMgr::plSimulationMgr()
    : fSuspended(true)
    , fLOSDispatch(new plLOSDispatch())
    , fSoundMgr(new plPhysicsSoundMgr)
    , fLog(nil)
{

}

bool plSimulationMgr::InitSimulation()
{
    fSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, NULL, &gErrorStream);
    if (!fSDK)
        return false; // client will handle this and ask user to install

    fLog = plStatusLogMgr::GetInstance().CreateStatusLog(40, "Simulation.log", plStatusLog::kFilledBackground | plStatusLog::kAlignToTop);
    fLog->AddLine("Initialized simulation mgr");

#ifndef PLASMA_EXTERNAL_RELEASE
    // If this is an internal build, enable the PhysX debugger
    fSDK->getFoundationSDK().getRemoteDebugger()->connect("localhost", 5425);
#endif

    if ( !plPXConvert::Validate() )
    {
#ifndef PLASMA_EXTERNAL_RELEASE
        hsMessageBox("Ageia's PhysX or Plasma offsets have changed, need to rewrite conversion code.","PhysX Error",MB_OK);
#endif
        return false; // client will handle this and ask user to install
    }

    return true;
}

plSimulationMgr::~plSimulationMgr()
{
    fLOSDispatch->UnRef();
    fLOSDispatch = nil;

    delete fSoundMgr;
    fSoundMgr = nil;

    hsAssert(fScenes.empty(), "Unreleased scenes at shutdown");

    if (fSDK)
        fSDK->release();

    delete fLog;
    fLog = nil;
}

NxScene* plSimulationMgr::GetScene(plKey world)
{
    if (!world)
        world = GetKey();

    NxScene* scene = fScenes[world];

    if (!scene)
    {
        NxSceneDesc sceneDesc;
        sceneDesc.gravity.set(0, 0, -32.174049f);
        sceneDesc.userTriggerReport = &gSensorReport;
        sceneDesc.userContactReport = &gContactReport;
        scene = fSDK->createScene(sceneDesc);

        // See "Advancing The Simulation State" in the PhysX SDK Documentation
        // This will cause PhysX to only update for our step size. If we call simulate
        // faster than that, PhysX will return immediately. If we call it slower than that,
        // PhysX will do some extra steps for us (isn't that nice?).
        // Anyway, this should be a good way to make us independent of the framerate.
        // If not, I blame the usual suspects (Tye, eap, etc...)
        scene->setTiming(kDefaultStepSize);

        // Most physicals use the default friction and restitution values, so we
        // make them the default.
        NxMaterial* mat = scene->getMaterialFromIndex(0);
        float rest = mat->getRestitution();
        float sfriction = mat->getStaticFriction();
        float dfriction = mat->getDynamicFriction();
        mat->setRestitution(0.5);
        mat->setStaticFriction(0.5);
        mat->setDynamicFriction(0.5);

        // By default we just leave all the collision groups enabled, since
        // PhysX already makes sure that things like statics and statics don't
        // collide.  However, we do make it so the avatar and dynamic blockers
        // only block avatars and dynamics.
        for (int i = 0; i < plSimDefs::kGroupMax; i++)
        {
            scene->setGroupCollisionFlag(i, plSimDefs::kGroupAvatarBlocker, false);
            scene->setGroupCollisionFlag(i, plSimDefs::kGroupDynamicBlocker, false);
            scene->setGroupCollisionFlag(i, plSimDefs::kGroupLOSOnly, false);
            scene->setGroupCollisionFlag(plSimDefs::kGroupLOSOnly, i, false);
        }
        scene->setGroupCollisionFlag(plSimDefs::kGroupAvatar, plSimDefs::kGroupAvatar, false);
        scene->setGroupCollisionFlag(plSimDefs::kGroupAvatar, plSimDefs::kGroupAvatarBlocker, true);
        scene->setGroupCollisionFlag(plSimDefs::kGroupDynamic, plSimDefs::kGroupDynamicBlocker, true);
        scene->setGroupCollisionFlag(plSimDefs::kGroupAvatar, plSimDefs::kGroupStatic, true);
        scene->setGroupCollisionFlag( plSimDefs::kGroupStatic, plSimDefs::kGroupAvatar, true);
        scene->setGroupCollisionFlag(plSimDefs::kGroupAvatar, plSimDefs::kGroupDynamic, true);
        
        // The dynamics are in actor group 1, everything else is in 0.  Request
        // a callback for whenever a dynamic touches something.
        scene->setActorGroupPairFlags(0, 1, NX_NOTIFY_ON_TOUCH);
        scene->setActorGroupPairFlags(1, 1, NX_NOTIFY_ON_TOUCH);

        fScenes[world] = scene;
    }

    return scene;
}

void plSimulationMgr::ReleaseScene(plKey world)
{
    if (!world)
        world = GetKey();

    SceneMap::iterator it = fScenes.find(world);
    hsAssert(it != fScenes.end(), "Unknown scene");
    if (it != fScenes.end())
    {
        NxScene* scene = it->second;
        if (scene->getNbActors() == 0)
        {
            fSDK->releaseScene(*scene);
            fScenes.erase(it);
        }
    }
}

void plSimulationMgr::ISendCollisionMsg(plKey receiver, plKey hitter, hsBool entering)
{
    DetectorLogYellow("Collision: %s is inside %s. Sending an %s msg", hitter ? hitter->GetName().c_str() : "(nil)",
                      receiver->GetName().c_str(), entering ? "'enter'" : "'exit'");
    plCollideMsg* msg = new plCollideMsg;
    msg->fOtherKey = hitter;
    msg->fEntering = entering;
    msg->AddReceiver(receiver);
    msg->Send();
}

void plSimulationMgr::UpdateDetectorsInScene(plKey world, plKey avatar, hsPoint3& pos, bool entering)
{
    // search thru the actors in a scene looking for convex hull detectors and see if the avatar is inside it
    // ... and then send appropiate collision message if needed
    NxScene* scene = GetScene(world);
    plSceneObject* avObj = plSceneObject::ConvertNoRef(avatar->ObjectIsLoaded());
    const plCoordinateInterface* ci = avObj->GetCoordinateInterface();
    hsPoint3 soPos = ci->GetWorldPos();
    if (scene)
    {
        uint32_t numActors = scene->getNbActors();
        NxActor** actors = scene->getActors();

        for (int i = 0; i < numActors; i++)
        {
            plPXPhysical* physical = (plPXPhysical*)actors[i]->userData;
            if (physical && physical->DoDetectorHullWorkaround())
            {
                if ( physical->IsObjectInsideHull(pos) )
                {
                    physical->SetInsideConvexHull(entering);
                    // we are entering this world... say we entered this detector
                    ISendCollisionMsg(physical->GetObjectKey(), avatar, entering);
                }
            }
        }
    }
}

void plSimulationMgr::UpdateAvatarInDetector(plKey world, plPXPhysical* detector)
{
    // search thru the actors in a scene looking for avatars that might be in the newly enabled detector region
    // ... and then send appropiate collision message if needed
    if ( detector->DoDetectorHullWorkaround() )
    {
        NxScene* scene = GetScene(world);
        if (scene)
        {
            uint32_t numActors = scene->getNbActors();
            NxActor** actors = scene->getActors();

            for (int i = 0; i < numActors; i++)
            {
                if ( actors[i]->userData == nil )
                {
                    // we go a controller
                    bool isController;
                    plPXPhysicalControllerCore* controller = plPXPhysicalControllerCore::GetController(*actors[i],&isController);
                    if (controller && controller->IsEnabled())
                    {
                        plKey avatar = controller->GetOwner();
                        plSceneObject* avObj = plSceneObject::ConvertNoRef(avatar->ObjectIsLoaded());
                        const plCoordinateInterface* ci;
                        if ( avObj && ( ci = avObj->GetCoordinateInterface() ) )
                        {
                            if ( detector->IsObjectInsideHull(ci->GetWorldPos()) )
                            {
                                detector->SetInsideConvexHull(true);
                                // we are entering this world... say we entered this detector
                                ISendCollisionMsg(detector->GetObjectKey(), avatar, true);
                            }
                        }
                    }
                }
            }
        }
    }
}

void plSimulationMgr::AddCollisionMsg(plKey hitee, plKey hitter, bool enter)
{
    // First, make sure we have no dupes
    for (CollisionVec::iterator it = fCollideMsgs.begin(); it != fCollideMsgs.end(); ++it)
    {
        plCollideMsg* pMsg = *it;

        // Should only ever be one receiver.
        // Oh, it seems we should update the hit status. The latest might be different than the older...
        // Even in the same frame >.<
        if (pMsg->fOtherKey == hitter && pMsg->GetReceiver(0) == hitee)
        {
            pMsg->fEntering = enter;
            DetectorLogRed("DUPLICATE COLLISION: %s hit %s",
                (hitter ? hitter->GetName().c_str() : "(nil)"),
                (hitee ? hitee->GetName().c_str() : "(nil)"));
            return;
        }
    }

    // Still here? Then this must be a unique hit!
    plCollideMsg* pMsg = new plCollideMsg;
    pMsg->AddReceiver(hitee);
    pMsg->fOtherKey = hitter;
    pMsg->fEntering = enter;
    fCollideMsgs.push_back(pMsg);
}

void plSimulationMgr::Advance(float delSecs)
{
    if (fSuspended)
        return;

    plProfile_IncCount(StepLen, (int)(delSecs*1000));

#ifndef PLASMA_EXTERNAL_RELASE
    uint32_t stepTime = hsTimer::GetPrecTickCount();
#endif
    plProfile_BeginTiming(Step);
    plPXPhysicalControllerCore::UpdatePrestep(delSecs);
    plPXPhysicalControllerCore::UpdatePoststep( delSecs);
    
    for (SceneMap::iterator it = fScenes.begin(); it != fScenes.end(); it++)
    {
        NxScene* scene = it->second;
        bool do_advance = true;
        if (fSubworldOptimization)
        {
            plKey world = (plKey)it->first;
            if (world == GetKey())
                world = nil;
            do_advance = plPXPhysicalControllerCore::AnyControllersInThisWorld(world);
        }
        if (do_advance)
        {
            scene->simulate(delSecs);
            scene->flushStream();
            scene->fetchResults(NX_RIGID_BODY_FINISHED, true);
        }
    }
    plPXPhysicalControllerCore::UpdatePostSimStep(delSecs);

    plProfile_EndTiming(Step);
#ifndef PLASMA_EXTERNAL_RELEASE
    if(plSimulationMgr::fDisplayAwakeActors)IDrawActiveActorList();
#endif 
    if (fExtraProfile)
    {
        int contacts = 0, dynActors = 0, dynShapes = 0, awake = 0, stShapes=0, actors=0, scenes=0, controllers=0 ;
        for (SceneMap::iterator it = fScenes.begin(); it != fScenes.end(); it++)
        {
            bool do_advance = true;
            if (fSubworldOptimization)
            {
                plKey world = (plKey)it->first;
                if (world == GetKey())
                    world = nil;
                do_advance = plPXPhysicalControllerCore::AnyControllersInThisWorld(world);
            }
            if (do_advance)
            {
                NxScene* scene = it->second;
                NxSceneStats stats;
                scene->getStats(stats);

                contacts += stats.numContacts;
                dynActors += stats.numDynamicActors;
                dynShapes += stats.numDynamicShapes;
                awake += stats.numDynamicActorsInAwakeGroups;
                stShapes += stats.numStaticShapes;
                actors += stats.numActors;
                scenes += 1;
                controllers += plPXPhysicalControllerCore::NumControllers();
            }
        }

        plProfile_IncCount(Awake, awake);
        plProfile_IncCount(Contacts, contacts);
        plProfile_IncCount(DynActors, dynActors);
        plProfile_IncCount(DynShapes, dynShapes);
        plProfile_IncCount(StaticShapes, stShapes);
        plProfile_IncCount(Actors, actors);
        plProfile_IncCount(Scenes, scenes);
        plProfile_IncCount(Controllers, controllers);
    }

    plProfile_IncCount(AnimatedPhysicals, plPXPhysical::fNumberAnimatedPhysicals);
    plProfile_IncCount(AnimatedActivators, plPXPhysical::fNumberAnimatedActivators);

    fSoundMgr->Update();

    plProfile_BeginTiming(ProcessSyncs);
    IProcessSynchs();
    plProfile_EndTiming(ProcessSyncs);

    plProfile_BeginTiming(UpdateContexts);
    ISendUpdates();
    plProfile_EndTiming(UpdateContexts);
}

void plSimulationMgr::ISendUpdates()
{
    for (CollisionVec::iterator it = fCollideMsgs.begin(); it != fCollideMsgs.end(); ++it)
    {
        plCollideMsg* pMsg = *it;
        DetectorLogYellow("Collision: %s was triggered by %s. Sending an %s msg", pMsg->GetReceiver(0)->GetName().c_str(),
                          pMsg->fOtherKey ? pMsg->fOtherKey->GetName().c_str() : "(nil)" , pMsg->fEntering ? "'enter'" : "'exit'");
        plgDispatch::Dispatch()->MsgSend(pMsg);
    }
    fCollideMsgs.clear();

    SceneMap::iterator it = fScenes.begin();
    for (; it != fScenes.end(); it++)
    {
        NxScene* scene = it->second;
        uint32_t numActors = scene->getNbActors();
        NxActor** actors = scene->getActors();

        for (int i = 0; i < numActors; i++)
        {
            plPXPhysical* physical = (plPXPhysical*)actors[i]->userData;
            if (physical)
            {
                // apply any hit forces
                physical->ApplyHitForce();

                if (physical->GetSceneNode())
                {
                    physical->SendNewLocation();
                }
                else
                {
                    // if there's no scene node, it's not active (probably about to be collected)
                    const plKey physKey = physical->GetKey();
                    if (physKey)
                    {
                        const plString &physName = physical->GetKeyName();
                        if (!physName.IsNull())
                        {
                            plSimulationMgr::Log("Removing physical <%s> because of missing scene node.\n", physName.c_str());
                        }
                    }
//                  Remove(physical);
                }
            }
        }

//      // iterate through the db types, which are powers-of-two enums.
//      for( plLOSDB db = static_cast<plLOSDB>(1) ;
//          db < plSimDefs::kLOSDBMax;
//          db = static_cast<plLOSDB>(db << 1) )
//      {
//          fLOSSolvers[db]->Resolve(fSubspace);
//      }
//      if(fNeedLOSCullPhase)
//      {
//          for( plLOSDB db = static_cast<plLOSDB>(1) ;
//              db < plSimDefs::kLOSDBMax;
//              db = static_cast<plLOSDB>(db << 1) )
//          {
//              fLOSSolvers[db]->Resolve(fSubspace);
//          }
//          fNeedLOSCullPhase = false;
//      }
    }
}

hsBool plSimulationMgr::MsgReceive(plMessage *msg)
{
    // Suspend/resume the simulation based on whether or not we're in an age...
    if (plAgeLoadedMsg* aMsg = plAgeLoadedMsg::ConvertNoRef(msg))
    {
        fSuspended = !aMsg->fLoaded;
        return true;
    }

    return hsKeyedObject::MsgReceive(msg);
}

/////////////////////////////////////////////////////////////////
//
//  RESOLUTION & TIMEOUT PARAMETERS
//
/////////////////////////////////////////////////////////////////

int plSimulationMgr::GetMaterialIdx(NxScene* scene, float friction, float restitution)
{
    if (friction == 0.5f && restitution == 0.5f)
        return 0;

    // Use the nutty PhysX method to search for a matching material
    #define kNumMatsPerCall 32
    NxMaterial* materials[kNumMatsPerCall];
    NxU32 iterator = 0;
    bool getMore = true;
    while (getMore)
    {
        int numMats = scene->getMaterialArray(materials, kNumMatsPerCall, iterator);

        for (int i = 0; i < numMats; i++)
        {
            if (materials[i]->getDynamicFriction() == friction &&
                materials[i]->getRestitution() == restitution)
            {
                return materials[i]->getMaterialIndex();
            }
        }

        getMore = (numMats == kNumMatsPerCall);
    }

    // Couldn't find the material, so create it
    NxMaterialDesc desc;
    desc.restitution = restitution;
    desc.dynamicFriction = friction;
    desc.staticFriction = friction;
    NxMaterial* mat = scene->createMaterial(desc);
    return mat->getMaterialIndex();
}

/////////////////////////////////////////////////////////////////
//
//  SYNCHRONIZATION
//  Very much a work in progress.
//  *** would like to synchronize interacting groups as an atomic unit
//  *** need a "morphing synch" that incrementally approaches the target
//
/////////////////////////////////////////////////////////////////

const double plSimulationMgr::SynchRequest::kDefaultTime = -1000.0;

void plSimulationMgr::ConsiderSynch(plPXPhysical* physical, plPXPhysical* other)
{
    if (physical->GetProperty(plSimulationInterface::kNoSynchronize) &&
        (!other || other->GetProperty(plSimulationInterface::kNoSynchronize)))
        return;

    // We only need to sync if a dynamic is colliding with something.
    // Set it up so the dynamic is in 'physical'
    if (other && other->GetGroup() == plSimDefs::kGroupDynamic)
    {
        plPXPhysical* temp = physical;
        physical = other;
        other = temp;
    }
    // Neither is dynamic, so we can exit now
    else if (physical->GetGroup() != plSimDefs::kGroupDynamic)
        return;

    bool syncPhys = !physical->GetProperty(plSimulationInterface::kNoSynchronize) &&
                    physical->IsDynamic() &&
                    physical->IsLocallyOwned();
    bool syncOther = other &&
                    !other->GetProperty(plSimulationInterface::kNoSynchronize) &&
                    other->IsDynamic() != 0.f &&
                    other->IsLocallyOwned();

    if (syncPhys)
    {
        double timeNow = hsTimer::GetSysSeconds();
        double timeElapsed = timeNow - physical->GetLastSyncTime();

        // If both objects are capable of syncing, we want to do it at the same
        // time, so no interpenetration issues pop up on other clients
        if (syncOther)
            timeElapsed = hsMaximum(timeElapsed, timeNow - other->GetLastSyncTime());

        // Set the sync time to 1 second from the last sync
        double syncTime = 0.0;
        if (timeElapsed > 1.0)
            syncTime = hsTimer::GetSysSeconds();
        else
            syncTime = hsTimer::GetSysSeconds() + (1.0 - timeElapsed);

        // This line will create and insert the request if it's not there already.
        SynchRequest& physReq = fPendingSynchs[physical];
        if (physReq.fTime == SynchRequest::kDefaultTime)
            physReq.fKey = physical->GetKey();
        physReq.fTime = syncTime;

        if (syncOther)
        {
            SynchRequest& otherReq = fPendingSynchs[other];
            if (otherReq.fTime == SynchRequest::kDefaultTime)
                otherReq.fKey = other->GetKey();
            otherReq.fTime = syncTime;
        }
    }
}

void plSimulationMgr::IProcessSynchs()
{
    double time = hsTimer::GetSysSeconds();

    PhysSynchMap::iterator i = fPendingSynchs.begin();

    while (i != fPendingSynchs.end())
    {
        SynchRequest req = (*i).second;
        if (req.fKey->ObjectIsLoaded())
        {
            plPXPhysical* phys = (*i).first;
            bool timesUp = (time >= req.fTime);
            bool allQuiet = false;//phys->GetActo GetBody()->isActive() == false;

            if (timesUp || allQuiet)
            {
                phys->DirtySynchState(kSDLPhysical, plSynchedObject::kBCastToClients);
                i = fPendingSynchs.erase(i);
            }
            else
            {
                i++;
            }
        }
        else
        {
            i = fPendingSynchs.erase(i);
        }
    }
}

void plSimulationMgr::Log(const char * fmt, ...)
{
    if(gTheInstance)
    {
        plStatusLog* log = GetInstance()->fLog;
        if(log)
        {
            va_list args;
            va_start(args, fmt);
            log->AddLineV(fmt, args);
            va_end(args);
        }
    }
}

void plSimulationMgr::LogV(const char* formatStr, va_list args)
{
    if(gTheInstance)
    {
        plStatusLog * log = GetInstance()->fLog;
        if(log)
        {
            log->AddLineV(formatStr, args);
        }
    }
}

void plSimulationMgr::ClearLog()
{
    if(gTheInstance)
    {
        plStatusLog *log = GetInstance()->fLog;
        if(log)
        {
            log->Clear();
        }
    }
}
#ifndef PLASMA_EXTERNAL_RELEASE

void plSimulationMgr::IDrawActiveActorList()
{
    plDebugText     &debugTxt = plDebugText::Instance();
    char            strBuf[ 2048 ];
    int             lineHeight = debugTxt.GetFontSize() + 4;
    uint32_t          scrnWidth, scrnHeight;

    debugTxt.GetScreenSize( &scrnWidth, &scrnHeight );
    int y = 10;
    int x = 10;

    sprintf(strBuf, "Number of scenes: %d", fScenes.size());
    debugTxt.DrawString(x, y, strBuf);
    y += lineHeight;
    int sceneNumber=1;
    for (SceneMap::iterator it = fScenes.begin(); it != fScenes.end(); it++)
    {
        
        sprintf(strBuf, "Scene: %s",it->first->GetName().c_str());
        debugTxt.DrawString(x, y, strBuf);
        y += lineHeight;
        uint32_t numActors =it->second->getNbActors();
        NxActor** actors =it->second->getActors();
        for(uint32_t i=0;i<numActors;i++)
        {
            if(!actors[i]->isSleeping())
            {
                sprintf(strBuf,"\t%s",actors[i]->getName());
                debugTxt.DrawString(x, y, strBuf);
                y += lineHeight;
            }
        }
        sceneNumber++;
    }
}
#endif //PLASMA_EXTERNAL_RELEASE
