//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Virtual mesh implementation. Cached terrain collision model // //============================================================================= #include "cbase.h" #include "convert.h" #include "ivp_surface_manager.hxx" #include "ivp_surman_polygon.hxx" #include "ivp_template_surbuild.hxx" #include "ivp_compact_surface.hxx" #include #include #include #include "ivp_surbuild_pointsoup.hxx" #include "ivp_surbuild_ledge_soup.hxx" #include "physics_trace.h" #include "collisionutils.h" #include "datamanager.h" #include "utlbuffer.h" #include "ledgewriter.h" #include "tier1/mempool.h" #include "tier0/memdbgon.h" class CPhysCollideVirtualMesh; CTSPool< CUtlVector > g_MeshFrameLocksPool; CThreadLocalPtr< CUtlVector > g_pMeshFrameLocks; // This is the surfacemanager class for IVP that implements the required functions by layering CPhysCollideVirtualMesh class IVP_SurfaceManager_VirtualMesh : public IVP_SurfaceManager { public: void add_reference_to_ledge(const IVP_Compact_Ledge *ledge); void remove_reference_to_ledge(const IVP_Compact_Ledge *ledge); void insert_all_ledges_hitting_ray(IVP_Ray_Solver *ray_solver, IVP_Real_Object *object); void get_radius_and_radius_dev_to_given_center(const IVP_U_Float_Point *center, IVP_FLOAT *radius, IVP_FLOAT *radius_deviation) const; virtual IVP_SURMAN_TYPE get_type() { return IVP_SURMAN_POLYGON; } // assume mesh is never a single triangle virtual const IVP_Compact_Ledge *get_single_convex() const; void get_mass_center(IVP_U_Float_Point *mass_center_out) const; void get_rotation_inertia( IVP_U_Float_Point *rotation_inertia_out ) const; void get_all_ledges_within_radius(const IVP_U_Point *observer_os, IVP_DOUBLE radius, const IVP_Compact_Ledge *root_ledge, IVP_Real_Object *other_object, const IVP_Compact_Ledge *other_reference_ledge, IVP_U_BigVector *resulting_ledges); void get_all_terminal_ledges(IVP_U_BigVector *resulting_ledges); IVP_SurfaceManager_VirtualMesh( CPhysCollideVirtualMesh *pMesh ); virtual ~IVP_SurfaceManager_VirtualMesh(); private: CPhysCollideVirtualMesh *m_pMesh; }; // These are the managed objects for the LRU of terrain collisions // These get created/destroyed dynamically by a resourcemanager // These contain the uncompressed collision models for each displacement patch // The idea is to have only the necessary instances of these in memory at any given time - never all of them class CMeshInstance { public: // resourcemanager static unsigned int EstimatedSize( const virtualmeshlist_t &list ); static CMeshInstance *CreateResource( const virtualmeshlist_t &list ); static unsigned int ComputeRootLedgeSize( const byte *pHull ); void DestroyResource() { delete this; } unsigned int Size() { return m_memSize; } CMeshInstance *GetData() { return this; } const triangleledge_t *GetLedges() { return (triangleledge_t *)m_pMemory; } inline int HullCount() { return m_hullCount; } const IVP_Compact_Ledge *GetOuterHull() { return (m_hullCount==1) ? (const IVP_Compact_Ledge *)(m_pMemory + m_hullOffset) : NULL; } int GetRootLedges( IVP_Compact_Ledge **pLedges, int outCount ) { int hullOffset = m_hullOffset; int count = min(outCount, (int)m_hullCount); for ( int i = 0; i < count; i++ ) { pLedges[i] = (IVP_Compact_Ledge *)(m_pMemory + hullOffset); hullOffset += sizeof(IVP_Compact_Ledge) + (sizeof(IVP_Compact_Triangle) * pLedges[i]->get_n_triangles()); } return count; } // locals CMeshInstance() { m_pMemory = 0; } ~CMeshInstance(); private: void Init( const virtualmeshlist_t &list ); int m_memSize; char *m_pMemory; unsigned short m_hullOffset; byte m_hullCount; byte m_pad; }; CMeshInstance::~CMeshInstance() { if ( m_pMemory ) { ivp_free_aligned( m_pMemory ); m_pMemory = NULL; } } unsigned int CMeshInstance::EstimatedSize( const virtualmeshlist_t &list ) { int ledgeSize = sizeof(triangleledge_t) * list.triangleCount; int pointSize = sizeof(IVP_Compact_Poly_Point) * list.vertexCount; int hullSize = ComputeRootLedgeSize(list.pHull); return ledgeSize + pointSize + hullSize; } // computes the unpacked size of the array of root ledges unsigned int CMeshInstance::ComputeRootLedgeSize( const byte *pData ) { if ( !pData ) return 0; virtualmeshhull_t *pHeader = (virtualmeshhull_t *)pData; packedhull_t *pHull = (packedhull_t *)(pHeader+1); unsigned int size = pHeader->hullCount * sizeof(IVP_Compact_Ledge); for ( int i = 0; i < pHeader->hullCount; i++ ) { size += sizeof(IVP_Compact_Triangle) * pHull[i].triangleCount; } return size; } CMeshInstance *CMeshInstance::CreateResource( const virtualmeshlist_t &list ) { CMeshInstance *pMesh = new CMeshInstance; pMesh->Init( list ); return pMesh; } // flat memory footprint has triangleledges (ledge + 2 triangles for terrain), then has verts, then optional convex hull void CMeshInstance::Init( const virtualmeshlist_t &list ) { int ledgeSize = sizeof(triangleledge_t) * list.triangleCount; int pointSize = sizeof(IVP_Compact_Poly_Point) * list.vertexCount; int memSize = ledgeSize + pointSize + ComputeRootLedgeSize(list.pHull); m_memSize = memSize; m_hullCount = 0; m_pMemory = (char *)ivp_malloc_aligned( memSize, 16 ); Assert( (int(m_pMemory) & 15) == 0 ); // make sure it is aligned IVP_Compact_Poly_Point *pPoints = (IVP_Compact_Poly_Point *)&m_pMemory[ledgeSize]; triangleledge_t *pLedges = (triangleledge_t *) m_pMemory; memset( m_pMemory, 0, memSize ); int i; for ( i = 0; i < list.vertexCount; i++ ) { ConvertPositionToIVP( list.pVerts[i], pPoints[i] ); } for ( i = 0; i < list.triangleCount; i++ ) { Vector v0 = list.pVerts[list.indices[i*3+0]]; Vector v1 = list.pVerts[list.indices[i*3+1]]; Vector v2 = list.pVerts[list.indices[i*3+2]]; Assert( v0 != v1 && v1 != v2 && v0 != v2 ); CVPhysicsVirtualMeshWriter::InitTwoSidedTriangleLege( &pLedges[i], pPoints, list.indices[i*3+0], list.indices[i*3+1], list.indices[i*3+2], 0 ); } Assert( list.triangleCount > 0 && list.triangleCount <= MAX_VIRTUAL_TRIANGLES ); // if there's a hull, build it out too if ( list.pHull ) { virtualmeshhull_t *pHeader = (virtualmeshhull_t *)list.pHull; m_hullCount = pHeader->hullCount; Assert( (ledgeSize + pointSize) < 65536 ); m_hullOffset = ledgeSize + pointSize; byte *pMem = (byte *)m_pMemory + m_hullOffset; #if _DEBUG int hullSize = CVPhysicsVirtualMeshWriter::UnpackLedgeListFromHull( pMem, pHeader, pPoints ); Assert((m_hullOffset+hullSize)==memSize); #else CVPhysicsVirtualMeshWriter::UnpackLedgeListFromHull( pMem, pHeader, pPoints ); #endif } } // UNDONE: Tune / expose this constant 512K budget for terrain collision const int g_MeshSize = (2048 * 1024); static CDataManager g_MeshManager( g_MeshSize ); static int numIndices = 0, numTriangles = 0, numBaseTriangles = 0, numSplits = 0; //----------------------------------------------------------------------------- // Purpose: This allows for just-in-time procedural triangle soup data to be // instanced & cached as IVP collision data (compact ledges) //----------------------------------------------------------------------------- // NOTE: This is the permanent in-memory representation. It holds the compressed data // and the parameters necessary to request the proxy geometry as needed class CPhysCollideVirtualMesh : public CPhysCollide { public: // UNDONE: Unlike other CPhysCollide objects, operations the virtual mesh are // non-const because they may instantiate the cache. This causes problems with the interface. // Maybe the cache stuff should be mutable, but it amounts to the same kind of // hackery to cast away const. // get a surface manager virtual IVP_SurfaceManager *CreateSurfaceManager( short &collideType ) const { collideType = COLLIDE_VIRTUAL; // UNDONE: Figure out how to avoid this const_cast return new IVP_SurfaceManager_VirtualMesh(const_cast(this)); } virtual void GetAllLedges( IVP_U_BigVector &ledges ) const { const triangleledge_t *pLedges = const_cast(this)->AddRef()->GetLedges(); for ( int i = 0; i < m_ledgeCount; i++ ) { ledges.add( const_cast(&pLedges[i].ledge) ); } const_cast(this)->Release(); } virtual unsigned int GetSerializationSize() const { if ( !m_pHull ) return 0; return m_pHull->TotalSize(); } virtual unsigned int SerializeToBuffer( char *pDest, bool bSwap = false ) const { unsigned int size = GetSerializationSize(); if ( size ) { memcpy( pDest, m_pHull, size ); } return size; } virtual int GetVCollideIndex() const { return 0; } virtual void SetMassCenter( const Vector &massCenter ) {Assert(0); } virtual Vector GetOrthographicAreas() const { return Vector(1,1,1);} Vector GetMassCenter() const; virtual float GetSphereRadius() const; float GetSphereRadiusIVP() const; void Init( const char *pBuffer, unsigned int size ) { } void GetAllLedgesWithinRadius( const IVP_U_Point *observer_os, IVP_DOUBLE radius, IVP_U_BigVector *resulting_ledges, const IVP_Compact_Ledge *pRootLedge = NULL ) { virtualmeshtrianglelist_t list; list.triangleCount = 0; Vector centerHL; ConvertPositionToHL( *observer_os, centerHL ); float radiusHL = ConvertDistanceToHL(radius); m_params.pMeshEventHandler->GetTrianglesInSphere( m_params.userData, centerHL, radiusHL, &list ); if ( list.triangleCount ) { CMeshInstance *pMesh = AddRef(); const triangleledge_t *pLedges = pMesh->GetLedges(); FrameRelease(); // If we have two root ledges, then each one contains half the triangles // only return triangles indexed under the root ledge being queried int minTriangle = 0; int maxTriangle = m_ledgeCount; if ( pMesh->HullCount() > 1 ) { Assert(pMesh->HullCount()==2); IVP_Compact_Ledge *pRootNodes[2]; pMesh->GetRootLedges( pRootNodes, 2 ); int midTriangle = m_ledgeCount/2; if ( pRootLedge == pRootNodes[0] ) { maxTriangle = midTriangle; } else { minTriangle = midTriangle; } } IVP_DOUBLE radiusSq = radius * radius; for ( int i = 0; i < list.triangleCount; i++ ) { Assert( list.triangleIndices[i] < m_ledgeCount ); if ( list.triangleIndices[i] < minTriangle || list.triangleIndices[i] >= maxTriangle ) continue; const IVP_Compact_Ledge *ledge = &pLedges[list.triangleIndices[i]].ledge; Assert(ledge->get_n_triangles() == 2); const IVP_Compact_Triangle *triangle = ledge->get_first_triangle(); IVP_DOUBLE qdist = IVP_CLS.calc_qlen_PF_F_space(ledge, triangle, observer_os); if ( qdist > radiusSq ) { continue; } resulting_ledges->add( const_cast(ledge) ); } } } virtual void OutputDebugInfo() const { Msg("Virtual mesh!\n"); } CPhysCollideVirtualMesh(const virtualmeshparams_t ¶ms) : m_params(params), m_hMemory( INVALID_MEMHANDLE ), m_ledgeCount( 0 ) { m_pHull = NULL; if ( params.buildOuterHull ) { BuildBoundingLedge(); } } virtual ~CPhysCollideVirtualMesh(); // adds a lock on the collsion memory :: MUST CALL Release() or FrameRelease corresponding to this call!!! CMeshInstance *AddRef(); void BuildBoundingLedge(); static virtualmeshhull_t *CreateMeshBoundingHull( const virtualmeshlist_t &list ); static void DestroyMeshBoundingHull(virtualmeshhull_t *pHull) { CVPhysicsVirtualMeshWriter::DestroyPackedHull(pHull); } static IVP_Compact_Surface *CreateBoundingSurfaceFromRange( const virtualmeshlist_t &list, int firstIndex, int indexCount ); int GetRootLedges( IVP_Compact_Ledge **pLedges, int outCount ) { int count = AddRef()->GetRootLedges(pLedges, outCount); FrameRelease(); return count; } IVP_Compact_Ledge *GetBoundingLedge() { IVP_Compact_Ledge *pLedge = const_cast(AddRef()->GetOuterHull()); FrameRelease(); return pLedge; } // releases a lock on the collision memory void Release(); // Analagous to Release, but happens at the end of the frame void FrameRelease() { CUtlVector *pLocks = g_pMeshFrameLocks; if ( !pLocks ) { g_pMeshFrameLocks = pLocks = g_MeshFrameLocksPool.GetObject(); Assert( pLocks ); } pLocks->AddToTail(this); } inline void GetBounds( Vector &mins, Vector &maxs ) const { m_params.pMeshEventHandler->GetWorldspaceBounds( m_params.userData, &mins, &maxs ); } private: CMeshInstance *BuildLedges(); virtualmeshparams_t m_params; virtualmeshhull_t *m_pHull; memhandle_t m_hMemory; short m_ledgeCount; }; static void FlushFrameLocks() { CUtlVector *pLocks = g_pMeshFrameLocks; if ( pLocks ) { for ( int i = 0; i < pLocks->Count(); i++ ) { Assert( (*pLocks)[i] ); (*pLocks)[i]->Release(); } pLocks->RemoveAll(); g_MeshFrameLocksPool.PutObject( g_pMeshFrameLocks ); g_pMeshFrameLocks = NULL; } } void VirtualMeshPSI() { FlushFrameLocks(); } Vector CPhysCollideVirtualMesh::GetMassCenter() const { Vector mins, maxs; GetBounds( mins, maxs ); return 0.5 * (mins + maxs); } float CPhysCollideVirtualMesh::GetSphereRadius() const { Vector mins, maxs; GetBounds( mins, maxs ); Vector point = 0.5 * (mins+maxs); return (maxs - point).Length(); } float CPhysCollideVirtualMesh::GetSphereRadiusIVP() const { return ConvertDistanceToIVP( GetSphereRadius() ); } static CThreadFastMutex s_BuildVirtualMeshMutex; CMeshInstance *CPhysCollideVirtualMesh::AddRef() { CMeshInstance *pMesh = g_MeshManager.LockResource( m_hMemory ); if ( !pMesh ) { s_BuildVirtualMeshMutex.Lock(); pMesh = g_MeshManager.LockResource( m_hMemory ); if ( !pMesh ) { pMesh = BuildLedges(); } s_BuildVirtualMeshMutex.Unlock(); } Assert( pMesh ); return pMesh; } void CPhysCollideVirtualMesh::Release() { g_MeshManager.UnlockResource( m_hMemory ); } CPhysCollideVirtualMesh::~CPhysCollideVirtualMesh() { CVPhysicsVirtualMeshWriter::DestroyPackedHull(m_pHull); g_MeshManager.DestroyResource( m_hMemory ); } CMeshInstance *CPhysCollideVirtualMesh::BuildLedges() { virtualmeshlist_t list; m_params.pMeshEventHandler->GetVirtualMesh( m_params.userData, &list ); if ( !list.pHull ) { list.pHull = (byte *)m_pHull; } if ( list.triangleCount ) { m_hMemory = g_MeshManager.CreateResource( list ); m_ledgeCount = list.triangleCount; CMeshInstance *pMesh = g_MeshManager.LockResource( m_hMemory ); return pMesh; } return NULL; } // build the outer ledge, split into two if necessary void CPhysCollideVirtualMesh::BuildBoundingLedge() { virtualmeshlist_t list; m_params.pMeshEventHandler->GetVirtualMesh( m_params.userData, &list ); m_pHull = CreateMeshBoundingHull(list); } virtualmeshhull_t *CPhysCollideVirtualMesh::CreateMeshBoundingHull( const virtualmeshlist_t &list ) { virtualmeshhull_t *pHull = NULL; if ( list.triangleCount ) { IVP_Compact_Surface *pSurface = CreateBoundingSurfaceFromRange( list, 0, list.indexCount ); if ( pSurface ) { const IVP_Compact_Ledge *pLedge = pSurface->get_compact_ledge_tree_root()->get_compact_hull(); if ( CVPhysicsVirtualMeshWriter::LedgeCanBePacked(pLedge, list) ) { pHull = CVPhysicsVirtualMeshWriter::CreatePackedHullFromLedges( list, &pLedge, 1 ); } else { // too big to pack to 8-bits, split in two IVP_Compact_Surface *pSurface0 = CreateBoundingSurfaceFromRange( list, 0, list.indexCount/2 ); IVP_Compact_Surface *pSurface1 = CreateBoundingSurfaceFromRange( list, list.indexCount/2, list.indexCount/2 ); const IVP_Compact_Ledge *pLedges[2] = {pSurface0->get_compact_ledge_tree_root()->get_compact_hull(), pSurface1->get_compact_ledge_tree_root()->get_compact_hull()}; pHull = CVPhysicsVirtualMeshWriter::CreatePackedHullFromLedges( list, pLedges, 2 ); ivp_free_aligned(pSurface0); ivp_free_aligned(pSurface1); } ivp_free_aligned(pSurface); } } return pHull; } IVP_Compact_Surface *CPhysCollideVirtualMesh::CreateBoundingSurfaceFromRange( const virtualmeshlist_t &list, int firstIndex, int indexCount ) { Assert( list.triangleCount ); IVP_U_Point triVerts[3]; IVP_U_Vector triList; IVP_SurfaceBuilder_Ledge_Soup builder; triList.add( &triVerts[0] ); triList.add( &triVerts[1] ); triList.add( &triVerts[2] ); int lastIndex = firstIndex + indexCount; int firstTriangle = firstIndex/3; int lastTriangle = lastIndex/3; for ( int i = firstTriangle; i < lastTriangle; i++ ) { ConvertPositionToIVP( list.pVerts[list.indices[i*3+0]], triVerts[0] ); ConvertPositionToIVP( list.pVerts[list.indices[i*3+1]], triVerts[1] ); ConvertPositionToIVP( list.pVerts[list.indices[i*3+2]], triVerts[2] ); IVP_Compact_Ledge *pLedge = IVP_SurfaceBuilder_Pointsoup::convert_pointsoup_to_compact_ledge( &triList ); builder.insert_ledge( pLedge ); } // build a convex hull of those verts IVP_Template_Surbuild_LedgeSoup params; params.build_root_convex_hull = IVP_TRUE; IVP_Compact_Surface *pSurface = builder.compile( ¶ms ); #if _DEBUG const IVP_Compact_Ledgetree_Node *node = pSurface->get_compact_ledge_tree_root(); IVP_Compact_Ledge *pLedge = const_cast(node->get_compact_hull()); // we're going to write into client data on each vert before we throw this away Assert(pLedge && !pLedge->is_terminal()); #endif return pSurface; } CPhysCollide *CreateVirtualMesh( const virtualmeshparams_t ¶ms ) { return new CPhysCollideVirtualMesh(params); } void DestroyVirtualMesh( CPhysCollide *pMesh ) { FlushFrameLocks(); delete pMesh; } //----------------------------------------------------------------------------- // IVP_SurfaceManager_VirtualMesh // This hooks the underlying collision model to IVP's surfacemanager interface //----------------------------------------------------------------------------- IVP_SurfaceManager_VirtualMesh::IVP_SurfaceManager_VirtualMesh( CPhysCollideVirtualMesh *pMesh ) : m_pMesh(pMesh) { } IVP_SurfaceManager_VirtualMesh::~IVP_SurfaceManager_VirtualMesh() { } void IVP_SurfaceManager_VirtualMesh::add_reference_to_ledge(const IVP_Compact_Ledge *ledge) { m_pMesh->AddRef(); } void IVP_SurfaceManager_VirtualMesh::remove_reference_to_ledge(const IVP_Compact_Ledge *ledge) { m_pMesh->Release(); } // Implement the IVP raycast. This is done by testing each triangle (front & back) - so it's slow void IVP_SurfaceManager_VirtualMesh::insert_all_ledges_hitting_ray(IVP_Ray_Solver *ray_solver, IVP_Real_Object *object) { IVP_Vector_of_Ledges_256 ledges; IVP_Ray_Solver_Os ray_solver_os( ray_solver, object); IVP_U_Point center(&ray_solver_os.ray_center_point); m_pMesh->GetAllLedgesWithinRadius( ¢er, ray_solver_os.ray_length * 0.5f, &ledges ); for (int i=ledges.len()-1;i>=0;i--) { const IVP_Compact_Ledge *l = ledges.element_at(i); ray_solver_os.check_ray_against_compact_ledge_os(l); } } // Used to predict collision detection needs void IVP_SurfaceManager_VirtualMesh::get_radius_and_radius_dev_to_given_center(const IVP_U_Float_Point *center, IVP_FLOAT *radius, IVP_FLOAT *radius_deviation) const { // UNDONE: Check radius_deviation to see if there is a useful optimization to be made here *radius = m_pMesh->GetSphereRadiusIVP(); *radius_deviation = *radius; } // get a single convex if appropriate const IVP_Compact_Ledge *IVP_SurfaceManager_VirtualMesh::get_single_convex() const { return m_pMesh->GetBoundingLedge(); } // get a mass center for objects using this collision rep void IVP_SurfaceManager_VirtualMesh::get_mass_center(IVP_U_Float_Point *mass_center_out) const { Vector center = m_pMesh->GetMassCenter(); ConvertPositionToIVP( center, *mass_center_out ); } //----------------------------------------------------------------------------- // Purpose: Compute a diagonalized inertia tensor. //----------------------------------------------------------------------------- void IVP_SurfaceManager_VirtualMesh::get_rotation_inertia( IVP_U_Float_Point *rotation_inertia_out ) const { // HACKHACK: No need for this because we only support static objects for now rotation_inertia_out->set(1,1,1); } //----------------------------------------------------------------------------- // Purpose: Query ledges (triangles in this case) in sphere //----------------------------------------------------------------------------- void IVP_SurfaceManager_VirtualMesh::get_all_ledges_within_radius(const IVP_U_Point *observer_os, IVP_DOUBLE radius, const IVP_Compact_Ledge *root_ledge, IVP_Real_Object *other_object, const IVP_Compact_Ledge *other_reference_ledge, IVP_U_BigVector *resulting_ledges) { if ( !root_ledge ) { IVP_Compact_Ledge *pLedges[2]; int count = m_pMesh->GetRootLedges( pLedges, ARRAYSIZE(pLedges) ); if ( count ) { for ( int i = 0; i < count; i++ ) { resulting_ledges->add( pLedges[i] ); // return the recursive/virtual outer hull } return; } } m_pMesh->GetAllLedgesWithinRadius( observer_os, radius, resulting_ledges, root_ledge ); } //----------------------------------------------------------------------------- // Purpose: Query all of the ledges (triangles) //----------------------------------------------------------------------------- void IVP_SurfaceManager_VirtualMesh::get_all_terminal_ledges(IVP_U_BigVector *resulting_ledges) { m_pMesh->GetAllLedges( *resulting_ledges ); }