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.
 
 
 
 
 

1452 lines
47 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "plPXPhysical.h"
#include <NxPhysics.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"
#include "plSurface/hsGMaterial.h"
#include "plSurface/plLayerInterface.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");
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)
{
#ifdef HS_DEBUGGING
plKey oldNode = GetSceneNode();
char msg[1024];
if (newNode)
sprintf(msg,"Physical object %s cannot change scenes. Already in %s, trying to switch to %s.",fObjectKey->GetName(),oldNode->GetName(),newNode->GetName());
else
sprintf(msg,"Physical object %s cannot change scenes. Already in %s, trying to switch to <nil key>.",fObjectKey->GetName(),oldNode->GetName());
hsAssert(oldNode == newNode, msg);
#endif // HS_DEBUGGING
}
/////////////////////////////////////////////////////////////////////
//
// 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
{
// Read in the cooked mesh
plPXStream pxs(stream);
if (recipe.bounds == plSimDefs::kHullBounds)
recipe.convexMesh = plSimulationMgr::GetInstance()->GetSDK()->createConvexMesh(pxs);
else
recipe.triMesh = plSimulationMgr::GetInstance()->GetSDK()->createTriangleMesh(pxs);
}
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);
}
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);
}
// 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;
}