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.
1567 lines
44 KiB
1567 lines
44 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 "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/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 "../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); |
|
|
|
PhysRecipe::PhysRecipe() |
|
: mass(0.f) |
|
, friction(0.f) |
|
, restitution(0.f) |
|
, bounds(plSimDefs::kBoundsMax) |
|
, group(plSimDefs::kGroupMax) |
|
, reportsOn(0) |
|
, objectKey(nil) |
|
, sceneNode(nil) |
|
, worldKey(nil) |
|
, convexMesh(nil) |
|
, triMesh(nil) |
|
, radius(0.f) |
|
, offset(0.f, 0.f, 0.f) |
|
, meshStream(nil) |
|
{ |
|
l2s.Reset(); |
|
} |
|
|
|
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) |
|
, fBoundsType(plSimDefs::kBoundsMax) |
|
, fLOSDBs(plSimDefs::kLOSDBNone) |
|
, fGroup(plSimDefs::kGroupMax) |
|
, fReportsOn(0) |
|
, fLastSyncTime(0.0f) |
|
, fProxyGen(nil) |
|
, fSceneNode(nil) |
|
, fWorldKey(nil) |
|
, fSndGroup(nil) |
|
, fWorldHull(nil) |
|
, fSaveTriangles(nil) |
|
, fHullNumberPlanes(0) |
|
, fMass(0.f) |
|
, 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(PhysRecipe& recipe) |
|
{ |
|
hsBool startAsleep = false; |
|
fBoundsType = recipe.bounds; |
|
fGroup = recipe.group; |
|
fReportsOn = recipe.reportsOn; |
|
fObjectKey = recipe.objectKey; |
|
fSceneNode = recipe.sceneNode; |
|
fWorldKey = recipe.worldKey; |
|
|
|
NxActorDesc actorDesc; |
|
NxSphereShapeDesc sphereDesc; |
|
NxConvexShapeDesc convexShapeDesc; |
|
NxTriangleMeshShapeDesc trimeshShapeDesc; |
|
NxBoxShapeDesc boxDesc; |
|
|
|
plPXConvert::Matrix(recipe.l2s, actorDesc.globalPose); |
|
|
|
switch (fBoundsType) |
|
{ |
|
case plSimDefs::kSphereBounds: |
|
{ |
|
hsMatrix44 sphereL2W; |
|
sphereL2W.Reset(); |
|
sphereL2W.SetTranslate(&recipe.offset); |
|
|
|
sphereDesc.radius = recipe.radius; |
|
plPXConvert::Matrix(sphereL2W, sphereDesc.localPose); |
|
sphereDesc.group = fGroup; |
|
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 (fGroup == plSimDefs::kGroupDetector && recipe.meshStream == nil) |
|
{ |
|
#ifdef USE_BOXES_FOR_DETECTOR_HULLS |
|
MakeBoxFromHull(recipe.convexMesh, boxDesc); |
|
plSimulationMgr::GetInstance()->GetSDK()->releaseConvexMesh(*recipe.convexMesh); |
|
boxDesc.group = fGroup; |
|
actorDesc.shapes.push_back(&boxDesc); |
|
#else |
|
#ifdef USE_PHYSX_CONVEXHULL_WORKAROUND |
|
// make a hull of planes for testing IsInside |
|
IMakeHull(recipe.convexMesh,recipe.l2s); |
|
#endif // USE_PHYSX_CONVEXHULL_WORKAROUND |
|
convexShapeDesc.meshData = recipe.convexMesh; |
|
convexShapeDesc.userData = recipe.meshStream; |
|
convexShapeDesc.group = fGroup; |
|
actorDesc.shapes.pushBack(&convexShapeDesc); |
|
#endif // USE_BOXES_FOR_DETECTOR_HULLS |
|
} |
|
else |
|
{ |
|
convexShapeDesc.meshData = recipe.convexMesh; |
|
convexShapeDesc.userData = recipe.meshStream; |
|
convexShapeDesc.group = fGroup; |
|
actorDesc.shapes.pushBack(&convexShapeDesc); |
|
} |
|
break; |
|
case plSimDefs::kBoxBounds: |
|
{ |
|
boxDesc.dimensions = plPXConvert::Point(recipe.bDimensions); |
|
|
|
hsMatrix44 boxL2W; |
|
boxL2W.Reset(); |
|
boxL2W.SetTranslate(&recipe.bOffset); |
|
plPXConvert::Matrix(boxL2W, boxDesc.localPose); |
|
|
|
boxDesc.group = fGroup; |
|
actorDesc.shapes.push_back(&boxDesc); |
|
} |
|
break; |
|
case plSimDefs::kExplicitBounds: |
|
case plSimDefs::kProxyBounds: |
|
if (fGroup == plSimDefs::kGroupDetector) |
|
{ |
|
SimLog("Someone using an Exact on a detector region: %s", GetKeyName()); |
|
} |
|
trimeshShapeDesc.meshData = recipe.triMesh; |
|
trimeshShapeDesc.userData = recipe.meshStream; |
|
trimeshShapeDesc.group = fGroup; |
|
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; |
|
fMass = recipe.mass; |
|
if (recipe.mass != 0) |
|
{ |
|
bodyDesc.mass = recipe.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 (fGroup != 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 (fGroup == 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 (fGroup == 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, recipe.friction, recipe.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 (fGroup == 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); |
|
|
|
if (fWorldKey) |
|
{ |
|
plGenRefMsg* ref = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kPhysRefWorld); |
|
hsgResMgr::ResMgr()->AddViaNotify(fWorldKey, ref, plRefFlags::kActiveRef); |
|
} |
|
|
|
// only dynamic physicals without noSync need SDLs |
|
if ( fGroup == 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(); |
|
plKey refKey = refMsg->GetRef()->GetKey(); |
|
plKey ourKey = GetKey(); |
|
PhysRefType refType = PhysRefType(refMsg->fType); |
|
|
|
const char* refKeyName = refKey ? refKey->GetName() : "MISSING"; |
|
|
|
if (refType == kPhysRefWorld) |
|
{ |
|
if (refCtxt == plRefMsg::kOnCreate || refCtxt == plRefMsg::kOnRequest) |
|
{ |
|
// Cache the initial transform, since we assume the sceneobject already knows |
|
// that and doesn't need to be told again |
|
IGetTransformGlobal(fCachedLocal2World); |
|
} |
|
if (refCtxt == plRefMsg::kOnDestroy) |
|
{ |
|
// our world was deleted out from under us: move to the main world |
|
// hsAssert(0, "Lost world"); |
|
} |
|
} |
|
else if (refType == kPhysRefSndGroup) |
|
{ |
|
switch (refCtxt) |
|
{ |
|
case plRefMsg::kOnCreate: |
|
case plRefMsg::kOnRequest: |
|
fSndGroup = plPhysicalSndGroup::ConvertNoRef( refMsg->GetRef() ); |
|
break; |
|
|
|
case plRefMsg::kOnDestroy: |
|
fSndGroup = nil; |
|
break; |
|
} |
|
} |
|
else |
|
{ |
|
hsAssert(0, "Unknown ref type, who sent us this?"); |
|
} |
|
|
|
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."); |
|
// We'll hit this at export time, when the ci isn't ready yet, so do a check |
|
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); |
|
|
|
PhysRecipe recipe; |
|
recipe.mass = stream->ReadSwapScalar(); |
|
recipe.friction = stream->ReadSwapScalar(); |
|
recipe.restitution = stream->ReadSwapScalar(); |
|
recipe.bounds = (plSimDefs::Bounds)stream->ReadByte(); |
|
recipe.group = (plSimDefs::Group)stream->ReadByte(); |
|
recipe.reportsOn = stream->ReadSwap32(); |
|
fLOSDBs = stream->ReadSwap16(); |
|
//hack for swim regions currently they are labeled as static av blockers |
|
if(fLOSDBs==plSimDefs::kLOSDBSwimRegion) |
|
{ |
|
recipe.group=plSimDefs::kGroupMax; |
|
} |
|
// |
|
recipe.objectKey = mgr->ReadKey(stream); |
|
recipe.sceneNode = mgr->ReadKey(stream); |
|
recipe.worldKey = mgr->ReadKey(stream); |
|
|
|
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(&recipe.l2s); |
|
recipe.l2s.SetTranslate(&pos); |
|
|
|
fProps.Read(stream); |
|
|
|
if (recipe.bounds == plSimDefs::kSphereBounds) |
|
{ |
|
recipe.radius = stream->ReadSwapScalar(); |
|
recipe.offset.Read(stream); |
|
} |
|
else if (recipe.bounds == plSimDefs::kBoxBounds) |
|
{ |
|
recipe.bDimensions.Read(stream); |
|
recipe.bOffset.Read(stream); |
|
} |
|
else |
|
{ |
|
if (recipe.bounds == plSimDefs::kHullBounds) |
|
recipe.convexMesh = IReadHull(stream); |
|
else |
|
recipe.triMesh = IReadTriMesh(stream); |
|
} |
|
|
|
Init(recipe); |
|
|
|
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(fWorldKey) |
|
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(fBoundsType); |
|
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 (fBoundsType == plSimDefs::kSphereBounds) |
|
{ |
|
const NxSphereShape* sphereShape = shape->isSphere(); |
|
stream->WriteSwapScalar(sphereShape->getRadius()); |
|
hsPoint3 localPos = plPXConvert::Point(sphereShape->getLocalPosition()); |
|
localPos.Write(stream); |
|
} |
|
else if (fBoundsType == 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 (fBoundsType == 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; |
|
}
|
|
|