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.
 
 
 
 
 

1020 lines
31 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/>.
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 "NxCooking.h"
#include "hsTimer.h"
#include "plProfile.h"
#include "plPXPhysical.h"
#include "plPXPhysicalControllerCore.h"
#include "plPXConvert.h"
#include "plPXSubWorld.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)
{
plKey otherKey = nil;
hsBool doReport = false;
// Get our trigger physical. This should definitely have a plPXPhysical
plPXPhysical* triggerPhys = (plPXPhysical*)triggerShape.getActor().userData;
// Get the triggerer. If it doesn't have a plPXPhyscial, it's an avatar
plPXPhysical* otherPhys = (plPXPhysical*)otherShape.getActor().userData;
if (otherPhys)
{
otherKey = otherPhys->GetObjectKey();
doReport = triggerPhys->DoReportOn((plSimDefs::Group)otherPhys->GetGroup());
}
else
{
plPXPhysicalControllerCore* controller = plPXPhysicalControllerCore::GetController(otherShape.getActor());
if (controller)
{
otherKey = controller->GetOwner();
doReport = triggerPhys->DoReportOn(plSimDefs::kGroupAvatar);
}
}
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.15 // if the step is greater than .15 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);
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)
{
gTheInstance->UnRegisterAs(kSimulationMgr_KEY); // this will destroy the instance
gTheInstance = nil;
}
}
plSimulationMgr* plSimulationMgr::GetInstance()
{
return gTheInstance;
}
//////////////////////////////////////////////////////////////////////////
plSimulationMgr::plSimulationMgr()
: fSuspended(true)
, fMaxDelta(kDefaultMaxDelta)
, fStepSize(kDefaultStepSize)
, fAccumulator(0.0f)
, fStepCount(0)
, 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 PhysX SDK");
if (!NxInitCooking(NULL, &gErrorStream))
{
fLog->AddLine("Phailed to init NxCooking");
fSDK->release();
fSDK = NULL;
return false;
}
#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)
{
NxCloseCooking();
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);
// The world key is assumed to be loaded (or null for main world) if we are here.
// As such, let us grab the plSceneObject's PXSubWorld definition to figure out
// what gravity should look like. Who knows, we might be in MC Escher land...
NxVec3 gravity(X_GRAVITY, Y_GRAVITY, Z_GRAVITY);
if (plSceneObject* so = plSceneObject::ConvertNoRef(world->VerifyLoaded())) {
if (plPXSubWorld* subworld = plPXSubWorld::ConvertNoRef(so->GetGenericInterface(plPXSubWorld::Index()))) {
gravity = plPXConvert::Vector(subworld->GetGravity());
}
}
NxSceneDesc sceneDesc;
sceneDesc.gravity = gravity;
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(i, plSimDefs::kGroupAvatarKinematic, 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);
// Kinematically controlled avatars interact with detectors and dynamics
scene->setGroupCollisionFlag(plSimDefs::kGroupAvatarKinematic, plSimDefs::kGroupDetector, true);
scene->setGroupCollisionFlag(plSimDefs::kGroupAvatarKinematic, 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
plPXPhysicalControllerCore* controller = plPXPhysicalControllerCore::GetController(*actors[i]);
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;
fAccumulator += delSecs;
if (fAccumulator < fStepSize)
{
// Not enough time has passed to perform a substep.
plPXPhysicalControllerCore::UpdateNonPhysical(fAccumulator / fStepSize);
return;
}
else if (fAccumulator > fMaxDelta)
{
if (fExtraProfile)
Log("Step clamped from %f to limit of %f", fAccumulator, fMaxDelta);
fAccumulator = fMaxDelta;
}
++fStepCount;
// Perform as many whole substeps as possible saving the remainder in our accumulator.
int numSubSteps = (int)(fAccumulator / fStepSize + 0.000001f);
float delta = numSubSteps * fStepSize;
fAccumulator -= delta;
plProfile_IncCount(StepLen, (int)(delta*1000));
plProfile_BeginTiming(Step);
plPXPhysicalControllerCore::Apply(delta);
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(delta);
scene->flushStream();
scene->fetchResults(NX_RIGID_BODY_FINISHED, true);
}
}
plPXPhysicalControllerCore::Update(numSubSteps, fAccumulator / fStepSize);
//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)
{
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;
// }
}
}
/////////////////////////////////////////////////////////////////
//
// 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