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.

1030 lines
33 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 "plSimulationMgr.h"
#include "NxPhysics.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 "../plModifier/plDetectorLog.h"
#ifndef PLASMA_EXTERNAL_RELEASE
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(),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(),triggerPhys->GetGroup(),otherPhys->GetObjectKey()->GetName(),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());
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());
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());
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(),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(),status & NX_TRIGGER_ON_ENTER ? "enter" : "exit");
SendCollisionMsg(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());
}
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(),status & NX_TRIGGER_ON_ENTER ? "enter" : "exit");
SendCollisionMsg(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());
}
if (!(status & NX_TRIGGER_ON_ENTER) && !(status & NX_TRIGGER_ON_LEAVE) )
{
if (plSimulationMgr::fExtraProfile)
DetectorLogRed("<--Kill collision %s :failed event(CH)",triggerPhys->GetObjectKey()->GetName());
}
}
else
{
#endif // USE_PHYSX_CONVEXHULL_WORKAROUND
if (status & NX_TRIGGER_ON_ENTER)
{
if (plSimulationMgr::fExtraProfile)
DetectorLogRed("-->Send Collision %s %s",triggerPhys->GetObjectKey()->GetName(),status & NX_TRIGGER_ON_ENTER ? "enter" : "exit");
SendCollisionMsg(triggerPhys->GetObjectKey(), otherKey, true);
}
if (status & NX_TRIGGER_ON_LEAVE)
{
if (plSimulationMgr::fExtraProfile)
DetectorLogRed("-->Send Collision %s %s",triggerPhys->GetObjectKey()->GetName(),status & NX_TRIGGER_ON_ENTER ? "enter" : "exit");
SendCollisionMsg(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());
}
#ifdef USE_PHYSX_CONVEXHULL_WORKAROUND
}
#endif // USE_PHYSX_CONVEXHULL_WORKAROUND
}
}
void SendCollisionMsg(plKey receiver, plKey hitter, hsBool entering)
{
plCollideMsg* msg = TRACKED_NEW plCollideMsg;
msg->fOtherKey = hitter;
msg->fEntering = entering;
msg->AddReceiver(receiver);
// msg->Send();\
//placing message in a list to be fired off after sim step
plSimulationMgr::GetInstance()->AddCollisionMsg(msg);
}
} 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;
// This class allows PhysX to use our heap manager
static class HeapAllocator : public NxUserAllocator {
public:
void * malloc (NxU32 size) {
return ALLOC(size);
}
void * mallocDEBUG (NxU32 size, const char * fileName, int line) {
return MemAlloc(size, 0, fileName, line);
}
void * realloc (void * memory, NxU32 size) {
return REALLOC(memory, size);
}
void free (void * memory) {
FREE(memory);
}
} gHeapAllocator;
/////////////////////////////////////////////////////////////////
//
// 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 = TRACKED_NEW plSimulationMgr();
if (gTheInstance->InitSimulation())
{
gTheInstance->RegisterAs(kSimulationMgr_KEY);
gTheInstance->GetKey()->RefObject();
}
else
{
// There was an error when creating the PhysX simulation
// ...then get rid of the simulation instance
DEL(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)
{
// UnRef to match our Ref in Init(). Unless something strange is
// going on, this should destroy the instance and set gTheInstance to nil.
// gTheInstance->GetKey()->UnRefObject();
gTheInstance->UnRegister(); // this will destroy the instance
gTheInstance = nil;
}
}
plSimulationMgr* plSimulationMgr::GetInstance()
{
return gTheInstance;
}
//////////////////////////////////////////////////////////////////////////
plSimulationMgr::plSimulationMgr()
: fSuspended(true)
, fMaxDelta(kDefaultMaxDelta)
, fStepSize(kDefaultStepSize)
, fLOSDispatch(TRACKED_NEW plLOSDispatch())
, fSoundMgr(new plPhysicsSoundMgr)
, fLog(nil)
{
}
bool plSimulationMgr::InitSimulation()
{
fSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, &gHeapAllocator, &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)
{
UInt32 maxSteps = (UInt32)hsCeil(fMaxDelta / fStepSize);
NxSceneDesc sceneDesc;
sceneDesc.gravity.set(0, 0, -32.174049f);
sceneDesc.userTriggerReport = &gSensorReport;
sceneDesc.userContactReport = &gContactReport;
sceneDesc.maxTimestep = fStepSize;
sceneDesc.maxIter = maxSteps;
scene = fSDK->createScene(sceneDesc);
// 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)
{
plCollideMsg* msg = TRACKED_NEW plCollideMsg;
msg->fOtherKey = hitter;
msg->fEntering = entering;
msg->AddReceiver(receiver);
msg->Send();
}
void plSimulationMgr::AddCollisionMsg(plCollideMsg* msg)
{
fCollisionMessages.Append(msg);
}
void plSimulationMgr::IDispatchCollisionMessages()
{
if(fCollisionMessages.GetCount())
{
#ifndef PLASMA_EXTERNAL_RELEASE
DetectorLog("--------------------------------------------------");
DetectorLog("Dispatching collision messages from last sim step");
#endif
for(int i=0; i<fCollisionMessages.GetCount();i++)
{
#ifndef PLASMA_EXTERNAL_RELEASE
DetectorLog("%s was hit by %s. Sending an %s message",fCollisionMessages[i]->GetReceiver(0)->GetName(),
fCollisionMessages[i]->GetSender()?fCollisionMessages[i]->GetSender()->GetName():"An Avatar",
fCollisionMessages[i]->fEntering? "enter" : "exit");
#endif
fCollisionMessages[i]->Send();
}
#ifndef PLASMA_EXTERNAL_RELEASE
DetectorLog("--------------------------------------------------");
#endif
fCollisionMessages.SetCount(0);
}
}
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 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 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::Advance(float delSecs)
{
if (fSuspended)
return;
if (delSecs > fMaxDelta)
{
if (fExtraProfile)
Log("Step clamped from %f to limit of %f", delSecs, fMaxDelta);
delSecs = fMaxDelta;
}
plProfile_IncCount(StepLen, (int)(delSecs*1000));
#ifndef PLASMA_EXTERNAL_RELASE
UInt32 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);
//sending off and clearing the Collision Messages generated by scene->simulate
IDispatchCollisionMessages();
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()
{
SceneMap::iterator it = fScenes.begin();
for (; it != fScenes.end(); it++)
{
NxScene* scene = it->second;
UInt32 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 char *physName = physical->GetKeyName();
if (physName)
{
plSimulationMgr::Log("Removing physical <%s> because of missing scene node.\n", physName);
}
}
// 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)
{
return hsKeyedObject::MsgReceive(msg);
}
/////////////////////////////////////////////////////////////////
//
// RESOLUTION & TIMEOUT PARAMETERS
//
/////////////////////////////////////////////////////////////////
void plSimulationMgr::SetMaxDelta(float maxDelta)
{
fMaxDelta = maxDelta;
}
float plSimulationMgr::GetMaxDelta() const
{
return fMaxDelta;
}
void plSimulationMgr::SetStepsPerSecond(int stepsPerSecond)
{
fStepSize = 1.0f / (float)stepsPerSecond;
}
int plSimulationMgr::GetStepsPerSecond()
{
return (int)((1.0 / fStepSize) + 0.5f); // round to nearest int
}
int plSimulationMgr::GetMaterialIdx(NxScene* scene, hsScalar friction, hsScalar 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
#include "../plPipeline/plDebugText.h"
void plSimulationMgr::IDrawActiveActorList()
{
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, "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());
debugTxt.DrawString(x, y, strBuf);
y += lineHeight;
UInt32 numActors =it->second->getNbActors();
NxActor** actors =it->second->getActors();
for(UInt32 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