|
|
|
/*==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 "plPXPhysical.h"
|
|
|
|
|
|
|
|
#include "NxPhysics.h"
|
|
|
|
#include "NxCooking.h"
|
|
|
|
|
|
|
|
#include "hsResMgr.h"
|
|
|
|
#include "hsStream.h"
|
|
|
|
#include "hsTimer.h"
|
|
|
|
#include "plProfile.h"
|
|
|
|
#include "hsQuat.h"
|
|
|
|
#include "hsSTLStream.h"
|
|
|
|
|
|
|
|
#include "plSimulationMgr.h"
|
|
|
|
#include "../plPhysical/plPhysicalSDLModifier.h"
|
|
|
|
#include "../plPhysical/plPhysicalSndGroup.h"
|
|
|
|
#include "../plPhysical/plPhysicalProxy.h"
|
|
|
|
#include "../pnSceneObject/plSimulationInterface.h"
|
|
|
|
#include "../pnSceneObject/plCoordinateInterface.h"
|
|
|
|
|
|
|
|
#include "../pnKeyedObject/plKey.h"
|
|
|
|
#include "../pnMessage/plCorrectionMsg.h"
|
|
|
|
#include "../pnMessage/plNodeRefMsg.h"
|
|
|
|
#include "../pnMessage/plObjRefMsg.h"
|
|
|
|
#include "../pnMessage/plSDLModifierMsg.h"
|
|
|
|
#include "../plMessage/plSimStateMsg.h"
|
|
|
|
#include "../plMessage/plSimInfluenceMsg.h"
|
|
|
|
#include "../plMessage/plLinearVelocityMsg.h"
|
|
|
|
#include "../plMessage/plAngularVelocityMsg.h"
|
|
|
|
#include "../plDrawable/plDrawableGenerator.h"
|
|
|
|
#include "../plNetClient/plNetClientMgr.h"
|
|
|
|
#include "../plNetTransport/plNetTransportMember.h"
|
|
|
|
#include "../plStatusLog/plStatusLog.h"
|
|
|
|
#include "plPXConvert.h"
|
|
|
|
#include "plPXPhysicalControllerCore.h"
|
|
|
|
#include "plPXSubWorld.h"
|
|
|
|
|
|
|
|
#include "../plModifier/plDetectorLog.h"
|
|
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
#define SpamMsg(x) x
|
|
|
|
#else
|
|
|
|
#define SpamMsg(x)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define LogActivate(func) if (fActor->isSleeping()) SimLog("%s activated by %s", GetKeyName(), func);
|
|
|
|
|
|
|
|
plProfile_Extern(MaySendLocation);
|
|
|
|
plProfile_Extern(LocationsSent);
|
|
|
|
plProfile_Extern(PhysicsUpdates);
|
|
|
|
|
|
|
|
static void ClearMatrix(hsMatrix44 &m)
|
|
|
|
{
|
|
|
|
m.fMap[0][0] = 0.0f; m.fMap[0][1] = 0.0f; m.fMap[0][2] = 0.0f; m.fMap[0][3] = 0.0f;
|
|
|
|
m.fMap[1][0] = 0.0f; m.fMap[1][1] = 0.0f; m.fMap[1][2] = 0.0f; m.fMap[1][3] = 0.0f;
|
|
|
|
m.fMap[2][0] = 0.0f; m.fMap[2][1] = 0.0f; m.fMap[2][2] = 0.0f; m.fMap[2][3] = 0.0f;
|
|
|
|
m.fMap[3][0] = 0.0f; m.fMap[3][1] = 0.0f; m.fMap[3][2] = 0.0f; m.fMap[3][3] = 0.0f;
|
|
|
|
m.NotIdentity();
|
|
|
|
}
|
|
|
|
|
|
|
|
int plPXPhysical::fNumberAnimatedPhysicals = 0;
|
|
|
|
int plPXPhysical::fNumberAnimatedActivators = 0;
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// plPXPhysical IMPLEMENTATION
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
plPXPhysical::plPXPhysical()
|
|
|
|
: fSDLMod(nil)
|
|
|
|
, fActor(nil)
|
|
|
|
, fLOSDBs(plSimDefs::kLOSDBNone)
|
|
|
|
, fGroup(plSimDefs::kGroupMax)
|
|
|
|
, fReportsOn(0)
|
|
|
|
, fLastSyncTime(0.0f)
|
|
|
|
, fProxyGen(nil)
|
|
|
|
, fSndGroup(nil)
|
|
|
|
, fWorldHull(nil)
|
|
|
|
, fSaveTriangles(nil)
|
|
|
|
, fHullNumberPlanes(0)
|
|
|
|
, fWeWereHit(false)
|
|
|
|
, fHitForce(0,0,0)
|
|
|
|
, fHitPos(0,0,0)
|
|
|
|
, fInsideConvexHull(false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
plPXPhysical::~plPXPhysical()
|
|
|
|
{
|
|
|
|
SpamMsg(plSimulationMgr::Log("Destroying physical %s", GetKeyName()));
|
|
|
|
|
|
|
|
if (fActor)
|
|
|
|
{
|
|
|
|
// Grab any mesh we may have (they need to be released manually)
|
|
|
|
NxConvexMesh* convexMesh = nil;
|
|
|
|
NxTriangleMesh* triMesh = nil;
|
|
|
|
NxShape* shape = fActor->getShapes()[0];
|
|
|
|
if (NxConvexShape* convexShape = shape->isConvexMesh())
|
|
|
|
convexMesh = &convexShape->getConvexMesh();
|
|
|
|
else if (NxTriangleMeshShape* trimeshShape = shape->isTriangleMesh())
|
|
|
|
triMesh = &trimeshShape->getTriangleMesh();
|
|
|
|
|
|
|
|
if (!fActor->isDynamic())
|
|
|
|
plPXPhysicalControllerCore::RebuildCache();
|
|
|
|
|
|
|
|
if (fActor->isDynamic() && fActor->readBodyFlag(NX_BF_KINEMATIC))
|
|
|
|
{
|
|
|
|
if (fGroup == plSimDefs::kGroupDynamic)
|
|
|
|
fNumberAnimatedPhysicals--;
|
|
|
|
else
|
|
|
|
fNumberAnimatedActivators--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Release the actor
|
|
|
|
NxScene* scene = plSimulationMgr::GetInstance()->GetScene(fWorldKey);
|
|
|
|
scene->releaseActor(*fActor);
|
|
|
|
fActor = nil;
|
|
|
|
|
|
|
|
// Now that the actor is freed, release the mesh
|
|
|
|
if (convexMesh)
|
|
|
|
plSimulationMgr::GetInstance()->GetSDK()->releaseConvexMesh(*convexMesh);
|
|
|
|
if (triMesh)
|
|
|
|
plSimulationMgr::GetInstance()->GetSDK()->releaseTriangleMesh(*triMesh);
|
|
|
|
|
|
|
|
// Release the scene, so it can be cleaned up if no one else is using it
|
|
|
|
plSimulationMgr::GetInstance()->ReleaseScene(fWorldKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fWorldHull)
|
|
|
|
delete [] fWorldHull;
|
|
|
|
if (fSaveTriangles)
|
|
|
|
delete [] fSaveTriangles;
|
|
|
|
|
|
|
|
delete fProxyGen;
|
|
|
|
|
|
|
|
// remove sdl modifier
|
|
|
|
plSceneObject* sceneObj = plSceneObject::ConvertNoRef(fObjectKey->ObjectIsLoaded());
|
|
|
|
if (sceneObj && fSDLMod)
|
|
|
|
{
|
|
|
|
sceneObj->RemoveModifier(fSDLMod);
|
|
|
|
}
|
|
|
|
delete fSDLMod;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void MakeBoxFromHull(NxConvexMesh* convexMesh, NxBoxShapeDesc& box)
|
|
|
|
{
|
|
|
|
NxConvexMeshDesc desc;
|
|
|
|
convexMesh->saveToDesc(desc);
|
|
|
|
|
|
|
|
hsScalar minX, minY, minZ, maxX, maxY, maxZ;
|
|
|
|
minX = minY = minZ = FLT_MAX;
|
|
|
|
maxX = maxY = maxZ = -FLT_MAX;
|
|
|
|
|
|
|
|
for (int i = 0; i < desc.numVertices; i++)
|
|
|
|
{
|
|
|
|
float* point = (float*)(((char*)desc.points) + desc.pointStrideBytes*i);
|
|
|
|
float x = point[0];
|
|
|
|
float y = point[1];
|
|
|
|
float z = point[2];
|
|
|
|
|
|
|
|
minX = hsMinimum(minX, x);
|
|
|
|
minY = hsMinimum(minY, y);
|
|
|
|
minZ = hsMinimum(minZ, z);
|
|
|
|
maxX = hsMaximum(maxX, x);
|
|
|
|
maxY = hsMaximum(maxY, y);
|
|
|
|
maxZ = hsMaximum(maxZ, z);
|
|
|
|
}
|
|
|
|
|
|
|
|
float xWidth = maxX - minX;
|
|
|
|
float yWidth = maxY - minY;
|
|
|
|
float zWidth = maxZ - minZ;
|
|
|
|
box.dimensions.x = xWidth / 2;
|
|
|
|
box.dimensions.y = yWidth / 2;
|
|
|
|
box.dimensions.z = zWidth / 2;
|
|
|
|
|
|
|
|
//hsMatrix44 mat;
|
|
|
|
//box.localPose.getRowMajor44(&mat.fMap[0][0]);
|
|
|
|
hsPoint3 trans(minX + (xWidth / 2), minY + (yWidth / 2), minZ + (zWidth / 2));
|
|
|
|
//mat.SetTranslate(&trans);
|
|
|
|
//box.localPose.setRowMajor44(&mat.fMap[0][0]);
|
|
|
|
|
|
|
|
hsMatrix44 boxL2W;
|
|
|
|
boxL2W.Reset();
|
|
|
|
boxL2W.SetTranslate(&trans);
|
|
|
|
plPXConvert::Matrix(boxL2W, box.localPose);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::IMakeHull(NxConvexMesh* convexMesh, hsMatrix44 l2w)
|
|
|
|
{
|
|
|
|
NxConvexMeshDesc desc;
|
|
|
|
convexMesh->saveToDesc(desc);
|
|
|
|
|
|
|
|
// make sure there are some triangles to work with
|
|
|
|
if (desc.numTriangles == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// get rid of any we may have already had
|
|
|
|
if (fSaveTriangles)
|
|
|
|
delete [] fSaveTriangles;
|
|
|
|
|
|
|
|
fHullNumberPlanes = desc.numTriangles;
|
|
|
|
fSaveTriangles = TRACKED_NEW hsPoint3[fHullNumberPlanes*3];
|
|
|
|
|
|
|
|
for (int i = 0; i < desc.numTriangles; i++)
|
|
|
|
{
|
|
|
|
UInt32* triangle = (UInt32*)(((char*)desc.triangles) + desc.triangleStrideBytes*i);
|
|
|
|
float* vertex1 = (float*)(((char*)desc.points) + desc.pointStrideBytes*triangle[0]);
|
|
|
|
float* vertex2 = (float*)(((char*)desc.points) + desc.pointStrideBytes*triangle[1]);
|
|
|
|
float* vertex3 = (float*)(((char*)desc.points) + desc.pointStrideBytes*triangle[2]);
|
|
|
|
hsPoint3 pt1(vertex1[0],vertex1[1],vertex1[2]);
|
|
|
|
hsPoint3 pt2(vertex2[0],vertex2[1],vertex2[2]);
|
|
|
|
hsPoint3 pt3(vertex3[0],vertex3[1],vertex3[2]);
|
|
|
|
|
|
|
|
fSaveTriangles[(i*3)+0] = pt1;
|
|
|
|
fSaveTriangles[(i*3)+1] = pt2;
|
|
|
|
fSaveTriangles[(i*3)+2] = pt3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::ISetHullToWorldWTriangles()
|
|
|
|
{
|
|
|
|
// if we have a detector hull and the world hasn't been updated
|
|
|
|
if (fWorldHull == nil)
|
|
|
|
{
|
|
|
|
fWorldHull = TRACKED_NEW hsPlane3[fHullNumberPlanes];
|
|
|
|
// use the local2world from the physics engine so that it matches the transform of the positions from the triggerees
|
|
|
|
hsMatrix44 l2w;
|
|
|
|
plPXConvert::Matrix(fActor->getGlobalPose(), l2w);
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fHullNumberPlanes; i++ )
|
|
|
|
{
|
|
|
|
hsPoint3 pt1 = fSaveTriangles[i*3];
|
|
|
|
hsPoint3 pt2 = fSaveTriangles[(i*3)+1];
|
|
|
|
hsPoint3 pt3 = fSaveTriangles[(i*3)+2];
|
|
|
|
|
|
|
|
// local to world translation
|
|
|
|
pt1 = l2w * pt1;
|
|
|
|
pt2 = l2w * pt2;
|
|
|
|
pt3 = l2w * pt3;
|
|
|
|
|
|
|
|
hsPlane3 plane(&pt1, &pt2, &pt3);
|
|
|
|
fWorldHull[i] = plane;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hsBool plPXPhysical::IsObjectInsideHull(const hsPoint3& pos)
|
|
|
|
{
|
|
|
|
if (fSaveTriangles)
|
|
|
|
{
|
|
|
|
ISetHullToWorldWTriangles();
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fHullNumberPlanes; i++ )
|
|
|
|
{
|
|
|
|
if (!ITestPlane(pos, fWorldHull[i]))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plPXPhysical::Should_I_Trigger(hsBool enter, hsPoint3& pos)
|
|
|
|
{
|
|
|
|
// see if we are inside the detector hull, if so, then don't trigger
|
|
|
|
bool trigger = false;
|
|
|
|
bool inside = IsObjectInsideHull(pos);
|
|
|
|
if ( !inside)
|
|
|
|
{
|
|
|
|
trigger = true;
|
|
|
|
fInsideConvexHull = enter;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// catch those rare cases on slow machines that miss the collision before avatar penetrated the face
|
|
|
|
if (enter && !fInsideConvexHull)
|
|
|
|
{
|
|
|
|
#ifdef PHYSX_SAVE_TRIGGERS_WORKAROUND
|
|
|
|
trigger = true;
|
|
|
|
fInsideConvexHull = enter;
|
|
|
|
DetectorLogSpecial("**>Saved a missing enter collision: %s",GetObjectKey()->GetName());
|
|
|
|
#else
|
|
|
|
DetectorLogSpecial("**>Could have saved a missing enter collision: %s",GetObjectKey()->GetName());
|
|
|
|
#endif PHYSX_SAVE_TRIGGERS_WORKAROUND
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return trigger;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hsBool plPXPhysical::Init()
|
|
|
|
{
|
|
|
|
hsBool startAsleep = false;
|
|
|
|
|
|
|
|
fGroup = fRecipe.group;
|
|
|
|
fReportsOn = fRecipe.reportsOn;
|
|
|
|
fObjectKey = fRecipe.objectKey;
|
|
|
|
fSceneNode = fRecipe.sceneNode;
|
|
|
|
|
|
|
|
NxActorDesc actorDesc;
|
|
|
|
NxSphereShapeDesc sphereDesc;
|
|
|
|
NxConvexShapeDesc convexShapeDesc;
|
|
|
|
NxTriangleMeshShapeDesc trimeshShapeDesc;
|
|
|
|
NxBoxShapeDesc boxDesc;
|
|
|
|
|
|
|
|
plPXConvert::Matrix(fRecipe.l2s, actorDesc.globalPose);
|
|
|
|
|
|
|
|
switch (fRecipe.bounds)
|
|
|
|
{
|
|
|
|
case plSimDefs::kSphereBounds:
|
|
|
|
{
|
|
|
|
hsMatrix44 sphereL2W;
|
|
|
|
sphereL2W.Reset();
|
|
|
|
sphereL2W.SetTranslate(&fRecipe.offset);
|
|
|
|
|
|
|
|
sphereDesc.radius = fRecipe.radius;
|
|
|
|
plPXConvert::Matrix(sphereL2W, sphereDesc.localPose);
|
|
|
|
sphereDesc.group = fRecipe.group;
|
|
|
|
actorDesc.shapes.pushBack(&sphereDesc);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case plSimDefs::kHullBounds:
|
|
|
|
// FIXME PHYSX - Remove when hull detection is fixed
|
|
|
|
// If this is read time (ie, meshStream is nil), turn the convex hull
|
|
|
|
// into a box. That way the data won't have to change when convex hulls
|
|
|
|
// actually work right.
|
|
|
|
if (fRecipe.group == plSimDefs::kGroupDetector && fRecipe.meshStream == nil)
|
|
|
|
{
|
|
|
|
#ifdef USE_BOXES_FOR_DETECTOR_HULLS
|
|
|
|
MakeBoxFromHull(fRecipe.convexMesh, boxDesc);
|
|
|
|
plSimulationMgr::GetInstance()->GetSDK()->releaseConvexMesh(*fRecipe.convexMesh);
|
|
|
|
boxDesc.group = fRecipe.group;
|
|
|
|
actorDesc.shapes.push_back(&boxDesc);
|
|
|
|
#else
|
|
|
|
#ifdef USE_PHYSX_CONVEXHULL_WORKAROUND
|
|
|
|
// make a hull of planes for testing IsInside
|
|
|
|
IMakeHull(fRecipe.convexMesh,fRecipe.l2s);
|
|
|
|
#endif // USE_PHYSX_CONVEXHULL_WORKAROUND
|
|
|
|
convexShapeDesc.meshData = fRecipe.convexMesh;
|
|
|
|
convexShapeDesc.userData = fRecipe.meshStream;
|
|
|
|
convexShapeDesc.group = fRecipe.group;
|
|
|
|
actorDesc.shapes.pushBack(&convexShapeDesc);
|
|
|
|
#endif // USE_BOXES_FOR_DETECTOR_HULLS
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
convexShapeDesc.meshData = fRecipe.convexMesh;
|
|
|
|
convexShapeDesc.userData = fRecipe.meshStream;
|
|
|
|
convexShapeDesc.group = fRecipe.group;
|
|
|
|
actorDesc.shapes.pushBack(&convexShapeDesc);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case plSimDefs::kBoxBounds:
|
|
|
|
{
|
|
|
|
boxDesc.dimensions = plPXConvert::Point(fRecipe.bDimensions);
|
|
|
|
|
|
|
|
hsMatrix44 boxL2W;
|
|
|
|
boxL2W.Reset();
|
|
|
|
boxL2W.SetTranslate(&fRecipe.bOffset);
|
|
|
|
plPXConvert::Matrix(boxL2W, boxDesc.localPose);
|
|
|
|
|
|
|
|
boxDesc.group = fRecipe.group;
|
|
|
|
actorDesc.shapes.push_back(&boxDesc);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case plSimDefs::kExplicitBounds:
|
|
|
|
case plSimDefs::kProxyBounds:
|
|
|
|
if (fRecipe.group == plSimDefs::kGroupDetector)
|
|
|
|
{
|
|
|
|
SimLog("Someone using an Exact on a detector region: %s", GetKeyName());
|
|
|
|
}
|
|
|
|
trimeshShapeDesc.meshData = fRecipe.triMesh;
|
|
|
|
trimeshShapeDesc.userData = fRecipe.meshStream;
|
|
|
|
trimeshShapeDesc.group = fRecipe.group;
|
|
|
|
actorDesc.shapes.pushBack(&trimeshShapeDesc);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
hsAssert(false, "Unknown geometry type during read.");
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now fill out the body, or dynamic part of the physical
|
|
|
|
NxBodyDesc bodyDesc;
|
|
|
|
if (fRecipe.mass != 0)
|
|
|
|
{
|
|
|
|
bodyDesc.mass = fRecipe.mass;
|
|
|
|
actorDesc.body = &bodyDesc;
|
|
|
|
|
|
|
|
if (GetProperty(plSimulationInterface::kPinned))
|
|
|
|
{
|
|
|
|
bodyDesc.flags |= NX_BF_FROZEN;
|
|
|
|
startAsleep = true; // put it to sleep if they are going to be frozen
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fRecipe.group != plSimDefs::kGroupDynamic || GetProperty(plSimulationInterface::kPhysAnim))
|
|
|
|
{
|
|
|
|
SetProperty(plSimulationInterface::kPassive, true);
|
|
|
|
|
|
|
|
// Even though the code for animated physicals and animated activators are the same
|
|
|
|
// keep these code snippets separated for fine tuning. Thanks.
|
|
|
|
if (fRecipe.group == plSimDefs::kGroupDynamic)
|
|
|
|
{
|
|
|
|
// handle the animated physicals.... make kinematic for now.
|
|
|
|
fNumberAnimatedPhysicals++;
|
|
|
|
bodyDesc.flags |= NX_BF_KINEMATIC;
|
|
|
|
startAsleep = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// handle the animated activators....
|
|
|
|
fNumberAnimatedActivators++;
|
|
|
|
bodyDesc.flags |= NX_BF_KINEMATIC;
|
|
|
|
startAsleep = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( GetProperty(plSimulationInterface::kPhysAnim) )
|
|
|
|
SimLog("An animated physical that has no mass: %s", GetKeyName());
|
|
|
|
}
|
|
|
|
|
|
|
|
actorDesc.userData = this;
|
|
|
|
actorDesc.name = GetKeyName();
|
|
|
|
|
|
|
|
// Put the dynamics into actor group 1. The actor groups are only used for
|
|
|
|
// deciding who we get contact reports for.
|
|
|
|
if (fRecipe.group == plSimDefs::kGroupDynamic)
|
|
|
|
actorDesc.group = 1;
|
|
|
|
|
|
|
|
NxScene* scene = plSimulationMgr::GetInstance()->GetScene(fWorldKey);
|
|
|
|
try
|
|
|
|
{
|
|
|
|
fActor = scene->createActor(actorDesc);
|
|
|
|
} catch (...)
|
|
|
|
{
|
|
|
|
hsAssert(false, "Actor creation crashed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
hsAssert(fActor, "Actor creation failed");
|
|
|
|
if (!fActor)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
NxShape* shape = fActor->getShapes()[0];
|
|
|
|
shape->setMaterial(plSimulationMgr::GetInstance()->GetMaterialIdx(scene, fRecipe.friction, fRecipe.restitution));
|
|
|
|
|
|
|
|
// Turn on the trigger flags for any detectors.
|
|
|
|
//
|
|
|
|
// Normally, we'd set these flags on the shape before it's created. However,
|
|
|
|
// in the case where the detector is going to be animated, it'll have a rigid
|
|
|
|
// body too, and that will cause problems at creation. According to Ageia,
|
|
|
|
// a detector shape doesn't actually count as a shape, so the SDK will have
|
|
|
|
// problems trying to calculate an intertial tensor. By letting it be
|
|
|
|
// created as a normal dynamic first, then setting the flags, we work around
|
|
|
|
// that problem.
|
|
|
|
if (fRecipe.group == plSimDefs::kGroupDetector)
|
|
|
|
{
|
|
|
|
shape->setFlag(NX_TRIGGER_ON_ENTER, true);
|
|
|
|
shape->setFlag(NX_TRIGGER_ON_LEAVE, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (GetProperty(plSimulationInterface::kStartInactive) || startAsleep)
|
|
|
|
{
|
|
|
|
if (!fActor->isSleeping())
|
|
|
|
{
|
|
|
|
if (plSimulationMgr::fExtraProfile)
|
|
|
|
SimLog("Deactivating %s in SetPositionAndRotationSim", GetKeyName());
|
|
|
|
fActor->putToSleep();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (GetProperty(plSimulationInterface::kDisable))
|
|
|
|
IEnable(false);
|
|
|
|
if (GetProperty(plSimulationInterface::kSuppressed_DEAD))
|
|
|
|
IEnable(false);
|
|
|
|
|
|
|
|
plNodeRefMsg* refMsg = TRACKED_NEW plNodeRefMsg(fSceneNode, plRefMsg::kOnCreate, -1, plNodeRefMsg::kPhysical);
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(GetKey(), refMsg, plRefFlags::kActiveRef);
|
|
|
|
|
|
|
|
// only dynamic physicals without noSync need SDLs
|
|
|
|
if ( fRecipe.group == plSimDefs::kGroupDynamic && !fProps.IsBitSet(plSimulationInterface::kNoSynchronize) )
|
|
|
|
{
|
|
|
|
// add SDL modifier
|
|
|
|
plSceneObject* sceneObj = plSceneObject::ConvertNoRef(fObjectKey->ObjectIsLoaded());
|
|
|
|
hsAssert(sceneObj, "nil sceneObject, failed to create and attach SDL modifier");
|
|
|
|
|
|
|
|
delete fSDLMod;
|
|
|
|
fSDLMod = TRACKED_NEW plPhysicalSDLModifier;
|
|
|
|
sceneObj->AddModifier(fSDLMod);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// MESSAGE HANDLING
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// MSGRECEIVE
|
|
|
|
hsBool plPXPhysical::MsgReceive( plMessage* msg )
|
|
|
|
{
|
|
|
|
if(plGenRefMsg *refM = plGenRefMsg::ConvertNoRef(msg))
|
|
|
|
{
|
|
|
|
return HandleRefMsg(refM);
|
|
|
|
}
|
|
|
|
else if(plSimulationMsg *simM = plSimulationMsg::ConvertNoRef(msg))
|
|
|
|
{
|
|
|
|
plLinearVelocityMsg* velMsg = plLinearVelocityMsg::ConvertNoRef(msg);
|
|
|
|
if(velMsg)
|
|
|
|
{
|
|
|
|
SetLinearVelocitySim(velMsg->Velocity());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
plAngularVelocityMsg* angvelMsg = plAngularVelocityMsg::ConvertNoRef(msg);
|
|
|
|
if(angvelMsg)
|
|
|
|
{
|
|
|
|
SetAngularVelocitySim(angvelMsg->AngularVelocity());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// couldn't find a local handler: pass to the base
|
|
|
|
else
|
|
|
|
return plPhysical::MsgReceive(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
// IHANDLEREFMSG
|
|
|
|
// there's two things we hold references to: subworlds
|
|
|
|
// and the simulation manager.
|
|
|
|
// right now, we're only worrying about the subworlds
|
|
|
|
hsBool plPXPhysical::HandleRefMsg(plGenRefMsg* refMsg)
|
|
|
|
{
|
|
|
|
UInt8 refCtxt = refMsg->GetContext();
|
|
|
|
|
|
|
|
switch (refMsg->fType) {
|
|
|
|
case kPhysRefWorld: {
|
|
|
|
switch (refCtxt) {
|
|
|
|
case plRefMsg::kOnCreate:
|
|
|
|
case plRefMsg::kOnRequest:
|
|
|
|
// PotS files specify a plHKSubWorld as the subworld key. For everything else,
|
|
|
|
// the subworlds are a plSceneObject key. For sanity purposes, we will allow
|
|
|
|
// references to the plPXSubWorld here. HOWEVER, we will need to grab the target
|
|
|
|
// and replace our reference...
|
|
|
|
if (plPXSubWorld* subWorldIface = plPXSubWorld::ConvertNoRef(refMsg->GetRef())) {
|
|
|
|
hsAssert(subWorldIface->GetOwnerKey(), "subworld owner is NULL?! Uh oh...");
|
|
|
|
plGenRefMsg* replaceRefMsg = new plGenRefMsg(GetKey(), plRefMsg::kOnReplace, 0, kPhysRefWorld);
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(subWorldIface->GetOwnerKey(), replaceRefMsg, plRefFlags::kActiveRef);
|
|
|
|
}
|
|
|
|
// fall-thru is intentional :)
|
|
|
|
case plRefMsg::kOnReplace:
|
|
|
|
// loading into a subworld
|
|
|
|
if (plSceneObject* subSO = plSceneObject::ConvertNoRef(refMsg->GetRef())) {
|
|
|
|
fWorldKey = subSO->GetKey();
|
|
|
|
|
|
|
|
// Cyan produced files will never have plPXSubWorld as this is a H'uru-ism.
|
|
|
|
// Let us make a default one such that we can play with default subworlds at runtime.
|
|
|
|
// HAAAAAAAAAX!!!
|
|
|
|
plPXSubWorld* subWorldIface = plPXSubWorld::ConvertNoRef(subSO->GetGenericInterface(plPXSubWorld::Index()));
|
|
|
|
if (!subWorldIface) {
|
|
|
|
subWorldIface = new plPXSubWorld();
|
|
|
|
|
|
|
|
// This can be simplified if/when incorporating zrax' String Theory library
|
|
|
|
std::string strKeyName = std::string(subSO->GetKeyName()) + "_DefSubWorld";
|
|
|
|
const char *cKeyName = strKeyName.c_str();
|
|
|
|
|
|
|
|
hsgResMgr::ResMgr()->NewKey(cKeyName,
|
|
|
|
subWorldIface, GetKey()->GetUoid().GetLocation());
|
|
|
|
|
|
|
|
plObjRefMsg* subIfaceRef = new plObjRefMsg(subSO->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface);
|
|
|
|
// this will send the reference immediately
|
|
|
|
hsgResMgr::ResMgr()->SendRef(subWorldIface, subIfaceRef, plRefFlags::kActiveRef);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, we can initialize the physical...
|
|
|
|
Init();
|
|
|
|
IGetTransformGlobal(fCachedLocal2World);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case plRefMsg::kOnDestroy: {
|
|
|
|
// our world was deleted out from under us: move to the main world
|
|
|
|
// NOTE: this was not implemented even before the PXSubWorld rewrite.
|
|
|
|
// not going to bother!
|
|
|
|
// hsAssert(0, "Lost world");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case kPhysRefSndGroup: {
|
|
|
|
switch (refCtxt)
|
|
|
|
{
|
|
|
|
case plRefMsg::kOnCreate:
|
|
|
|
case plRefMsg::kOnRequest:
|
|
|
|
fSndGroup = plPhysicalSndGroup::ConvertNoRef( refMsg->GetRef() );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case plRefMsg::kOnDestroy:
|
|
|
|
fSndGroup = nil;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
hsAssert(0, "Unknown ref type, who sent us this?");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::IEnable(hsBool enable)
|
|
|
|
{
|
|
|
|
fProps.SetBit(plSimulationInterface::kDisable, !enable);
|
|
|
|
if (!enable)
|
|
|
|
{
|
|
|
|
fActor->raiseActorFlag(NX_AF_DISABLE_COLLISION);
|
|
|
|
if (fActor->isDynamic())
|
|
|
|
fActor->raiseBodyFlag(NX_BF_FROZEN);
|
|
|
|
else
|
|
|
|
plPXPhysicalControllerCore::RebuildCache();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fActor->clearActorFlag(NX_AF_DISABLE_COLLISION);
|
|
|
|
|
|
|
|
// PHYSX FIXME - after re-enabling a possible detector, we need to check to see if any avatar is already in the PhysX turdy hull detector region
|
|
|
|
plSimulationMgr::GetInstance()->UpdateAvatarInDetector(fWorldKey, this);
|
|
|
|
|
|
|
|
if (fActor->isDynamic())
|
|
|
|
fActor->clearBodyFlag(NX_BF_FROZEN);
|
|
|
|
else
|
|
|
|
plPXPhysicalControllerCore::RebuildCache();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plPhysical& plPXPhysical::SetProperty(int prop, hsBool status)
|
|
|
|
{
|
|
|
|
if (GetProperty(prop) == status)
|
|
|
|
{
|
|
|
|
const char* propName = "(unknown)";
|
|
|
|
switch (prop)
|
|
|
|
{
|
|
|
|
case plSimulationInterface::kDisable: propName = "kDisable"; break;
|
|
|
|
case plSimulationInterface::kPinned: propName = "kPinned"; break;
|
|
|
|
case plSimulationInterface::kPassive: propName = "kPassive"; break;
|
|
|
|
case plSimulationInterface::kPhysAnim: propName = "kPhysAnim"; break;
|
|
|
|
case plSimulationInterface::kStartInactive: propName = "kStartInactive"; break;
|
|
|
|
case plSimulationInterface::kNoSynchronize: propName = "kNoSynchronize"; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* name = "(unknown)";
|
|
|
|
if (GetKey())
|
|
|
|
name = GetKeyName();
|
|
|
|
if (plSimulationMgr::fExtraProfile)
|
|
|
|
plSimulationMgr::Log("Warning: Redundant physical property set (property %s, value %s) on %s", propName, status ? "true" : "false", name);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (prop)
|
|
|
|
{
|
|
|
|
case plSimulationInterface::kDisable:
|
|
|
|
IEnable(!status);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case plSimulationInterface::kPinned:
|
|
|
|
if (fActor->isDynamic())
|
|
|
|
{
|
|
|
|
// if the body is already unpinned and you unpin it again,
|
|
|
|
// you'll wipe out its velocity. hence the check.
|
|
|
|
hsBool current = fActor->readBodyFlag(NX_BF_FROZEN);
|
|
|
|
if (status != current)
|
|
|
|
{
|
|
|
|
if (status)
|
|
|
|
fActor->raiseBodyFlag(NX_BF_FROZEN);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fActor->clearBodyFlag(NX_BF_FROZEN);
|
|
|
|
LogActivate("SetProperty");
|
|
|
|
fActor->wakeUp();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
fProps.SetBit(prop, status);
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
plProfile_Extern(SetTransforms);
|
|
|
|
|
|
|
|
#define kMaxNegativeZPos -2000.f
|
|
|
|
|
|
|
|
bool CompareMatrices(const hsMatrix44 &matA, const hsMatrix44 &matB, float tolerance)
|
|
|
|
{
|
|
|
|
return
|
|
|
|
(fabs(matA.fMap[0][0] - matB.fMap[0][0]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[0][1] - matB.fMap[0][1]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[0][2] - matB.fMap[0][2]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[0][3] - matB.fMap[0][3]) < tolerance) &&
|
|
|
|
|
|
|
|
(fabs(matA.fMap[1][0] - matB.fMap[1][0]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[1][1] - matB.fMap[1][1]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[1][2] - matB.fMap[1][2]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[1][3] - matB.fMap[1][3]) < tolerance) &&
|
|
|
|
|
|
|
|
(fabs(matA.fMap[2][0] - matB.fMap[2][0]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[2][1] - matB.fMap[2][1]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[2][2] - matB.fMap[2][2]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[2][3] - matB.fMap[2][3]) < tolerance) &&
|
|
|
|
|
|
|
|
(fabs(matA.fMap[3][0] - matB.fMap[3][0]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[3][1] - matB.fMap[3][1]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[3][2] - matB.fMap[3][2]) < tolerance) &&
|
|
|
|
(fabs(matA.fMap[3][3] - matB.fMap[3][3]) < tolerance);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called after the simulation has run....sends new positions to the various scene objects
|
|
|
|
// *** want to do this in response to an update message....
|
|
|
|
void plPXPhysical::SendNewLocation(hsBool synchTransform, hsBool isSynchUpdate)
|
|
|
|
{
|
|
|
|
// we only send if:
|
|
|
|
// - the body is active or forceUpdate is on
|
|
|
|
// - the mass is non-zero
|
|
|
|
// - the physical is not passive
|
|
|
|
hsBool bodyActive = !fActor->isSleeping();
|
|
|
|
hsBool dynamic = fActor->isDynamic();
|
|
|
|
|
|
|
|
if ((bodyActive || isSynchUpdate) && dynamic)// && fInitialTransform)
|
|
|
|
{
|
|
|
|
plProfile_Inc(MaySendLocation);
|
|
|
|
|
|
|
|
if (!GetProperty(plSimulationInterface::kPassive))
|
|
|
|
{
|
|
|
|
hsMatrix44 curl2w = fCachedLocal2World;
|
|
|
|
// we're going to cache the transform before sending so we can recognize if it comes back
|
|
|
|
IGetTransformGlobal(fCachedLocal2World);
|
|
|
|
|
|
|
|
if (!CompareMatrices(curl2w, fCachedLocal2World, .0001f))
|
|
|
|
{
|
|
|
|
plProfile_Inc(LocationsSent);
|
|
|
|
plProfile_BeginLap(PhysicsUpdates, GetKeyName());
|
|
|
|
|
|
|
|
// quick peek at the translation...last time it was corrupted because we applied a non-unit quaternion
|
|
|
|
// hsAssert(real_finite(fCachedLocal2World.fMap[0][3]) &&
|
|
|
|
// real_finite(fCachedLocal2World.fMap[1][3]) &&
|
|
|
|
// real_finite(fCachedLocal2World.fMap[2][3]), "Bad transform outgoing");
|
|
|
|
|
|
|
|
if (fCachedLocal2World.GetTranslate().fZ < kMaxNegativeZPos)
|
|
|
|
{
|
|
|
|
SimLog("Physical %s fell to %.1f (%.1f is the max). Suppressing.", GetKeyName(), fCachedLocal2World.GetTranslate().fZ, kMaxNegativeZPos);
|
|
|
|
// Since this has probably been falling for a while, and thus not getting any syncs,
|
|
|
|
// make sure to save it's current pos so we'll know to reset it later
|
|
|
|
DirtySynchState(kSDLPhysical, plSynchedObject::kBCastToClients);
|
|
|
|
IEnable(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
hsMatrix44 w2l;
|
|
|
|
fCachedLocal2World.GetInverse(&w2l);
|
|
|
|
plCorrectionMsg *pCorrMsg = TRACKED_NEW plCorrectionMsg(GetObjectKey(), fCachedLocal2World, w2l, synchTransform);
|
|
|
|
pCorrMsg->Send();
|
|
|
|
if (fProxyGen)
|
|
|
|
fProxyGen->SetTransform(fCachedLocal2World, w2l);
|
|
|
|
plProfile_EndLap(PhysicsUpdates, GetKeyName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::ApplyHitForce()
|
|
|
|
{
|
|
|
|
if (fActor && fWeWereHit)
|
|
|
|
{
|
|
|
|
fActor->addForceAtPos(plPXConvert::Vector(fHitForce), plPXConvert::Point(fHitPos), NX_FORCE);
|
|
|
|
fWeWereHit = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void plPXPhysical::ISetTransformGlobal(const hsMatrix44& l2w)
|
|
|
|
{
|
|
|
|
hsAssert(fActor->isDynamic(), "Shouldn't move a static actor");
|
|
|
|
fActor->wakeUp();
|
|
|
|
|
|
|
|
NxMat34 mat;
|
|
|
|
|
|
|
|
if (fWorldKey)
|
|
|
|
{
|
|
|
|
plSceneObject* so = plSceneObject::ConvertNoRef(fWorldKey->ObjectIsLoaded());
|
|
|
|
hsAssert(so, "Scene object not loaded while accessing subworld.");
|
|
|
|
// physical to subworld (simulation space)
|
|
|
|
hsMatrix44 p2s = so->GetCoordinateInterface()->GetWorldToLocal() * l2w;
|
|
|
|
plPXConvert::Matrix(p2s, mat);
|
|
|
|
if (fProxyGen)
|
|
|
|
{
|
|
|
|
hsMatrix44 w2l;
|
|
|
|
p2s.GetInverse(&w2l);
|
|
|
|
fProxyGen->SetTransform(p2s, w2l);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// No need to localize
|
|
|
|
else
|
|
|
|
{
|
|
|
|
plPXConvert::Matrix(l2w, mat);
|
|
|
|
if (fProxyGen)
|
|
|
|
{
|
|
|
|
hsMatrix44 w2l;
|
|
|
|
l2w.GetInverse(&w2l);
|
|
|
|
fProxyGen->SetTransform(l2w, w2l);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This used to check for the kPhysAnim flag, however animated detectors
|
|
|
|
// are also kinematic but not kPhysAnim, therefore, this would break on PhysX
|
|
|
|
// SDKs (yes, I'm looking at you, 2.6.4) that actually obey the ***GlobalPose
|
|
|
|
// rules set forth in the SDK documentation.
|
|
|
|
if (fActor->readBodyFlag(NX_BF_KINEMATIC))
|
|
|
|
fActor->moveGlobalPose(mat);
|
|
|
|
else
|
|
|
|
fActor->setGlobalPose(mat);
|
|
|
|
}
|
|
|
|
|
|
|
|
// the physical may have several parents between it and the subworld object,
|
|
|
|
// but the *havok* transform is only one level away from the subworld.
|
|
|
|
// to avoid any confusion about this difference, we avoid referring to the
|
|
|
|
// subworld as "parent" and use, for example, "l2s" (local-to-sub) instead
|
|
|
|
// of the canonical plasma "l2p" (local-to-parent)
|
|
|
|
void plPXPhysical::IGetTransformGlobal(hsMatrix44& l2w) const
|
|
|
|
{
|
|
|
|
plPXConvert::Matrix(fActor->getGlobalPose(), l2w);
|
|
|
|
|
|
|
|
if (fWorldKey)
|
|
|
|
{
|
|
|
|
plSceneObject* so = plSceneObject::ConvertNoRef(fWorldKey->ObjectIsLoaded());
|
|
|
|
hsAssert(so, "Scene object not loaded while accessing subworld.");
|
|
|
|
if (so->GetCoordinateInterface())
|
|
|
|
{
|
|
|
|
const hsMatrix44& s2w = so->GetCoordinateInterface()->GetLocalToWorld();
|
|
|
|
l2w = s2w * l2w;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::IGetPositionSim(hsPoint3& pos) const
|
|
|
|
{
|
|
|
|
pos = plPXConvert::Point(fActor->getGlobalPosition());
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::IGetRotationSim(hsQuat& rot) const
|
|
|
|
{
|
|
|
|
rot = plPXConvert::Quat(fActor->getGlobalOrientationQuat());
|
|
|
|
}
|
|
|
|
void plPXPhysical::ISetPositionSim(const hsPoint3& pos)
|
|
|
|
{
|
|
|
|
if (GetProperty(plSimulationInterface::kPhysAnim))
|
|
|
|
fActor->moveGlobalPosition(plPXConvert::Point(pos));
|
|
|
|
else
|
|
|
|
fActor->setGlobalPosition(plPXConvert::Point(pos));
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::ISetRotationSim(const hsQuat& rot)
|
|
|
|
{
|
|
|
|
if (GetProperty(plSimulationInterface::kPhysAnim))
|
|
|
|
fActor->moveGlobalOrientation(plPXConvert::Quat(rot));
|
|
|
|
else
|
|
|
|
fActor->setGlobalOrientation(plPXConvert::Quat(rot));
|
|
|
|
}
|
|
|
|
|
|
|
|
// This form is assumed by convention to be global.
|
|
|
|
void plPXPhysical::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l, hsBool force)
|
|
|
|
{
|
|
|
|
// hsAssert(real_finite(l2w.fMap[0][3]) && real_finite(l2w.fMap[1][3]) && real_finite(l2w.fMap[2][3]), "Bad transform incoming");
|
|
|
|
|
|
|
|
|
|
|
|
// make sure the physical is dynamic.
|
|
|
|
// also make sure there is some difference between the matrices...
|
|
|
|
// ... but not when a subworld... because the subworld maybe animating and if the object is still then it is actually moving within the subworld
|
|
|
|
if (force || (fActor->isDynamic() && (fWorldKey || !CompareMatrices(l2w, fCachedLocal2World, .0001f))) )
|
|
|
|
{
|
|
|
|
ISetTransformGlobal(l2w);
|
|
|
|
plProfile_Inc(SetTransforms);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( !fActor->isDynamic() && plSimulationMgr::fExtraProfile)
|
|
|
|
SimLog("Setting transform on non-dynamic: %s.", GetKeyName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GETTRANSFORM
|
|
|
|
void plPXPhysical::GetTransform(hsMatrix44& l2w, hsMatrix44& w2l)
|
|
|
|
{
|
|
|
|
IGetTransformGlobal(l2w);
|
|
|
|
l2w.GetInverse(&w2l);
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plPXPhysical::GetLinearVelocitySim(hsVector3& vel) const
|
|
|
|
{
|
|
|
|
hsBool result = false;
|
|
|
|
|
|
|
|
if (fActor->isDynamic())
|
|
|
|
{
|
|
|
|
vel = plPXConvert::Vector(fActor->getLinearVelocity());
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
vel.Set(0, 0, 0);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::SetLinearVelocitySim(const hsVector3& vel)
|
|
|
|
{
|
|
|
|
if (fActor->isDynamic())
|
|
|
|
fActor->setLinearVelocity(plPXConvert::Vector(vel));
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::ClearLinearVelocity()
|
|
|
|
{
|
|
|
|
SetLinearVelocitySim(hsVector3(0, 0, 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plPXPhysical::GetAngularVelocitySim(hsVector3& vel) const
|
|
|
|
{
|
|
|
|
hsBool result = false;
|
|
|
|
if (fActor->isDynamic())
|
|
|
|
{
|
|
|
|
vel = plPXConvert::Vector(fActor->getAngularVelocity());
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
vel.Set(0, 0, 0);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::SetAngularVelocitySim(const hsVector3& vel)
|
|
|
|
{
|
|
|
|
if (fActor->isDynamic())
|
|
|
|
fActor->setAngularVelocity(plPXConvert::Vector(vel));
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// NETWORK SYNCHRONIZATION
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
plKey plPXPhysical::GetSceneNode() const
|
|
|
|
{
|
|
|
|
return fSceneNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::SetSceneNode(plKey newNode)
|
|
|
|
{
|
|
|
|
plKey oldNode = GetSceneNode();
|
|
|
|
if (oldNode == newNode)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If we don't do this, we get leaked keys and a crash on exit with certain clones
|
|
|
|
// Note this has nothing do to with the world that the physical is in
|
|
|
|
if (newNode) {
|
|
|
|
plNodeRefMsg* refMsg = new plNodeRefMsg(newNode, plNodeRefMsg::kOnRequest, -1, plNodeRefMsg::kPhysical);
|
|
|
|
hsgResMgr::ResMgr()->SendRef(GetKey(), refMsg, plRefFlags::kActiveRef);
|
|
|
|
}
|
|
|
|
if (oldNode)
|
|
|
|
oldNode->Release(GetKey());
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// READING AND WRITING
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
#include "plPXStream.h"
|
|
|
|
|
|
|
|
void plPXPhysical::Read(hsStream* stream, hsResMgr* mgr)
|
|
|
|
{
|
|
|
|
plPhysical::Read(stream, mgr);
|
|
|
|
ClearMatrix(fCachedLocal2World);
|
|
|
|
|
|
|
|
fRecipe.mass = stream->ReadSwapScalar();
|
|
|
|
fRecipe.friction = stream->ReadSwapScalar();
|
|
|
|
fRecipe.restitution = stream->ReadSwapScalar();
|
|
|
|
fRecipe.bounds = (plSimDefs::Bounds)stream->ReadByte();
|
|
|
|
fRecipe.group = (plSimDefs::Group)stream->ReadByte();
|
|
|
|
fRecipe.reportsOn = stream->ReadSwap32();
|
|
|
|
fLOSDBs = stream->ReadSwap16();
|
|
|
|
//hack for swim regions currently they are labeled as static av blockers
|
|
|
|
if(fLOSDBs==plSimDefs::kLOSDBSwimRegion)
|
|
|
|
{
|
|
|
|
fRecipe.group=plSimDefs::kGroupMax;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
fRecipe.objectKey = mgr->ReadKey(stream);
|
|
|
|
fRecipe.sceneNode = mgr->ReadKey(stream);
|
|
|
|
fRecipe.worldKey = mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kPhysRefWorld), plRefFlags::kActiveRef);
|
|
|
|
|
|
|
|
mgr->ReadKeyNotifyMe(stream, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kPhysRefSndGroup), plRefFlags::kActiveRef);
|
|
|
|
|
|
|
|
hsPoint3 pos;
|
|
|
|
hsQuat rot;
|
|
|
|
pos.Read(stream);
|
|
|
|
rot.Read(stream);
|
|
|
|
rot.MakeMatrix(&fRecipe.l2s);
|
|
|
|
fRecipe.l2s.SetTranslate(&pos);
|
|
|
|
|
|
|
|
fProps.Read(stream);
|
|
|
|
|
|
|
|
if (fRecipe.bounds == plSimDefs::kSphereBounds)
|
|
|
|
{
|
|
|
|
fRecipe.radius = stream->ReadSwapScalar();
|
|
|
|
fRecipe.offset.Read(stream);
|
|
|
|
}
|
|
|
|
else if (fRecipe.bounds == plSimDefs::kBoxBounds)
|
|
|
|
{
|
|
|
|
fRecipe.bDimensions.Read(stream);
|
|
|
|
fRecipe.bOffset.Read(stream);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (fRecipe.bounds == plSimDefs::kHullBounds)
|
|
|
|
fRecipe.convexMesh = IReadHull(stream);
|
|
|
|
else
|
|
|
|
fRecipe.triMesh = IReadTriMesh(stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we do not have a world, specified, we go ahead and init into the main world...
|
|
|
|
// This will been done in MsgReceive otherwise
|
|
|
|
if (!fRecipe.worldKey)
|
|
|
|
Init();
|
|
|
|
|
|
|
|
hsAssert(!fProxyGen, "Already have proxy gen, double read?");
|
|
|
|
|
|
|
|
hsColorRGBA physColor;
|
|
|
|
hsScalar opac = 1.0f;
|
|
|
|
|
|
|
|
if (fGroup == plSimDefs::kGroupAvatar)
|
|
|
|
{
|
|
|
|
// local avatar is light purple and transparent
|
|
|
|
physColor.Set(.2f, .1f, .2f, 1.f);
|
|
|
|
opac = 0.4f;
|
|
|
|
}
|
|
|
|
else if (fGroup == plSimDefs::kGroupDynamic)
|
|
|
|
{
|
|
|
|
// Dynamics are red
|
|
|
|
physColor.Set(1.f,0.f,0.f,1.f);
|
|
|
|
}
|
|
|
|
else if (fGroup == plSimDefs::kGroupDetector)
|
|
|
|
{
|
|
|
|
if(!fWorldKey)
|
|
|
|
{
|
|
|
|
// Detectors are blue, and transparent
|
|
|
|
physColor.Set(0.f,0.f,1.f,1.f);
|
|
|
|
opac = 0.3f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// subworld Detectors are green
|
|
|
|
physColor.Set(0.f,1.f,0.f,1.f);
|
|
|
|
opac = 0.3f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (fGroup == plSimDefs::kGroupStatic)
|
|
|
|
{
|
|
|
|
if (GetProperty(plSimulationInterface::kPhysAnim))
|
|
|
|
// Statics that are animated are more reddish?
|
|
|
|
physColor.Set(1.f,0.6f,0.2f,1.f);
|
|
|
|
else
|
|
|
|
// Statics are yellow
|
|
|
|
physColor.Set(1.f,0.8f,0.2f,1.f);
|
|
|
|
// if in a subworld... slightly transparent
|
|
|
|
if(fRecipe.worldKey)
|
|
|
|
opac = 0.6f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// don't knows are grey
|
|
|
|
physColor.Set(0.6f,0.6f,0.6f,1.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
fProxyGen = TRACKED_NEW plPhysicalProxy(hsColorRGBA().Set(0,0,0,1.f), physColor, opac);
|
|
|
|
fProxyGen->Init(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool IIsNxStream(hsStream* s)
|
|
|
|
{
|
|
|
|
char tag[4];
|
|
|
|
s->Read(sizeof(tag), tag);
|
|
|
|
|
|
|
|
// PhysX streams begin with the magic string "NXS\x01"
|
|
|
|
// Our hacked streams begin with the magic string "HSP\x01"
|
|
|
|
if (memcmp("HSP\x01", tag, sizeof(tag)) == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// We're not going to compare to see if it says NXS. We will let the PhysX SDK
|
|
|
|
// worry about garbage data. Just rewind the stream back so PhysX can deal with it.
|
|
|
|
s->SetPosition(s->GetPosition() - sizeof(tag));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
NxConvexMesh* plPXPhysical::IReadHull(hsStream* s)
|
|
|
|
{
|
|
|
|
if (IIsNxStream(s))
|
|
|
|
{
|
|
|
|
plPXStream pxs(s);
|
|
|
|
return plSimulationMgr::GetInstance()->GetSDK()->createConvexMesh(pxs);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::vector<hsPoint3> verts;
|
|
|
|
verts.resize(s->ReadSwap32());
|
|
|
|
for (size_t i = 0; i < verts.size(); ++i)
|
|
|
|
verts[i].Read(s);
|
|
|
|
|
|
|
|
// Unfortunately, the only way I know of to accomplish this is to cook to a RAM stream,
|
|
|
|
// then have PhysX read the cooked data from the RAM stream. Yes, this is very sad.
|
|
|
|
// I blame PhysX. It needs to die in a fiaaaaaaaaaaah
|
|
|
|
hsRAMStream ram;
|
|
|
|
plPXStream pxs(&ram);
|
|
|
|
|
|
|
|
NxConvexMeshDesc desc;
|
|
|
|
desc.numVertices = verts.size();
|
|
|
|
desc.pointStrideBytes = sizeof(hsPoint3);
|
|
|
|
desc.points = &verts[0];
|
|
|
|
desc.flags = NX_CF_COMPUTE_CONVEX | NX_CF_USE_UNCOMPRESSED_NORMALS;
|
|
|
|
if (!NxCookConvexMesh(desc, pxs))
|
|
|
|
{
|
|
|
|
SimLog("Failed to cook hull for '%s'", GetKeyName());
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ram.Rewind();
|
|
|
|
return plSimulationMgr::GetInstance()->GetSDK()->createConvexMesh(pxs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NxTriangleMesh* plPXPhysical::IReadTriMesh(hsStream* s)
|
|
|
|
{
|
|
|
|
if (IIsNxStream(s))
|
|
|
|
{
|
|
|
|
plPXStream pxs(s);
|
|
|
|
return plSimulationMgr::GetInstance()->GetSDK()->createTriangleMesh(pxs);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::vector<hsPoint3> verts;
|
|
|
|
verts.resize(s->ReadSwap32());
|
|
|
|
for (size_t i = 0; i < verts.size(); ++i)
|
|
|
|
verts[i].Read(s);
|
|
|
|
std::vector<UInt32> indices;
|
|
|
|
UInt32 numTriangles = s->ReadSwap32();
|
|
|
|
indices.resize(numTriangles * 3);
|
|
|
|
for (size_t i = 0; i < indices.size(); ++i)
|
|
|
|
indices[i] = s->ReadSwap32();
|
|
|
|
|
|
|
|
// Unfortunately, the only way I know of to accomplish this is to cook to a RAM stream,
|
|
|
|
// then have PhysX read the cooked data from the RAM stream. Yes, this is very sad.
|
|
|
|
// I blame PhysX. It needs to die in a fiaaaaaaaaaaah
|
|
|
|
hsRAMStream ram;
|
|
|
|
plPXStream pxs(&ram);
|
|
|
|
|
|
|
|
NxTriangleMeshDesc desc;
|
|
|
|
desc.numVertices = verts.size();
|
|
|
|
desc.pointStrideBytes = sizeof(hsPoint3);
|
|
|
|
desc.points = &verts[0];
|
|
|
|
desc.numTriangles = numTriangles;
|
|
|
|
desc.triangleStrideBytes = sizeof(UInt32) * 3;
|
|
|
|
desc.triangles = &indices[0];
|
|
|
|
desc.flags = 0;
|
|
|
|
if (!NxCookTriangleMesh(desc, pxs))
|
|
|
|
{
|
|
|
|
SimLog("Failed to cook trimesh for '%s'", GetKeyName());
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ram.Rewind();
|
|
|
|
return plSimulationMgr::GetInstance()->GetSDK()->createTriangleMesh(pxs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::Write(hsStream* stream, hsResMgr* mgr)
|
|
|
|
{
|
|
|
|
plPhysical::Write(stream, mgr);
|
|
|
|
|
|
|
|
hsAssert(fActor, "nil actor");
|
|
|
|
hsAssert(fActor->getNbShapes() == 1, "Can only write actors with one shape. Writing first only.");
|
|
|
|
NxShape* shape = fActor->getShapes()[0];
|
|
|
|
|
|
|
|
NxMaterialIndex matIdx = shape->getMaterial();
|
|
|
|
NxScene* scene = plSimulationMgr::GetInstance()->GetScene(fWorldKey);
|
|
|
|
NxMaterial* mat = scene->getMaterialFromIndex(matIdx);
|
|
|
|
float friction = mat->getStaticFriction();
|
|
|
|
float restitution = mat->getRestitution();
|
|
|
|
|
|
|
|
stream->WriteSwapScalar(fActor->getMass());
|
|
|
|
stream->WriteSwapScalar(friction);
|
|
|
|
stream->WriteSwapScalar(restitution);
|
|
|
|
stream->WriteByte(fRecipe.bounds);
|
|
|
|
stream->WriteByte(fGroup);
|
|
|
|
stream->WriteSwap32(fReportsOn);
|
|
|
|
stream->WriteSwap16(fLOSDBs);
|
|
|
|
mgr->WriteKey(stream, fObjectKey);
|
|
|
|
mgr->WriteKey(stream, fSceneNode);
|
|
|
|
mgr->WriteKey(stream, fWorldKey);
|
|
|
|
mgr->WriteKey(stream, fSndGroup);
|
|
|
|
|
|
|
|
hsPoint3 pos;
|
|
|
|
hsQuat rot;
|
|
|
|
IGetPositionSim(pos);
|
|
|
|
IGetRotationSim(rot);
|
|
|
|
pos.Write(stream);
|
|
|
|
rot.Write(stream);
|
|
|
|
|
|
|
|
fProps.Write(stream);
|
|
|
|
|
|
|
|
if (fRecipe.bounds == plSimDefs::kSphereBounds)
|
|
|
|
{
|
|
|
|
const NxSphereShape* sphereShape = shape->isSphere();
|
|
|
|
stream->WriteSwapScalar(sphereShape->getRadius());
|
|
|
|
hsPoint3 localPos = plPXConvert::Point(sphereShape->getLocalPosition());
|
|
|
|
localPos.Write(stream);
|
|
|
|
}
|
|
|
|
else if (fRecipe.bounds == plSimDefs::kBoxBounds)
|
|
|
|
{
|
|
|
|
const NxBoxShape* boxShape = shape->isBox();
|
|
|
|
hsPoint3 dim = plPXConvert::Point(boxShape->getDimensions());
|
|
|
|
dim.Write(stream);
|
|
|
|
hsPoint3 localPos = plPXConvert::Point(boxShape->getLocalPosition());
|
|
|
|
localPos.Write(stream);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (fRecipe.bounds == plSimDefs::kHullBounds)
|
|
|
|
hsAssert(shape->isConvexMesh(), "Hull shape isn't a convex mesh");
|
|
|
|
else
|
|
|
|
hsAssert(shape->isTriangleMesh(), "Exact shape isn't a trimesh");
|
|
|
|
|
|
|
|
// We hide the stream we used to create this mesh away in the shape user data.
|
|
|
|
// Pull it out and write it to disk.
|
|
|
|
hsVectorStream* vecStream = (hsVectorStream*)shape->userData;
|
|
|
|
stream->Write(vecStream->GetEOF(), vecStream->GetData());
|
|
|
|
delete vecStream;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// TESTING SDL
|
|
|
|
// Send phys sendState msg to object's plPhysicalSDLModifier
|
|
|
|
//
|
|
|
|
hsBool plPXPhysical::DirtySynchState(const char* SDLStateName, UInt32 synchFlags )
|
|
|
|
{
|
|
|
|
if (GetObjectKey())
|
|
|
|
{
|
|
|
|
plSynchedObject* so=plSynchedObject::ConvertNoRef(GetObjectKey()->ObjectIsLoaded());
|
|
|
|
if (so)
|
|
|
|
{
|
|
|
|
fLastSyncTime = hsTimer::GetSysSeconds();
|
|
|
|
return so->DirtySynchState(SDLStateName, synchFlags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::GetSyncState(hsPoint3& pos, hsQuat& rot, hsVector3& linV, hsVector3& angV)
|
|
|
|
{
|
|
|
|
IGetPositionSim(pos);
|
|
|
|
IGetRotationSim(rot);
|
|
|
|
GetLinearVelocitySim(linV);
|
|
|
|
GetAngularVelocitySim(angV);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::SetSyncState(hsPoint3* pos, hsQuat* rot, hsVector3* linV, hsVector3* angV)
|
|
|
|
{
|
|
|
|
bool initialSync = plNetClientApp::GetInstance()->IsLoadingInitialAgeState() &&
|
|
|
|
plNetClientApp::GetInstance()->GetJoinOrder() == 0;
|
|
|
|
|
|
|
|
// If the physical has fallen out of the sim, and this is initial age state, and we're
|
|
|
|
// the first person in, reset it to the original position. (ie, prop the default state
|
|
|
|
// we've got right now)
|
|
|
|
if (pos && pos->fZ < kMaxNegativeZPos && initialSync)
|
|
|
|
{
|
|
|
|
SimLog("Physical %s loaded out of range state. Forcing initial state to server.", GetKeyName());
|
|
|
|
DirtySynchState(kSDLPhysical, plSynchedObject::kBCastToClients);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pos)
|
|
|
|
ISetPositionSim(*pos);
|
|
|
|
if (rot)
|
|
|
|
ISetRotationSim(*rot);
|
|
|
|
|
|
|
|
if (linV)
|
|
|
|
SetLinearVelocitySim(*linV);
|
|
|
|
if (angV)
|
|
|
|
SetAngularVelocitySim(*angV);
|
|
|
|
|
|
|
|
SendNewLocation(false, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plPXPhysical::ExcludeRegionHack(hsBool cleared)
|
|
|
|
{
|
|
|
|
NxShape* shape = fActor->getShapes()[0];
|
|
|
|
shape->setFlag(NX_TRIGGER_ON_ENTER, !cleared);
|
|
|
|
shape->setFlag(NX_TRIGGER_ON_LEAVE, !cleared);
|
|
|
|
fGroup = cleared ? plSimDefs::kGroupExcludeRegion : plSimDefs::kGroupDetector;
|
|
|
|
shape->setGroup(fGroup);
|
|
|
|
/*if switching a static need to inform the controller that it needs to rebuild
|
|
|
|
the collision cache otherwise will still think that the detector is still static or that
|
|
|
|
the static is still a detector*/
|
|
|
|
plPXPhysicalControllerCore::RebuildCache();
|
|
|
|
|
|
|
|
}
|
|
|
|
hsBool plPXPhysical::OverlapWithCapsule(NxCapsule& cap)
|
|
|
|
{
|
|
|
|
NxShape* shape = fActor->getShapes()[0];
|
|
|
|
return shape->checkOverlapCapsule(cap);
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plPXPhysical::IsDynamic() const
|
|
|
|
{
|
|
|
|
return fGroup == plSimDefs::kGroupDynamic &&
|
|
|
|
!GetProperty(plSimulationInterface::kPhysAnim);
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "../plSurface/hsGMaterial.h"
|
|
|
|
#include "../plSurface/plLayerInterface.h"
|
|
|
|
|
|
|
|
// Some helper functions for pulling info out of a PhysX trimesh description
|
|
|
|
inline hsPoint3& GetTrimeshVert(NxTriangleMeshDesc& desc, int idx)
|
|
|
|
{
|
|
|
|
return *((hsPoint3*)(((char*)desc.points)+desc.pointStrideBytes*idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
void GetTrimeshTri(NxTriangleMeshDesc& desc, int idx, UInt16* out)
|
|
|
|
{
|
|
|
|
if (hsCheckBits(desc.flags, NX_MF_16_BIT_INDICES))
|
|
|
|
{
|
|
|
|
UInt16* descTris = ((UInt16*)(((char*)desc.triangles)+desc.pointStrideBytes*idx));
|
|
|
|
out[0] = descTris[0];
|
|
|
|
out[1] = descTris[1];
|
|
|
|
out[2] = descTris[2];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
UInt32* descTris = ((UInt32*)(((char*)desc.triangles)+desc.pointStrideBytes*idx));
|
|
|
|
out[0] = (UInt16)descTris[0];
|
|
|
|
out[1] = (UInt16)descTris[1];
|
|
|
|
out[2] = (UInt16)descTris[2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Some helper functions for pulling info out of a PhysX trimesh description
|
|
|
|
inline hsPoint3& GetConvexVert(NxConvexMeshDesc& desc, int idx)
|
|
|
|
{
|
|
|
|
return *((hsPoint3*)(((char*)desc.points)+desc.pointStrideBytes*idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
void GetConvexTri(NxConvexMeshDesc& desc, int idx, UInt16* out)
|
|
|
|
{
|
|
|
|
if (hsCheckBits(desc.flags, NX_MF_16_BIT_INDICES))
|
|
|
|
{
|
|
|
|
UInt16* descTris = ((UInt16*)(((char*)desc.triangles)+desc.pointStrideBytes*idx));
|
|
|
|
out[0] = descTris[0];
|
|
|
|
out[1] = descTris[1];
|
|
|
|
out[2] = descTris[2];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
UInt32* descTris = ((UInt32*)(((char*)desc.triangles)+desc.pointStrideBytes*idx));
|
|
|
|
out[0] = (UInt16)descTris[0];
|
|
|
|
out[1] = (UInt16)descTris[1];
|
|
|
|
out[2] = (UInt16)descTris[2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a visible object that can be viewed by users for debugging purposes.
|
|
|
|
plDrawableSpans* plPXPhysical::CreateProxy(hsGMaterial* mat, hsTArray<UInt32>& idx, plDrawableSpans* addTo)
|
|
|
|
{
|
|
|
|
plDrawableSpans* myDraw = addTo;
|
|
|
|
hsMatrix44 l2w, unused;
|
|
|
|
GetTransform(l2w, unused);
|
|
|
|
|
|
|
|
hsBool blended = ((mat->GetLayer(0)->GetBlendFlags() & hsGMatState::kBlendMask));
|
|
|
|
|
|
|
|
NxShape* shape = fActor->getShapes()[0];
|
|
|
|
|
|
|
|
NxTriangleMeshShape* trimeshShape = shape->isTriangleMesh();
|
|
|
|
if (trimeshShape)
|
|
|
|
{
|
|
|
|
NxTriangleMeshDesc desc;
|
|
|
|
trimeshShape->getTriangleMesh().saveToDesc(desc);
|
|
|
|
|
|
|
|
hsTArray<hsPoint3> pos;
|
|
|
|
hsTArray<UInt16> tris;
|
|
|
|
|
|
|
|
const int kMaxTris = 10000;
|
|
|
|
const int kMaxVerts = 32000;
|
|
|
|
if ((desc.numVertices < kMaxVerts) && (desc.numTriangles < kMaxTris))
|
|
|
|
{
|
|
|
|
pos.SetCount(desc.numVertices);
|
|
|
|
tris.SetCount(desc.numTriangles * 3);
|
|
|
|
|
|
|
|
for (int i = 0; i < desc.numVertices; i++ )
|
|
|
|
pos[i] = GetTrimeshVert(desc, i);
|
|
|
|
|
|
|
|
for (int i = 0; i < desc.numTriangles; i++)
|
|
|
|
GetTrimeshTri(desc, i, &tris[i*3]);
|
|
|
|
|
|
|
|
myDraw = plDrawableGenerator::GenerateDrawable(pos.GetCount(),
|
|
|
|
pos.AcquireArray(),
|
|
|
|
nil, // normals - def to avg (smooth) norm
|
|
|
|
nil, // uvws
|
|
|
|
0, // uvws per vertex
|
|
|
|
nil, // colors - def to white
|
|
|
|
true, // do a quick fake shade
|
|
|
|
nil, // optional color modulation
|
|
|
|
tris.GetCount(),
|
|
|
|
tris.AcquireArray(),
|
|
|
|
mat,
|
|
|
|
l2w,
|
|
|
|
blended,
|
|
|
|
&idx,
|
|
|
|
myDraw);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int curTri = 0;
|
|
|
|
int trisToDo = desc.numTriangles;
|
|
|
|
while (trisToDo > 0)
|
|
|
|
{
|
|
|
|
int trisThisRound = trisToDo > kMaxTris ? kMaxTris : trisToDo;
|
|
|
|
|
|
|
|
trisToDo -= trisThisRound;
|
|
|
|
|
|
|
|
pos.SetCount(trisThisRound * 3);
|
|
|
|
tris.SetCount(trisThisRound * 3);
|
|
|
|
|
|
|
|
for (int i = 0; i < trisThisRound; i++)
|
|
|
|
{
|
|
|
|
GetTrimeshTri(desc, curTri, &tris[i*3]);
|
|
|
|
pos[i*3 + 0] = GetTrimeshVert(desc, tris[i*3+0]);
|
|
|
|
pos[i*3 + 1] = GetTrimeshVert(desc, tris[i*3+1]);
|
|
|
|
pos[i*3 + 2] = GetTrimeshVert(desc, tris[i*3+2]);
|
|
|
|
|
|
|
|
curTri++;
|
|
|
|
}
|
|
|
|
myDraw = plDrawableGenerator::GenerateDrawable(pos.GetCount(),
|
|
|
|
pos.AcquireArray(),
|
|
|
|
nil, // normals - def to avg (smooth) norm
|
|
|
|
nil, // uvws
|
|
|
|
0, // uvws per vertex
|
|
|
|
nil, // colors - def to white
|
|
|
|
true, // do a quick fake shade
|
|
|
|
nil, // optional color modulation
|
|
|
|
tris.GetCount(),
|
|
|
|
tris.AcquireArray(),
|
|
|
|
mat,
|
|
|
|
l2w,
|
|
|
|
blended,
|
|
|
|
&idx,
|
|
|
|
myDraw);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NxConvexShape* convexShape = shape->isConvexMesh();
|
|
|
|
if (convexShape)
|
|
|
|
{
|
|
|
|
NxConvexMeshDesc desc;
|
|
|
|
convexShape->getConvexMesh().saveToDesc(desc);
|
|
|
|
|
|
|
|
hsTArray<hsPoint3> pos;
|
|
|
|
hsTArray<UInt16> tris;
|
|
|
|
|
|
|
|
pos.SetCount(desc.numVertices);
|
|
|
|
tris.SetCount(desc.numTriangles * 3);
|
|
|
|
|
|
|
|
for (int i = 0; i < desc.numVertices; i++ )
|
|
|
|
pos[i] = GetConvexVert(desc, i);
|
|
|
|
|
|
|
|
for (int i = 0; i < desc.numTriangles; i++)
|
|
|
|
GetConvexTri(desc, i, &tris[i*3]);
|
|
|
|
|
|
|
|
myDraw = plDrawableGenerator::GenerateDrawable(pos.GetCount(),
|
|
|
|
pos.AcquireArray(),
|
|
|
|
nil, // normals - def to avg (smooth) norm
|
|
|
|
nil, // uvws
|
|
|
|
0, // uvws per vertex
|
|
|
|
nil, // colors - def to white
|
|
|
|
true, // do a quick fake shade
|
|
|
|
nil, // optional color modulation
|
|
|
|
tris.GetCount(),
|
|
|
|
tris.AcquireArray(),
|
|
|
|
mat,
|
|
|
|
l2w,
|
|
|
|
blended,
|
|
|
|
&idx,
|
|
|
|
myDraw);
|
|
|
|
}
|
|
|
|
|
|
|
|
NxSphereShape* sphere = shape->isSphere();
|
|
|
|
if (sphere)
|
|
|
|
{
|
|
|
|
float radius = sphere->getRadius();
|
|
|
|
hsPoint3 offset = plPXConvert::Point(sphere->getLocalPosition());
|
|
|
|
myDraw = plDrawableGenerator::GenerateSphericalDrawable(offset, radius,
|
|
|
|
mat, l2w, blended,
|
|
|
|
nil, &idx, myDraw);
|
|
|
|
}
|
|
|
|
|
|
|
|
NxBoxShape* box = shape->isBox();
|
|
|
|
if (box)
|
|
|
|
{
|
|
|
|
hsPoint3 dim = plPXConvert::Point(box->getDimensions());
|
|
|
|
myDraw = plDrawableGenerator::GenerateBoxDrawable(dim.fX*2.f, dim.fY*2.f, dim.fZ*2.f,
|
|
|
|
mat,l2w,blended,
|
|
|
|
nil,&idx,myDraw);
|
|
|
|
}
|
|
|
|
return myDraw;
|
|
|
|
}
|