/*==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 "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().c_str(), 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().c_str()));

    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);

    float 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 = new hsPoint3[fHullNumberPlanes*3];

    for (int i = 0; i < desc.numTriangles; i++)
    {
        uint32_t* triangle = (uint32_t*)(((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 = 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().c_str());
#else
            DetectorLogSpecial("**>Could have saved a missing enter collision: %s",GetObjectKey()->GetName().c_str());
#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().c_str());
        }
        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().c_str());
    }

    actorDesc.userData = this;
    actorDesc.name = GetKeyName().c_str();

    // 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().c_str());
            fActor->putToSleep();
        }
    }

    if (GetProperty(plSimulationInterface::kDisable))
        IEnable(false);
    if (GetProperty(plSimulationInterface::kSuppressed_DEAD))
        IEnable(false);

    plNodeRefMsg* refMsg = new plNodeRefMsg(fSceneNode, plRefMsg::kOnCreate, -1, plNodeRefMsg::kPhysical); 
    hsgResMgr::ResMgr()->AddViaNotify(GetKey(), refMsg, plRefFlags::kActiveRef);

    if (fWorldKey)
    {
        plGenRefMsg* ref = 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 = 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_t refCtxt = refMsg->GetContext();
    plKey refKey = refMsg->GetRef()->GetKey();
    plKey ourKey = GetKey();
    PhysRefType refType = PhysRefType(refMsg->fType);

    plString refKeyName = refKey ? refKey->GetName() : _TEMP_CONVERT_FROM_LITERAL("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;
        }

        plString name = _TEMP_CONVERT_FROM_LITERAL("(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.c_str());
    }

    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().c_str());

                // 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().c_str(), 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 = new plCorrectionMsg(GetObjectKey(), fCachedLocal2World, w2l, synchTransform);
                pCorrMsg->Send();
                if (fProxyGen)
                    fProxyGen->SetTransform(fCachedLocal2World, w2l);
                plProfile_EndLap(PhysicsUpdates, GetKeyName().c_str());
            }
        }
    }
}

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");

    // If we wake up normal dynamic actors, they might explode.
    // However, kinematics won't update if they are asleep. Thankfully, kinematics don't
    //          explode, move, or cause spontaneous nuclear warfare.
    if (fActor->readBodyFlag(NX_BF_KINEMATIC))
        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().c_str());
    }
}

// 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)
{
    // Not Supported
}

/////////////////////////////////////////////////////////////////////
//
// READING AND WRITING
//
/////////////////////////////////////////////////////////////////////

#include "plPXStream.h"

void plPXPhysical::Read(hsStream* stream, hsResMgr* mgr)
{
    plPhysical::Read(stream, mgr);  
    ClearMatrix(fCachedLocal2World);

    PhysRecipe recipe;
    recipe.mass = stream->ReadLEScalar();
    recipe.friction = stream->ReadLEScalar();
    recipe.restitution = stream->ReadLEScalar();
    recipe.bounds = (plSimDefs::Bounds)stream->ReadByte();
    recipe.group = (plSimDefs::Group)stream->ReadByte();
    recipe.reportsOn = stream->ReadLE32();
    fLOSDBs = stream->ReadLE16();
    //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, 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->ReadLEScalar();
        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;
    float 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 = 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->WriteLEScalar(fActor->getMass());
    stream->WriteLEScalar(friction);
    stream->WriteLEScalar(restitution);
    stream->WriteByte(fBoundsType);
    stream->WriteByte(fGroup);
    stream->WriteLE32(fReportsOn);
    stream->WriteLE16(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->WriteLEScalar(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_t 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().c_str());
        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_t* out)
{
    if (hsCheckBits(desc.flags, NX_MF_16_BIT_INDICES))
    {
        uint16_t* descTris = ((uint16_t*)(((char*)desc.triangles)+desc.pointStrideBytes*idx));
        out[0] = descTris[0];
        out[1] = descTris[1];
        out[2] = descTris[2];
    }
    else
    {
        uint32_t* descTris = ((uint32_t*)(((char*)desc.triangles)+desc.pointStrideBytes*idx));
        out[0] = (uint16_t)descTris[0];
        out[1] = (uint16_t)descTris[1];
        out[2] = (uint16_t)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_t* out)
{
    if (hsCheckBits(desc.flags, NX_MF_16_BIT_INDICES))
    {
        uint16_t* descTris = ((uint16_t*)(((char*)desc.triangles)+desc.pointStrideBytes*idx));
        out[0] = descTris[0];
        out[1] = descTris[1];
        out[2] = descTris[2];
    }
    else
    {
        uint32_t* descTris = ((uint32_t*)(((char*)desc.triangles)+desc.pointStrideBytes*idx));
        out[0] = (uint16_t)descTris[0];
        out[1] = (uint16_t)descTris[1];
        out[2] = (uint16_t)descTris[2];
    }
}

// Make a visible object that can be viewed by users for debugging purposes.
plDrawableSpans* plPXPhysical::CreateProxy(hsGMaterial* mat, hsTArray<uint32_t>& 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_t>    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_t>    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;
}
