//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "platform/platform.h"

#include "ts/tsShapeInstance.h"
#include "ts/tsMaterialList.h"
#include "scene/sceneObject.h"
#include "collision/convex.h"
#include "collision/collision.h"
#include "T3D/tsStatic.h" // TODO: We shouldn't have this dependancy!
#include "T3D/physics/physicsPlugin.h"
#include "T3D/physics/physicsCollision.h"
#include "collision/concretePolyList.h"
#include "collision/vertexPolyList.h"
#include "platform/profiler.h"

#include "opcode/Opcode.h"
#include "opcode/Ice/IceAABB.h"
#include "opcode/Ice/IcePoint.h"
#include "opcode/OPC_AABBTree.h"
#include "opcode/OPC_AABBCollider.h"

static bool gOpcodeInitialized = false;

//-------------------------------------------------------------------------------------
// Collision methods
//-------------------------------------------------------------------------------------

bool TSShapeInstance::buildPolyList(AbstractPolyList * polyList, S32 dl)
{
   // if dl==-1, nothing to do
   if (dl==-1)
      return false;

   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::buildPolyList");

   // get subshape and object detail
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   // This detail does not have any geometry.
   if ( ss < 0 )
      return false;

   // nothing emitted yet...
   bool emitted = false;
   U32 surfaceKey = 0;

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;
   if (start<end)
   {
      MatrixF initialMat;
      Point3F initialScale;
      polyList->getTransform(&initialMat,&initialScale);

      // set up for first object's node
      MatrixF mat;
      MatrixF scaleMat(true);
      F32* p = scaleMat;
      p[0]  = initialScale.x;
      p[5]  = initialScale.y;
      p[10] = initialScale.z;
      const MatrixF *previousMat = &mMeshObjects[start].getTransform();
      mat.mul(initialMat,scaleMat);
      mat.mul(*previousMat);
      polyList->setTransform(&mat,Point3F(1, 1, 1));

      // run through objects and collide
      for (S32 i=start; i<end; i++)
      {
         MeshObjectInstance * mesh = &mMeshObjects[i];

         if (od >= mesh->object->numMeshes)
            continue;

         if (&mesh->getTransform() != previousMat)
         {
            // different node from before, set up for this node
            previousMat = &mesh->getTransform();

            if (previousMat != NULL)
            {
               mat.mul(initialMat,scaleMat);
               mat.mul(*previousMat);
               polyList->setTransform(&mat,Point3F(1, 1, 1));
            }
         }
         // collide...
         emitted |= mesh->buildPolyList(od,polyList,surfaceKey,mMaterialList);
      }

      // restore original transform...
      polyList->setTransform(&initialMat,initialScale);
   }

   return emitted;
}

bool TSShapeInstance::getFeatures(const MatrixF& mat, const Point3F& n, ConvexFeature* cf, S32 dl)
{
   // if dl==-1, nothing to do
   if (dl==-1)
      return false;

   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::buildPolyList");

   // get subshape and object detail
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   // nothing emitted yet...
   bool emitted = false;
   U32 surfaceKey = 0;

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;
   if (start<end)
   {
      MatrixF final;
      const MatrixF* previousMat = &mMeshObjects[start].getTransform();
      final.mul(mat, *previousMat);

      // run through objects and collide
      for (S32 i=start; i<end; i++)
      {
         MeshObjectInstance * mesh = &mMeshObjects[i];

         if (od >= mesh->object->numMeshes)
            continue;

         if (&mesh->getTransform() != previousMat)
         {
            previousMat = &mesh->getTransform();
            final.mul(mat, *previousMat);
         }
         emitted |= mesh->getFeatures(od, final, n, cf, surfaceKey);
      }
   }
   return emitted;
}

bool TSShapeInstance::castRay(const Point3F & a, const Point3F & b, RayInfo * rayInfo, S32 dl)
{
   // if dl==-1, nothing to do
   if (dl==-1)
      return false;

   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::castRay");

   // get subshape and object detail
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   // This detail has no geometry to hit.
   if ( ss < 0 )
      return false;

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;
   RayInfo saveRay;
   saveRay.t = 1.0f;
   const MatrixF * saveMat = NULL;
   bool found = false;
   if (start<end)
   {
      Point3F ta, tb;

      // set up for first object's node
      MatrixF mat;
      const MatrixF * previousMat = &mMeshObjects[start].getTransform();
      mat = *previousMat;
      mat.inverse();
      mat.mulP(a,&ta);
      mat.mulP(b,&tb);

      // run through objects and collide
      for (S32 i=start; i<end; i++)
      {
         MeshObjectInstance * mesh = &mMeshObjects[i];

         if (od >= mesh->object->numMeshes)
            continue;

         if (&mesh->getTransform() != previousMat)
         {
            // different node from before, set up for this node
            previousMat = &mesh->getTransform();

            if (previousMat != NULL)
            {
               mat = *previousMat;
               mat.inverse();
               mat.mulP(a,&ta);
               mat.mulP(b,&tb);
            }
         }
         // collide...
         if (mesh->castRay(od,ta,tb,rayInfo, mMaterialList))
         {
            if (!rayInfo)
               return true;

            if (rayInfo->t <= saveRay.t)
            {
               saveRay = *rayInfo;
               saveMat = previousMat;
            }
            found = true;
         }
      }
   }

   // collide with any skins for this detail level...
   // TODO: if ever...

   // finalize the deal...
   if (found)
   {
      *rayInfo = saveRay;
      saveMat->mulV(rayInfo->normal);
      rayInfo->point  = b-a;
      rayInfo->point *= rayInfo->t;
      rayInfo->point += a;
   }
   return found;
}

bool TSShapeInstance::castRayRendered(const Point3F & a, const Point3F & b, RayInfo * rayInfo, S32 dl)
{
   // if dl==-1, nothing to do
   if (dl==-1)
      return false;

   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::castRayRendered");

   // get subshape and object detail
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   if ( ss == -1 )
      return false;

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;
   RayInfo saveRay;
   saveRay.t = 1.0f;
   const MatrixF * saveMat = NULL;
   bool found = false;
   if (start<end)
   {
      Point3F ta, tb;

      // set up for first object's node
      MatrixF mat;
      const MatrixF * previousMat = &mMeshObjects[start].getTransform();
      mat = *previousMat;
      mat.inverse();
      mat.mulP(a,&ta);
      mat.mulP(b,&tb);

      // run through objects and collide
      for (S32 i=start; i<end; i++)
      {
         MeshObjectInstance * mesh = &mMeshObjects[i];

         if (od >= mesh->object->numMeshes)
            continue;

         if (&mesh->getTransform() != previousMat)
         {
            // different node from before, set up for this node
            previousMat = &mesh->getTransform();

            if (previousMat != NULL)
            {
               mat = *previousMat;
               mat.inverse();
               mat.mulP(a,&ta);
               mat.mulP(b,&tb);
            }
         }
         // collide...
         if (mesh->castRayRendered(od,ta,tb,rayInfo, mMaterialList))
         {
            if (!rayInfo)
               return true;

            if (rayInfo->t <= saveRay.t)
            {
               saveRay = *rayInfo;
               saveMat = previousMat;
            }
            found = true;
         }
      }
   }

   // collide with any skins for this detail level...
   // TODO: if ever...

   // finalize the deal...
   if (found)
   {
      *rayInfo = saveRay;
      saveMat->mulV(rayInfo->normal);
      rayInfo->point  = b-a;
      rayInfo->point *= rayInfo->t;
      rayInfo->point += a;
   }
   return found;
}

Point3F TSShapeInstance::support(const Point3F & v, S32 dl)
{
   // if dl==-1, nothing to do
   AssertFatal(dl != -1, "Error, should never try to collide with a non-existant detail level!");
   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::support");

   // get subshape and object detail
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;

   F32     currMaxDP   = -1e9f;
   Point3F currSupport = Point3F(0, 0, 0);
   const MatrixF * previousMat = NULL;
   MatrixF mat;
   if (start<end)
   {
      Point3F va;

      // set up for first object's node
      previousMat = &mMeshObjects[start].getTransform();
      mat = *previousMat;
      mat.inverse();

      // run through objects and collide
      for (S32 i=start; i<end; i++)
      {
         MeshObjectInstance * mesh = &mMeshObjects[i];

         if (od >= mesh->object->numMeshes)
            continue;

         TSMesh* physMesh = mesh->getMesh(od);
         if (physMesh && !mesh->forceHidden && mesh->visible > 0.01f)
         {
            // collide...
            if (&mesh->getTransform() != previousMat)
            {
               // different node from before, set up for this node
               previousMat = &mesh->getTransform();
               mat = *previousMat;
               mat.inverse();
            }
            mat.mulV(v, &va);
            physMesh->support(mesh->frame, va, &currMaxDP, &currSupport);
         }
      }
   }

   if (currMaxDP != -1e9f)
   {
      previousMat->mulP(currSupport);
      return currSupport;
   }
   else
   {
      return Point3F(0, 0, 0);
   }
}

void TSShapeInstance::computeBounds(S32 dl, Box3F & bounds)
{
   // if dl==-1, nothing to do
   if (dl==-1)
      return;

   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::computeBounds");

   // get subshape and object detail
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   // use shape bounds for imposter details
   if (ss < 0)
   {
      bounds = mShape->mBounds;
      return;
   }

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;

   // run through objects and updating bounds as we go
   bounds.minExtents.set( 10E30f, 10E30f, 10E30f);
   bounds.maxExtents.set(-10E30f,-10E30f,-10E30f);
   Box3F box;
   for (S32 i=start; i<end; i++)
   {
      MeshObjectInstance * mesh = &mMeshObjects[i];

      if (od >= mesh->object->numMeshes)
         continue;

      if (mesh->getMesh(od))
      {
         mesh->getMesh(od)->computeBounds(mesh->getTransform(),box, 0); // use frame 0 so TSSkinMesh uses skinned verts to compute bounds
         bounds.minExtents.setMin(box.minExtents);
         bounds.maxExtents.setMax(box.maxExtents);
      }
   }
}

//-------------------------------------------------------------------------------------
// Object (MeshObjectInstance & PluginObjectInstance) collision methods
//-------------------------------------------------------------------------------------

bool TSShapeInstance::ObjectInstance::buildPolyList(S32 objectDetail, AbstractPolyList *polyList, U32 &surfaceKey, TSMaterialList *materials )
{
   TORQUE_UNUSED( objectDetail );
   TORQUE_UNUSED( polyList );
   TORQUE_UNUSED( surfaceKey );
   TORQUE_UNUSED( materials );

   AssertFatal(0,"TSShapeInstance::ObjectInstance::buildPolyList:  no default method.");
   return false;
}

bool TSShapeInstance::ObjectInstance::getFeatures(S32 objectDetail, const MatrixF& mat, const Point3F& n, ConvexFeature* cf, U32& surfaceKey)
{
   TORQUE_UNUSED( objectDetail );
   TORQUE_UNUSED( mat );
   TORQUE_UNUSED( n );
   TORQUE_UNUSED( cf );
   TORQUE_UNUSED( surfaceKey );

   AssertFatal(0,"TSShapeInstance::ObjectInstance::buildPolyList:  no default method.");
   return false;
}

void TSShapeInstance::ObjectInstance::support(S32, const Point3F&, F32*, Point3F*)
{
   AssertFatal(0,"TSShapeInstance::ObjectInstance::supprt:  no default method.");
}

bool TSShapeInstance::ObjectInstance::castRay( S32 objectDetail, const Point3F &start, const Point3F &end, RayInfo *rayInfo, TSMaterialList *materials )
{
   TORQUE_UNUSED( objectDetail );
   TORQUE_UNUSED( start );
   TORQUE_UNUSED( end );
   TORQUE_UNUSED( rayInfo );

   AssertFatal(0,"TSShapeInstance::ObjectInstance::castRay:  no default method.");
   return false;
}

bool TSShapeInstance::MeshObjectInstance::buildPolyList( S32 objectDetail, AbstractPolyList *polyList, U32 &surfaceKey, TSMaterialList *materials )
{
   TSMesh * mesh = getMesh(objectDetail);
   if (mesh && !forceHidden && visible>0.01f)
      return mesh->buildPolyList(frame,polyList,surfaceKey,materials);
   return false;
}

bool TSShapeInstance::MeshObjectInstance::getFeatures(S32 objectDetail, const MatrixF& mat, const Point3F& n, ConvexFeature* cf, U32& surfaceKey)
{
   TSMesh* mesh = getMesh(objectDetail);
   if (mesh && !forceHidden && visible > 0.01f)
      return mesh->getFeatures(frame, mat, n, cf, surfaceKey);
   return false;
}

void TSShapeInstance::MeshObjectInstance::support(S32 objectDetail, const Point3F& v, F32* currMaxDP, Point3F* currSupport)
{
   TSMesh* mesh = getMesh(objectDetail);
   if (mesh && !forceHidden && visible > 0.01f)
      mesh->support(frame, v, currMaxDP, currSupport);
}


bool TSShapeInstance::MeshObjectInstance::castRay( S32 objectDetail, const Point3F & start, const Point3F & end, RayInfo * rayInfo, TSMaterialList* materials )
{
   TSMesh* mesh = getMesh( objectDetail );
   if( mesh && !forceHidden && visible > 0.01f )
      return mesh->castRay( frame, start, end, rayInfo, materials );
   return false;
}

bool TSShapeInstance::MeshObjectInstance::castRayRendered( S32 objectDetail, const Point3F & start, const Point3F & end, RayInfo * rayInfo, TSMaterialList* materials )
{
   TSMesh* mesh = getMesh( objectDetail );
   if( mesh && !forceHidden && visible > 0.01f )
      return mesh->castRayRendered( frame, start, end, rayInfo, materials );
   return false;
}

bool TSShapeInstance::ObjectInstance::castRayOpcode( S32 objectDetail, const Point3F & start, const Point3F & end, RayInfo *rayInfo, TSMaterialList* materials )
{
   TORQUE_UNUSED( objectDetail );
   TORQUE_UNUSED( start );
   TORQUE_UNUSED( end );
   TORQUE_UNUSED( rayInfo );
   TORQUE_UNUSED( materials );

   return false;
}

bool TSShapeInstance::ObjectInstance::buildPolyListOpcode( S32 objectDetail, AbstractPolyList *polyList, U32 &surfaceKey, TSMaterialList *materials )
{
   TORQUE_UNUSED( objectDetail );
   TORQUE_UNUSED( polyList );
   TORQUE_UNUSED( surfaceKey );
   TORQUE_UNUSED( materials );

   return false;
}

bool TSShapeInstance::ObjectInstance::buildConvexOpcode( const MatrixF &mat, S32 objectDetail, const Box3F &bounds, Convex *c, Convex *list )
{
   TORQUE_UNUSED( mat );
   TORQUE_UNUSED( objectDetail );
   TORQUE_UNUSED( bounds );
   TORQUE_UNUSED( c );
   TORQUE_UNUSED( list );

   return false;
}

bool TSShapeInstance::MeshObjectInstance::castRayOpcode( S32 objectDetail, const Point3F & start, const Point3F & end, RayInfo *info, TSMaterialList* materials )
{
   TSMesh * mesh = getMesh(objectDetail);
   if (mesh && !forceHidden && visible>0.01f)
      return mesh->castRayOpcode(start, end, info, materials);
   return false;
}

bool TSShapeInstance::MeshObjectInstance::buildPolyListOpcode( S32 objectDetail, AbstractPolyList *polyList, const Box3F &box, TSMaterialList *materials )
{
   TSMesh * mesh = getMesh(objectDetail);
   if ( mesh && !forceHidden && visible > 0.01f && box.isOverlapped( mesh->getBounds() ) )
      return mesh->buildPolyListOpcode(frame,polyList,box,materials);
   return false;
}

bool TSShapeInstance::MeshObjectInstance::buildConvexOpcode( const MatrixF &mat, S32 objectDetail, const Box3F &bounds, Convex *c, Convex *list)
{
   TSMesh * mesh = getMesh(objectDetail);
   if ( mesh && !forceHidden && visible > 0.01f && bounds.isOverlapped( mesh->getBounds() ) )
      return mesh->buildConvexOpcode(mat, bounds, c, list);
   return false;
}

bool TSShapeInstance::buildPolyListOpcode( S32 dl, AbstractPolyList *polyList, const Box3F &box )
{
   PROFILE_SCOPE( TSShapeInstance_buildPolyListOpcode_MeshObjInst );

   if (dl==-1)
      return false;

   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::buildPolyListOpcode");

   // get subshape and object detail
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   if ( ss < 0 )
      return false;

   S32 od = detail->objectDetailNum;

   // nothing emitted yet...
   bool emitted = false;

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;
   if (start<end)
   {
      MatrixF initialMat;
      Point3F initialScale;
      polyList->getTransform(&initialMat,&initialScale);

      // set up for first object's node
      MatrixF mat;
      MatrixF scaleMat(true);
      F32* p = scaleMat;
      p[0]  = initialScale.x;
      p[5]  = initialScale.y;
      p[10] = initialScale.z;
      const MatrixF * previousMat = &mMeshObjects[start].getTransform();
      mat.mul(initialMat,scaleMat);
      mat.mul(*previousMat);
      polyList->setTransform(&mat,Point3F(1, 1, 1));

      // Update our bounding box...
      Box3F localBox = box;
      MatrixF otherMat = mat;
      otherMat.inverse();
      otherMat.mul(localBox);

      // run through objects and collide
      for (S32 i=start; i<end; i++)
      {
         MeshObjectInstance * mesh = &mMeshObjects[i];

         if (od >= mesh->object->numMeshes)
            continue;

         if (&mesh->getTransform() != previousMat)
         {
            // different node from before, set up for this node
            previousMat = &mesh->getTransform();

            if (previousMat != NULL)
            {
               mat.mul(initialMat,scaleMat);
               mat.mul(*previousMat);
               polyList->setTransform(&mat,Point3F(1, 1, 1));

               // Update our bounding box...
               otherMat = mat;
               otherMat.inverse();
               localBox = box;
               otherMat.mul(localBox);
            }
         }

         // collide...
         emitted |= mesh->buildPolyListOpcode(od,polyList,localBox,mMaterialList);
      }

      // restore original transform...
      polyList->setTransform(&initialMat,initialScale);
   }

   return emitted;
}

bool TSShapeInstance::castRayOpcode( S32 dl, const Point3F & startPos, const Point3F & endPos, RayInfo *info)
{
   // if dl==-1, nothing to do
   if (dl==-1)
      return false;

   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::castRayOpcode");

   info->t = 100.f;

   // get subshape and object detail
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   if ( ss < 0 )
      return false;

   S32 od = detail->objectDetailNum;

   // nothing emitted yet...
   bool emitted = false;

   const MatrixF* saveMat = NULL;
   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;
   if (start<end)
   {
      MatrixF mat;
      const MatrixF * previousMat = &mMeshObjects[start].getTransform();
      mat = *previousMat;
      mat.inverse();
      Point3F localStart, localEnd;
      mat.mulP(startPos, &localStart);
      mat.mulP(endPos, &localEnd);

      // run through objects and collide
      for (S32 i=start; i<end; i++)
      {
         MeshObjectInstance * mesh = &mMeshObjects[i];

         if (od >= mesh->object->numMeshes)
            continue;

         if (&mesh->getTransform() != previousMat)
         {
            // different node from before, set up for this node
            previousMat = &mesh->getTransform();

            if (previousMat != NULL)
            {
               mat = *previousMat;
               mat.inverse();
               mat.mulP(startPos, &localStart);
               mat.mulP(endPos, &localEnd);
            }
         }

         // collide...
         if ( mesh->castRayOpcode(od,localStart, localEnd, info, mMaterialList) )
         {
            saveMat = previousMat;
            emitted = true;
         }
      }
   }

   if ( emitted )
   {
      saveMat->mulV(info->normal);
      info->point  = endPos - startPos;
      info->point *= info->t;
      info->point += startPos;
   }

   return emitted;
}

bool TSShapeInstance::buildConvexOpcode( const MatrixF &objMat, const Point3F &objScale, S32 dl, const Box3F &bounds, Convex *c, Convex *list )
{
   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::buildConvexOpcode");

   // get subshape and object detail
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   // nothing emitted yet...
   bool emitted = false;

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;
   if (start<end)
   {
      MatrixF initialMat = objMat;
      Point3F initialScale = objScale;

      // set up for first object's node
      MatrixF mat;
      MatrixF scaleMat(true);
      F32* p = scaleMat;
      p[0]  = initialScale.x;
      p[5]  = initialScale.y;
      p[10] = initialScale.z;
      const MatrixF * previousMat = &mMeshObjects[start].getTransform();
      mat.mul(initialMat,scaleMat);
      mat.mul(*previousMat);

      // Update our bounding box...
      Box3F localBox = bounds;
      MatrixF otherMat = mat;
      otherMat.inverse();
      otherMat.mul(localBox);

      // run through objects and collide
      for (S32 i=start; i<end; i++)
      {
         MeshObjectInstance * mesh = &mMeshObjects[i];

         if (od >= mesh->object->numMeshes)
            continue;

         if (&mesh->getTransform() != previousMat)
         {
            // different node from before, set up for this node
            previousMat = &mesh->getTransform();

            if (previousMat != NULL)
            {
               mat.mul(initialMat,scaleMat);
               mat.mul(*previousMat);

               // Update our bounding box...
               otherMat = mat;
               otherMat.inverse();
               localBox = bounds;
               otherMat.mul(localBox);
            }
         }

         // collide... note we pass the original mech transform
         // here so that the convex data returned is in mesh space.
         emitted |= mesh->buildConvexOpcode(*previousMat,od,localBox,c, list);
      }
   }

   return emitted;
}

void TSShape::findColDetails( bool useVisibleMesh, Vector<S32> *outDetails, Vector<S32> *outLOSDetails ) const
{
   PROFILE_SCOPE( TSShape_findColDetails );

   if ( useVisibleMesh )
   {
      // If we're using the visible mesh for collision then
      // find the highest detail and use that.

      U32 highestDetail = -1;
      F32 highestSize = -F32_MAX;

      for ( U32 i = 0; i < details.size(); i++ )
      {
         // Make sure we skip any details that shouldn't be rendered
         if ( details[i].size < 0 )
            continue;

         /*
         // Also make sure we skip any collision details with a size.
         const String &name = names[details[i].nameIndex];
         if (  dStrStartsWith( name, "Collision" ) ||
               dStrStartsWith( name, "LOS" ) )
            continue;
         */

         // Otherwise test against the current highest size
         if ( details[i].size > highestSize )
         {
            highestDetail = i;
            highestSize = details[i].size;
         }
      }

      // We use the same detail for both raycast and collisions.
      if ( highestDetail != -1 )
      {
         outDetails->push_back( highestDetail );
         if ( outLOSDetails )
            outLOSDetails->push_back( highestDetail );
      }

      return;
   }

   // Detail meshes starting with COL or LOS is considered
   // to be a collision mesh.
   //
   // The LOS (light of sight) details are used for raycasts.

   for ( U32 i = 0; i < details.size(); i++ )
   {
      const String &name = names[ details[i].nameIndex ];
      if ( !dStrStartsWith( name, "Collision" ) )
         continue;

      outDetails->push_back(i);

      // If we're not returning LOS details then skip out.
      if ( !outLOSDetails )
         continue;

      // The way LOS works is that it will check to see if there is a LOS detail that matches
      // the the collision detail + 1 + MaxCollisionShapes (this variable name should change in
      // the future). If it can't find a matching LOS it will simply use the collision instead.
      // We check for any "unmatched" LOS's further down.

      // Extract the detail number from the name.
      S32 number = 0;
      String::GetTrailingNumber( name, number );
      
      // Look for a matching LOS collision detail.
      //
      // TODO: Fix the old 9 detail offset which is there
      // because you cannot have two detail markers with
      // the same detail number.
      //
      const S32 LOSOverrideOffset = 9;
      String buff = String::ToString( "LOS-%d", mAbs( number ) + LOSOverrideOffset );
      S32 los = findDetail( buff );
      
      // If we didn't find the lod detail then use the
      // normal collision detail for LOS tests.
      if ( los == -1 )
         los = i;

      outLOSDetails->push_back( los );
   }

   // If we're not returning LOS details then skip out.
   if ( !outLOSDetails )
      return;

   // Snag any "unmatched" LOS details and put 
   // them at the end of the list.
   for ( U32 i = 0; i < details.size(); i++ )
   {
      const String &name = names[ details[i].nameIndex ];
      if ( !dStrStartsWith( name, "LOS" ) )
         continue;

      // See if we already have this LOS
      bool found = false;
      for (U32 j = 0; j < outLOSDetails->size(); j++)
      {
         if ( (*outLOSDetails)[j] == i )
         {
            found = true;
            break;
         }
      }

      if ( !found )
         outLOSDetails->push_back(i);
   }
}

PhysicsCollision* TSShape::buildColShape( bool useVisibleMesh, const Point3F &scale )
{
   return _buildColShapes( useVisibleMesh, scale, NULL, false );
}

void TSShape::buildColShapes( bool useVisibleMesh, const Point3F &scale, Vector< CollisionShapeInfo > *list )
{
   _buildColShapes( useVisibleMesh, scale, list, true );
}

PhysicsCollision* TSShape::_buildColShapes( bool useVisibleMesh, const Point3F &scale, Vector< CollisionShapeInfo > *list, bool perMesh )
{
   PROFILE_SCOPE( TSShape_buildColShapes );

   if ( !PHYSICSMGR )
      return NULL;

   PhysicsCollision *colShape = NULL;
   U32 surfaceKey = 0;

   if ( useVisibleMesh )
   {
      // Here we build triangle collision meshes from the
      // visible detail levels.

      // A negative subshape on the detail means we don't have geometry.
      const TSShape::Detail &detail = details[0];     
      if ( detail.subShapeNum < 0 )
         return NULL;

      // We don't try to optimize the triangles we're given
      // and assume the art was created properly for collision.
      ConcretePolyList polyList;
      polyList.setTransform( &MatrixF::Identity, scale );

      // Create the collision meshes.
      S32 start = subShapeFirstObject[ detail.subShapeNum ];
      S32 end = start + subShapeNumObjects[ detail.subShapeNum ];
      for ( S32 o=start; o < end; o++ )
      {
         const TSShape::Object &object = objects[o];
         if ( detail.objectDetailNum >= object.numMeshes )
            continue;

         // No mesh or no verts.... nothing to do.
         TSMesh *mesh = meshes[ object.startMeshIndex + detail.objectDetailNum ];
         if ( !mesh || mesh->mNumVerts == 0 )
            continue;

         // Gather the mesh triangles.
         polyList.clear();
         mesh->buildPolyList( 0, &polyList, surfaceKey, NULL );

         // Create the collision shape if we haven't already.
         if ( !colShape )
            colShape = PHYSICSMGR->createCollision();

         // Get the object space mesh transform.
         MatrixF localXfm;
         getNodeWorldTransform( object.nodeIndex, &localXfm );

         colShape->addTriangleMesh( polyList.mVertexList.address(),
            polyList.mVertexList.size(),
            polyList.mIndexList.address(),
            polyList.mIndexList.size() / 3,
            localXfm );

         if ( perMesh )
         {
            list->increment();
            list->last().colNode = -1;
            list->last().colShape = colShape;
            colShape = NULL;
         }
      }

      // Return what we built... if anything.
      return colShape;
   }


   // Scan out the collision hulls...
   //
   // TODO: We need to support LOS collision for physics.
   //
   for ( U32 i = 0; i < details.size(); i++ )
   {
      const TSShape::Detail &detail = details[i];
      const String &name = names[detail.nameIndex];

      // Is this a valid collision detail.
      if ( !dStrStartsWith( name, "Collision" ) || detail.subShapeNum < 0 )
         continue;

      // Now go thru the meshes for this detail.
      S32 start = subShapeFirstObject[ detail.subShapeNum ];
      S32 end = start + subShapeNumObjects[ detail.subShapeNum ];
      if ( start >= end )
         continue;         

      for ( S32 o=start; o < end; o++ )
      {
         const TSShape::Object &object = objects[o];
         const String &meshName = names[ object.nameIndex ];

         if ( object.numMeshes <= detail.objectDetailNum )
            continue;

         // No mesh, a flat bounds, or no verts.... nothing to do.
         TSMesh *mesh = meshes[ object.startMeshIndex + detail.objectDetailNum ];
         if ( !mesh || mesh->getBounds().isEmpty() || mesh->mNumVerts == 0 )
            continue;

         // We need the default mesh transform.
         MatrixF localXfm;
         getNodeWorldTransform( object.nodeIndex, &localXfm );
         Point3F t = localXfm.getPosition();
         t.convolve(scale);
         localXfm.setPosition(t);

         // We have some sort of collision shape... so allocate it.
         if ( !colShape )
            colShape = PHYSICSMGR->createCollision();

         // We have geometry... what is it?
         if ( dStrStartsWith( meshName, "Colbox" ) )
         {
            // The bounds define the box extents directly.
            Point3F halfWidth = mesh->getBounds().getExtents() * scale * 0.5f;

            // Add the offset to the center of the bounds 
            // into the local space transform.
            MatrixF centerXfm( true );
            Point3F t = mesh->getBounds().getCenter();
            t.convolve(scale);
            centerXfm.setPosition(t);
            localXfm.mul( centerXfm );

            colShape->addBox( halfWidth, localXfm );
         }
         else if ( dStrStartsWith( meshName, "Colsphere" ) )
         {
            // Get a sphere inscribed to the bounds.
            Point3F extents = mesh->getBounds().getExtents() * scale;
            F32 radius = extents.least() * 0.5f;

            // Add the offset to the center of the bounds
            // into the local space transform.
            MatrixF primXfm( true );
            Point3F t = mesh->getBounds().getCenter();
            t.convolve(scale);
            primXfm.setPosition(t);
            localXfm.mul( primXfm );

            colShape->addSphere( radius, localXfm );
         }
         else if ( dStrStartsWith( meshName, "Colcapsule" ) )
         {
            // Use the smallest extent as the radius for the capsule.
            Point3F extents = mesh->getBounds().getExtents() * scale;
            F32 radius = extents.least() * 0.5f;

            // We need to center the capsule and align it to the Y axis.
            MatrixF primXfm( true );
            Point3F t = mesh->getBounds().getCenter();
            t.convolve(scale);
            primXfm.setPosition(t);

            // Use the longest axis as the capsule height.
            F32 height = -radius * 2.0f;
            if ( extents.x > extents.y && extents.x > extents.z )
            {
               primXfm.setColumn( 0, Point3F( 0, 0, 1 ) );
               primXfm.setColumn( 1, Point3F( 1, 0, 0 ) );
               primXfm.setColumn( 2, Point3F( 0, 1, 0 ) );
               height += extents.x;
            }
            else if ( extents.z > extents.x && extents.z > extents.y )
            {
               primXfm.setColumn( 0, Point3F( 0, 1, 0 ) );
               primXfm.setColumn( 1, Point3F( 0, 0, 1 ) );
               primXfm.setColumn( 2, Point3F( 1, 0, 0 ) );
               height += extents.z;
            }
            else
               height += extents.y;

            // Add the primitive transform into the local transform.
            localXfm.mul( primXfm );

            // If we didn't find a positive height then fallback to
            // creating a sphere which is better than nothing.
            if ( height > 0.0f )
               colShape->addCapsule( radius, height, localXfm );
            else
               colShape->addSphere( radius, localXfm );
         }
         else if ( dStrStartsWith( meshName, "Colmesh" ) )
         {
            // For a triangle mesh we gather the triangles raw from the
            // mesh and don't do any optimizations or cleanup.
            ConcretePolyList polyList;
            polyList.setTransform( &MatrixF::Identity, scale );
            mesh->buildPolyList( 0, &polyList, surfaceKey, NULL );
            colShape->addTriangleMesh( polyList.mVertexList.address(), 
                                       polyList.mVertexList.size(),
                                       polyList.mIndexList.address(),
                                       polyList.mIndexList.size() / 3,
                                       localXfm );
         }
         else
         {
            // Any other mesh name we assume as a generic convex hull.
            //
            // Collect the verts using the vertex polylist which will 
            // filter out duplicates.  This is importaint as the convex
            // generators can sometimes fail with duplicate verts.
            //
            VertexPolyList polyList;
            MatrixF meshMat( localXfm );

            polyList.setTransform( &MatrixF::Identity, scale );
            mesh->buildPolyList( 0, &polyList, surfaceKey, NULL );
            colShape->addConvex( polyList.getVertexList().address(), 
                                 polyList.getVertexList().size(),
                                 meshMat );
         }

         if ( perMesh )
         {
            list->increment();
            
            S32 detailNum;
            String::GetTrailingNumber( name, detailNum );            
            
            String str = String::ToString( "%s%i", meshName.c_str(), detailNum );
            S32 found = findNode( str );

            if ( found == -1 )
            {
               str = str.replace('-','_');
               found = findNode( str );
            }

            list->last().colNode = found;            
            list->last().colShape = colShape;

            colShape = NULL;
         }

      } // objects

   } // details

   return colShape;
}

bool TSMesh::buildPolyListOpcode( const S32 od, AbstractPolyList *polyList, const Box3F &nodeBox, TSMaterialList *materials )
{
   PROFILE_SCOPE( TSMesh_buildPolyListOpcode );

   // This is small... there is no win for preallocating it.
   Opcode::AABBCollider opCollider;
   opCollider.SetPrimitiveTests( true );

   // This isn't really needed within the AABBCollider as 
   // we don't use temporal coherance... use a static to 
   // remove the allocation overhead.
   static Opcode::AABBCache opCache;

   IceMaths::AABB opBox;
   opBox.SetMinMax(  Point( nodeBox.minExtents.x, nodeBox.minExtents.y, nodeBox.minExtents.z ),
                     Point( nodeBox.maxExtents.x, nodeBox.maxExtents.y, nodeBox.maxExtents.z ) );
   Opcode::CollisionAABB opCBox(opBox);

   if ( !opCollider.Collide( opCache, opCBox, *mOptTree ) )
      return false;

   U32 count = opCollider.GetNbTouchedPrimitives();
   const udword *idx = opCollider.GetTouchedPrimitives();

   Opcode::VertexPointers vp;
   U32 plIdx[3];
   S32 j;
   Point3F tmp;
   const IceMaths::Point **verts;
   const	Opcode::MeshInterface *mi = mOptTree->GetMeshInterface();

   for ( S32 i=0; i < count; i++ )
   {
      // Get the triangle...
      mi->GetTriangle( vp, idx[i] );
      verts = vp.Vertex;

      // And register it in the polylist...
      polyList->begin( NULL, i );

      for( j = 2; j > -1; j-- )
      {
         tmp.set( verts[j]->x, verts[j]->y, verts[j]->z );
         plIdx[j] = polyList->addPoint( tmp );
         polyList->vertex( plIdx[j] );
      }

      polyList->plane( plIdx[0], plIdx[2], plIdx[1] );

      polyList->end();
   }

   // TODO: Add a polyList->getCount() so we can see if we
   // got clipped polys and didn't really emit anything.
   return count > 0;
}

bool TSMesh::buildConvexOpcode( const MatrixF &meshToObjectMat, const Box3F &nodeBox, Convex *convex, Convex *list )
{
   PROFILE_SCOPE( TSMesh_buildConvexOpcode );

   // This is small... there is no win for preallocating it.
   Opcode::AABBCollider opCollider;
   opCollider.SetPrimitiveTests( true );

   // This isn't really needed within the AABBCollider as 
   // we don't use temporal coherance... use a static to 
   // remove the allocation overhead.
   static Opcode::AABBCache opCache;

   IceMaths::AABB opBox;
   opBox.SetMinMax(  Point( nodeBox.minExtents.x, nodeBox.minExtents.y, nodeBox.minExtents.z ),
                     Point( nodeBox.maxExtents.x, nodeBox.maxExtents.y, nodeBox.maxExtents.z ) );
   Opcode::CollisionAABB opCBox(opBox);

   if( !opCollider.Collide( opCache, opCBox, *mOptTree ) )
      return false;

   U32 cnt = opCollider.GetNbTouchedPrimitives();
   const udword *idx = opCollider.GetTouchedPrimitives();

   Opcode::VertexPointers vp;
   for ( S32 i = 0; i < cnt; i++ )
   {
      // First, check our active convexes for a potential match (and clean things
      // up, too.)
      const U32 curIdx = idx[i];

      // See if the square already exists as part of the working set.
      bool gotMatch = false;
      CollisionWorkingList& wl = convex->getWorkingList();
      for ( CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext )
      {
         if( itr->mConvex->getType() != TSPolysoupConvexType )
            continue;

         const TSStaticPolysoupConvex *chunkc = static_cast<TSStaticPolysoupConvex*>( itr->mConvex );

         if( chunkc->getObject() != TSStaticPolysoupConvex::smCurObject )
            continue;
               
         if( chunkc->mesh != this )
            continue;

         if( chunkc->idx != curIdx )
            continue;

         // A match! Don't need to add it.
         gotMatch = true;
         break;
      }

      if( gotMatch )
         continue;

      // Get the triangle...
      mOptTree->GetMeshInterface()->GetTriangle( vp, idx[i] );

      Point3F a( vp.Vertex[0]->x, vp.Vertex[0]->y, vp.Vertex[0]->z );
      Point3F b( vp.Vertex[1]->x, vp.Vertex[1]->y, vp.Vertex[1]->z );
      Point3F c( vp.Vertex[2]->x, vp.Vertex[2]->y, vp.Vertex[2]->z );

      // Transform the result into object space!
      meshToObjectMat.mulP( a );
      meshToObjectMat.mulP( b );
      meshToObjectMat.mulP( c );

      PlaneF p( c, b, a );
      Point3F peak = ((a + b + c) / 3.0f) - (p * 0.15f);

      // Set up the convex...
      TSStaticPolysoupConvex *cp = new TSStaticPolysoupConvex();

      list->registerObject( cp );
      convex->addToWorkingList( cp );

      cp->mesh    = this;
      cp->idx     = curIdx;
      cp->mObject = TSStaticPolysoupConvex::smCurObject;

      cp->normal = p;
      cp->verts[0] = a;
      cp->verts[1] = b;
      cp->verts[2] = c;
      cp->verts[3] = peak;

      // Update the bounding box.
      Box3F &bounds = cp->box;
      bounds.minExtents.set( F32_MAX,  F32_MAX,  F32_MAX );
      bounds.maxExtents.set( -F32_MAX, -F32_MAX, -F32_MAX );

      bounds.minExtents.setMin( a );
      bounds.minExtents.setMin( b );
      bounds.minExtents.setMin( c );
      bounds.minExtents.setMin( peak );

      bounds.maxExtents.setMax( a );
      bounds.maxExtents.setMax( b );
      bounds.maxExtents.setMax( c );
      bounds.maxExtents.setMax( peak );
   }

   return true;
}

void TSMesh::prepOpcodeCollision()
{
   // Make sure opcode is loaded!
   if( !gOpcodeInitialized )
   {
      Opcode::InitOpcode();
      gOpcodeInitialized = true;
   }

   // Don't re init if we already have something...
   if ( mOptTree )
      return;

   // Ok, first set up a MeshInterface
   Opcode::MeshInterface *mi = new Opcode::MeshInterface();
   mOpMeshInterface = mi;

   // Figure out how many triangles we have...
   U32 triCount = 0;
   const U32 base = 0;
   for ( U32 i = 0; i < mPrimitives.size(); i++ )
   {
      TSDrawPrimitive & draw = mPrimitives[i];
      const U32 start = draw.start;

      AssertFatal( draw.matIndex & TSDrawPrimitive::Indexed,"TSMesh::buildPolyList (1)" );

      // gonna depend on what kind of primitive it is...
      if ( (draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles )
         triCount += draw.numElements / 3;
      else
      {
         // Have to walk the tristrip to get a count... may have degenerates
         U32 idx0 = base + mIndices[start + 0];
         U32 idx1;
         U32 idx2 = base + mIndices[start + 1];
         U32 * nextIdx = &idx1;
         for ( S32 j = 2; j < draw.numElements; j++ )
         {
            *nextIdx = idx2;
            //            nextIdx = (j%2)==0 ? &idx0 : &idx1;
            nextIdx = (U32*) ( (dsize_t)nextIdx ^ (dsize_t)&idx0 ^ (dsize_t)&idx1);
            idx2 = base + mIndices[start + j];
            if ( idx0 == idx1 || idx0 == idx2 || idx1 == idx2 )
               continue;

            triCount++;
         }
      }
   }

   // Just do the first trilist for now.
   mi->SetNbVertices( mVertexData.isReady() ? mNumVerts : mVerts.size() );
   mi->SetNbTriangles( triCount );

   // Stuff everything into appropriate arrays.
   IceMaths::IndexedTriangle *its = new IceMaths::IndexedTriangle[ mi->GetNbTriangles() ], *curIts = its;
   IceMaths::Point           *pts = new IceMaths::Point[ mi->GetNbVertices() ];

   mOpTris = its;
   mOpPoints = pts;

   // add the polys...
   for ( U32 i = 0; i < mPrimitives.size(); i++ )
   {
      TSDrawPrimitive & draw = mPrimitives[i];
      const U32 start = draw.start;

      AssertFatal( draw.matIndex & TSDrawPrimitive::Indexed,"TSMesh::buildPolyList (1)" );

      const U32 matIndex = draw.matIndex & TSDrawPrimitive::MaterialMask;

      // gonna depend on what kind of primitive it is...
      if ( (draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles )
      {
         for ( S32 j = 0; j < draw.numElements; )
         {
            curIts->mVRef[2] = base + mIndices[start + j + 0];
            curIts->mVRef[1] = base + mIndices[start + j + 1];
            curIts->mVRef[0] = base + mIndices[start + j + 2];
            curIts->mMatIdx = matIndex;
            curIts++;

            j += 3;
         }
      }
      else
      {
         AssertFatal( (draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Strip,"TSMesh::buildPolyList (2)" );

         U32 idx0 = base + mIndices[start + 0];
         U32 idx1;
         U32 idx2 = base + mIndices[start + 1];
         U32 * nextIdx = &idx1;
         for ( S32 j = 2; j < draw.numElements; j++ )
         {
            *nextIdx = idx2;
            //            nextIdx = (j%2)==0 ? &idx0 : &idx1;
            nextIdx = (U32*) ( (dsize_t)nextIdx ^ (dsize_t)&idx0 ^ (dsize_t)&idx1);
            idx2 = base + mIndices[start + j];
            if ( idx0 == idx1 || idx0 == idx2 || idx1 == idx2 )
               continue;

            curIts->mVRef[2] = idx0;
            curIts->mVRef[1] = idx1;
            curIts->mVRef[0] = idx2;
            curIts->mMatIdx = matIndex;
            curIts++;
         }
      }
   }

   AssertFatal( (curIts - its) == mi->GetNbTriangles(), "Triangle count mismatch!" );

   for( S32 i = 0; i < mi->GetNbVertices(); i++ )
   {
      if( mVertexData.isReady() )
      {
         const __TSMeshVertexBase &vertData = mVertexData.getBase(i);
         pts[i].Set( vertData.vert().x, vertData.vert().y, vertData.vert().z );
      }
      else
      {
         pts[i].Set( mVerts[i].x, mVerts[i].y, mVerts[i].z );
      }
   }

   mi->SetPointers( its, pts );

   // Ok, we've got a mesh interface populated, now let's build a thingy to collide against.
   mOptTree = new Opcode::Model();

   Opcode::OPCODECREATE opcc;

   opcc.mCanRemap = true;
   opcc.mIMesh = mi;
   opcc.mKeepOriginal = false;
   opcc.mNoLeaf = false;
   opcc.mQuantized = false;
   opcc.mSettings.mLimit = 1;

   mOptTree->Build( opcc );
}

static Point3F	texGenAxis[18] =
{
   Point3F(0,0,1), Point3F(1,0,0), Point3F(0,-1,0),
   Point3F(0,0,-1), Point3F(1,0,0), Point3F(0,1,0),
   Point3F(1,0,0), Point3F(0,1,0), Point3F(0,0,1),
   Point3F(-1,0,0), Point3F(0,1,0), Point3F(0,0,-1),
   Point3F(0,1,0), Point3F(1,0,0), Point3F(0,0,1),
   Point3F(0,-1,0), Point3F(-1,0,0), Point3F(0,0,-1)
};


bool TSMesh::castRayOpcode( const Point3F &start, const Point3F &end, RayInfo *info, TSMaterialList *materials )
{
   Opcode::RayCollider ray;
   Opcode::CollisionFaces cfs;

   IceMaths::Point dir(end.x - start.x, end.y - start.y, end.z - start.z );
   const F32 rayLen = dir.Magnitude();
   IceMaths::Ray vec( Point(start.x, start.y, start.z), dir.Normalize() );

   ray.SetDestination( &cfs);
   ray.SetFirstContact( false );
   ray.SetClosestHit( true );
   ray.SetPrimitiveTests( true );
   ray.SetCulling( true );
   ray.SetMaxDist( rayLen );

   AssertFatal( ray.ValidateSettings() == NULL, "invalid ray settings" );

   // Do collision.
   bool safety = ray.Collide( vec, *mOptTree );
   AssertFatal( safety, "TSMesh::castRayOpcode - no good ray collide!" );

   // If no hit, just skip out.
   if( cfs.GetNbFaces() == 0 )
      return false;

   // Got a hit!
   AssertFatal( cfs.GetNbFaces() == 1, "bad" );
   const Opcode::CollisionFace &face = cfs.GetFaces()[0];

   // If the cast was successful let's check if the t value is less than what we had
   // and toggle the collision boolean
   // Stupid t... i prefer coffee
   const F32 t = face.mDistance / rayLen;

   if( t < 0.0f || t > 1.0f )
      return false;

   if( t <= info->t )
   {
      info->t = t;

      // Calculate the normal.
      Opcode::VertexPointers vp;
      mOptTree->GetMeshInterface()->GetTriangle( vp, face.mFaceID );

      if ( materials && vp.MatIdx >= 0 && vp.MatIdx < materials->size() )
         info->material = materials->getMaterialInst( vp.MatIdx );

      // Get the two edges.
      IceMaths::Point baseVert = *vp.Vertex[0];
      IceMaths::Point a = *vp.Vertex[1] - baseVert;
      IceMaths::Point b = *vp.Vertex[2] - baseVert;

      IceMaths::Point n;
      n.Cross( a, b );
      n.Normalize();

      info->normal.set( n.x, n.y, n.z );

      // generate UV coordinate across mesh based on 
      // matching normals, this isn't done by default and is 
      // primarily of interest in matching a collision point to 
      // either a GUI control coordinate or finding a hit pixel in texture space
      if (info->generateTexCoord)
      {
         baseVert = *vp.Vertex[0];
         a = *vp.Vertex[1];
         b = *vp.Vertex[2];

         Point3F facePoint = (1.0f - face.mU - face.mV) * Point3F(baseVert.x, baseVert.y, baseVert.z)  
            + face.mU * Point3F(a.x, a.y, a.z) + face.mV * Point3F(b.x, b.y, b.z);

         U32 faces[1024];
         U32 numFaces = 0;
         for (U32 i = 0; i < mOptTree->GetMeshInterface()->GetNbTriangles(); i++)
         {
            if ( i == face.mFaceID )
            {
               faces[numFaces++] = i;
            }
            else
            {
               IceMaths::Point n2;

               mOptTree->GetMeshInterface()->GetTriangle( vp, i );

               baseVert = *vp.Vertex[0];
               a = *vp.Vertex[1] - baseVert;
               b = *vp.Vertex[2] - baseVert;
               n2.Cross( a, b );
               n2.Normalize();

               F32 eps = .01f;
               if ( mFabs(n.x - n2.x) < eps && mFabs(n.y - n2.y) < eps && mFabs(n.z - n2.z) < eps)
               {
                  faces[numFaces++] = i;
               }
            }

            if (numFaces == 1024)
            {
               // too many faces in this collision mesh for UV generation
               return true;
            }

         }

         Point3F min(F32_MAX, F32_MAX, F32_MAX);
         Point3F max(-F32_MAX, -F32_MAX, -F32_MAX);

         for (U32 i = 0; i < numFaces; i++)
         {
            mOptTree->GetMeshInterface()->GetTriangle( vp, faces[i] );

            for ( U32 j =0; j < 3; j++)
            {
               a = *vp.Vertex[j];

               if (a.x < min.x)
                  min.x = a.x;
               if (a.y < min.y)
                  min.y = a.y;
               if (a.z < min.z)
                  min.z = a.z;

               if (a.x > max.x)
                  max.x = a.x;
               if (a.y > max.y)
                  max.y = a.y;
               if (a.z > max.z)
                  max.z = a.z;

            }

         }

         // slerp
         Point3F divSafe = (max - min);
         if (divSafe.x == 0.0f) divSafe.x = POINT_EPSILON;
         if (divSafe.y == 0.0f) divSafe.y = POINT_EPSILON;
         if (divSafe.z == 0.0f) divSafe.z = POINT_EPSILON;

         Point3F s = ( (max - min) - (facePoint - min) ) / divSafe;

         // compute axis
         S32		bestAxis = 0;
         F32      best = 0.f;

         for (U32 i = 0 ; i < 6 ; i++)
         {
            F32 dot = mDot (info->normal, texGenAxis[i*3]);
            if (dot > best)
            {
               best = dot;
               bestAxis = i;
            }
         }

         Point3F xv = texGenAxis[bestAxis*3+1];
         Point3F yv = texGenAxis[bestAxis*3+2];

         S32 sv, tv;

         if (xv.x)
            sv = 0;
         else if (xv.y)
            sv = 1;
         else
            sv = 2;

         if (yv.x)
            tv = 0;
         else if (yv.y)
            tv = 1;
         else
            tv = 2;

         // handle coord translation
         if (bestAxis == 2 || bestAxis == 3)
         {
            S32 x = sv;
            sv = tv;
            tv = x;

            if (yv.z < 0)
               s[sv] = 1.f - s[sv];
         }

         if (bestAxis < 2)
         {
            if (yv.y < 0)
               s[sv] = 1.f - s[sv];
         }

         if (bestAxis > 3)
         {
            s[sv] = 1.f - s[sv];
            if (yv.z > 0)
               s[tv] = 1.f - s[tv];

         }

         // done!
         info->texCoord.set(s[sv], s[tv]);

      }

      return true;
   }

   return false;
}
