Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

402 lines
12 KiB

//========= Copyright c 1996-2008, Valve Corporation, All rights reserved. ============//
//
// Purpose: Builds physics2 collision models from studio model source
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//=============================================================================//
#include <string.h>
#include "tier1/tier1.h"
#include "tier1/smartptr.h"
#include "tier2/p4helpers.h"
#include "datalinker.h"
#include "vphysics2_interface.h"
#include "vphysics2_interface_flags.h"
#include "alignedarray.h"
#include "studiomdl.h"
#include "filesystem_tools.h"
#include "collisionmodelsource.h"
#include "physics2collision.h"
#include "physdll.h"
#include "phzfile.h"
static IPhysics2Cook *g_pCook;
struct bodypart_t
{
IPhysics2CookedMeshBase* mesh;
int bone;
bodypart_t(){}
bodypart_t(IPhysics2CookedMeshBase* _mesh, int _bone):mesh(_mesh),bone(_bone){}
};
class CPhysics2CollisionBuilder: public CCollisionModelSource
{
public:
void Init(CCollisionModelSource *pSource)
{
*static_cast<CCollisionModelSource*>(this) = *pSource;
}
void Shutdown()
{
Destroy(m_bodyparts);
}
void Destroy(CUtlVector<bodypart_t>&bodyparts)
{
for(int i = 0; i < bodyparts.Size(); ++i)
g_pCook->Destroy(bodyparts[i].mesh);
}
void Build()
{
if(m_isJointed)
BuildJointed();
else
BuildRigid();
}
void BuildRigid();
void BuildJointed();
void Write();
void Destroy(CUtlVector<IPhysics2CookedMeshBase*> &arrPolytopes);
CUtlVector<bodypart_t> m_bodyparts;
};
class CMeshAdaptor:public CPhysics2CustomMeshBase
{
public:
virtual uint GetType()const {return PHYSICS2_SHAPE_TYPE_CUSTOM;}
virtual uint NumVertices() const {return m_pMesh->numvertices;}
virtual uint NumTriangles() const {return m_pMesh->numfaces;}
virtual void GetVertices(float *pVertsOut, uint nByteStride, const fltx4 &factor = Four_Ones)
{
uint numVerts = m_pMesh->numvertices;
byte *pOut = (byte*)pVertsOut;
for(uint i =0; i< numVerts; ++i)
{
fltx4 vert = MulSIMD(LoadUnaligned3SIMD(&m_pVerts[i+m_pMesh->vertexoffset].x), factor);
StoreUnaligned3SIMD((float*)pOut, vert);
pOut += nByteStride;
}
}
virtual void GetTriangles(int *pTrisOut, uint nByteStride)
{
uint numTris = m_pMesh->numfaces;
byte *pOut = (byte*)pTrisOut;
for(uint i = 0;i < numTris; ++i)
{
const s_face_t *pFace = m_pFaces + i + m_pMesh->faceoffset;
Assert(pFace->a+m_pMesh->vertexoffset < (uint)m_pMesh->numvertices && pFace->b+m_pMesh->vertexoffset < (uint)m_pMesh->numvertices && pFace->c+m_pMesh->vertexoffset < (uint)m_pMesh->numvertices
);
((int*)pOut)[0] = pFace->a;
((int*)pOut)[1] = pFace->b;
((int*)pOut)[2] = pFace->c;
pOut += nByteStride;
}
}
virtual uint GetSizeOf()const {return sizeof(*this);}
const s_face_t *m_pFaces;// non-offset faces
const Vector *m_pVerts; // non-offset verts
const s_mesh_t *m_pMesh;
};
static CPhysics2CollisionBuilder g_builder;
void Physics2Collision_Build(CCollisionModelSource *pSource)
{
g_pCook = g_pPhysics2->GetCook();
g_builder.Init(pSource);
g_builder.Build();
}
void Physics2Collision_Write()
{
g_builder.Write();
}
void CPhysics2CollisionBuilder::BuildJointed()
{
// first, go through all meshes and determine what bones they belong to
//CUtlVector<CUtlVector<int> > arrMeshBones(0,m_pModel->nummeshes);
// remap it : bone -> faces
CUtlVector<CUtlVector<s_face_t> > arrBoneFaces;
arrBoneFaces.SetSize(m_pModel->numbones);
// Constructing elements of the array. This is irritating, there should be a method to do that..
for(int i =0; i < m_pModel->numbones; ++i)
new(&arrBoneFaces[i])CUtlVector<s_mesh_t *>(32);
//for(int i = 0; i < m_pModel->nummeshes; ++i)
// new(&arrMeshBones[i])CUtlVector<int>();
// for each mesh, find bone(s) it belongs to and push it to that bone (those bones)
for(int nMesh = 0; nMesh < m_pModel->nummeshes; ++nMesh)
{
s_mesh_t *pMesh = m_pModel->mesh + m_pModel->meshindex[nMesh];
for(int nFace = 0; nFace < pMesh->numfaces; ++nFace)
{
s_face_t face = GetGlobalFace(pMesh, nFace);
s_boneweight_t &boneweight = m_pModel->vertex[face.a].boneweight;
if(boneweight.numbones)
{
int boneIndex = RemapBone(boneweight.bone[0]);
if(boneIndex >= 0 && boneIndex < m_pModel->numbones)
arrBoneFaces[boneIndex].AddToTail(face);
}
}
}
// for each bone, we have 0..many meshes now; compile the meshes; we don't try to share the meshes between different bones here,
// the idea is that we'll have rigid binding to skeleton, possibly sometimes multiple meshes to the same bone, but not the same mesh
// to multiple bones
CUtlVector<Vector> bonespaceVerts;
bonespaceVerts.SetCount(m_pModel->numvertices);
for(int nBone = 0; nBone < m_pModel->numbones; ++nBone)
{
CUtlVector<IPhysics2CookedMeshBase*> arrPolytopes;
bodypart_t bodypart;
bodypart.bone = nBone;
bodypart.mesh = NULL;
CUtlVector<s_face_t> &arrFaces = arrBoneFaces[nBone];
if(ShouldProcessBone(nBone) && arrFaces.Size())
{
// convert ALL vertices into this bone's frame (it's easier)
ConvertToBoneSpace(nBone, bonespaceVerts);
// cook one polytope for each s_mesh_t (out of the Mesh interface)
s_mesh_t mesh;
mesh.faceoffset = 0;
mesh.numfaces = arrFaces.Size();
mesh.vertexoffset = 0;
mesh.numvertices = bonespaceVerts.Size();
CMeshAdaptor adaptor;
adaptor.m_pMesh = &mesh;
adaptor.m_pFaces = arrFaces.Base();
adaptor.m_pVerts = bonespaceVerts.Base();
if(IPhysics2CookedPolytope *pCookedPolytope = g_pCook->CookPolytope(&adaptor))
arrPolytopes.AddToTail(pCookedPolytope);
}
if(arrPolytopes.Size() > 1)
{
if(m_allowConcaveJoints)
{
bodypart.mesh = g_pCook->CookMopp(arrPolytopes.Base(), arrPolytopes.Size());
}
else
{
bodypart.mesh = g_pCook->CookPolytopeFromMeshes(arrPolytopes.Base(), arrPolytopes.Size());
}
Destroy(arrPolytopes);
}
else
if(arrPolytopes.Size() == 1)
{
bodypart.mesh = arrPolytopes[0];
}
if(bodypart.mesh)
m_bodyparts.AddToTail(bodypart);
}
}
void CPhysics2CollisionBuilder::BuildRigid()
{
CUtlVector<Vector> worldspaceVerts;
worldspaceVerts.SetCount(m_pModel->numvertices);
ConvertToWorldSpace( worldspaceVerts );
m_bodyparts.SetSize(0);
bool bValid = true;
if ( m_allowConcave )
{
CUtlVector<CMeshAdaptor> arrMeshes;
int numMeshes = m_pModel->nummeshes;
arrMeshes.SetCount(numMeshes);
for ( int i = 0; i < numMeshes; i++ )
{
s_mesh_t *pMesh = m_pModel->mesh + m_pModel->meshindex[i];
arrMeshes[i].m_pFaces = m_pModel->face;
arrMeshes[i].m_pVerts = worldspaceVerts.Base();//m_pModel->vertex;
arrMeshes[i].m_pMesh = pMesh;
}
// this is one way to do it: make one polysoup
//g_pCook->CookPolysoupFromMeshes(arrMeshes.Base(), numMeshes);
// another way is to create a bunch of convex polytopes
for ( int i = 0; i < numMeshes; i++ )
{
IPhysics2CookedPolytope *polytope = g_pCook->CookPolytope(&arrMeshes[i]);
if(polytope)
{
m_bodyparts.AddToTail(bodypart_t(polytope, -1));
}
}
}
if ( m_bodyparts.Count() > m_maxConvex )
{
MdlWarning("COSTLY COLLISION MODEL!!!! (%d parts - %d allowed)\n", m_bodyparts.Count(), m_maxConvex );
bValid = false;
}
if ( !bValid && m_bodyparts.Count() )
{
MdlWarning("Error with convex elements of %s, building single convex!!!!\n", m_pModel->filename );
Destroy(m_bodyparts);
}
// either we don't want concave, or there was an error building it
if ( !m_bodyparts.Count() )
{
CUtlVector_Vector4DAligned arrVerts;
arrVerts.SetSize(worldspaceVerts.Count());
for(int i = 0;i < worldspaceVerts.Count(); ++i)
{
const Vector &v = worldspaceVerts[i];
arrVerts[i].Init(v.x,v.y,v.z);
}
IPhysics2CookedPolytope *polytope = g_pCook->CookPolytopeFromVertices((Vector4DAligned*)arrVerts.Base(), worldspaceVerts.Count());
m_bodyparts.AddToTail(bodypart_t(polytope,-1));
}
if(m_bodyparts.Size() > 1)
{
// fold it into one single neat mesh
CUtlVector<IPhysics2CookedMeshBase*>arrMeshes(m_bodyparts.Size(),m_bodyparts.Size());
for(int i = 0;i < m_bodyparts.Size(); ++i)
arrMeshes[i] = m_bodyparts[i].mesh;
IPhysics2CookedMopp *mopp = g_pCook->CookMopp(arrMeshes.Base(), m_bodyparts.Size());
Destroy(m_bodyparts);
if(mopp)
m_bodyparts.AddToTail(bodypart_t(mopp, -1));
}
}
void CPhysics2CollisionBuilder::Destroy(CUtlVector<IPhysics2CookedMeshBase*> &arrPolytopes)
{
for ( int i = 0; i < arrPolytopes.Count(); i++ )
g_pCook->Destroy( arrPolytopes[i] );
arrPolytopes.Purge();
}
void CPhysics2CollisionBuilder::Write()
{
char filename[512];
strcpy( filename, gamedir );
strcat( filename, "models/" );
strcat( filename, m_pOverrideName ? m_pOverrideName : outname );
Q_SetExtension( filename, ".phz", sizeof( filename ) );
if(!m_bodyparts.Size())
{
CPlainAutoPtr< CP4File > spFile( g_p4factory->AccessFile( filename ) );
unlink(filename);
return;
}
DataLinker::Stream stream;
Physics2CollisionHeader_t *pHeader = stream.Write<Physics2CollisionHeader_t>();
pHeader->m_dataVersion = g_pPhysics2->GetSerializeVersion();
pHeader->m_numBones = m_bodyparts.Size();
//if(!m_pModel->numbones)
// pHeader->m_numBones = 1; // there's still 1 pseudo-bone there
Physics2RigidPolyShape_t *pRigids = stream.IStream::WriteAndLinkStrided(&pHeader->m_shapes, sizeof(Physics2RigidPolyShape_t), m_bodyparts.Count());
///
// Note: I want all inertia descriptors to reside together for cache coherency in dynamics phase, so I'm writing inertia first, then the shapes
///
for(int nBodyPart = 0; nBodyPart < m_bodyparts.Size(); ++nBodyPart)
{
int boneIndex = m_bodyparts[nBodyPart].bone;
IPhysics2CookedMeshBase *pMesh = m_bodyparts[nBodyPart].mesh;
const char *boneName = boneIndex < 0 ? "" : m_pModel->localBone[boneIndex].name;
// we'll leave all offsets to NULL if there's no mesh for that bone
if(pMesh)
{
IPhysics2CookedInertia *pInertia = g_pCook->CookInertia(pMesh->GetShape()); // the inertia of the model as a rigid whole
if(pInertia)
{
stream.IStream::Link(&pRigids[nBodyPart].m_inertia, pInertia->Serialize(&stream));
g_pCook->Destroy(pInertia);
}
else
Warning("Could not cook inertia for '%s' #d\n", boneName, boneIndex);
}
pRigids[nBodyPart].m_localBoneIndex = boneIndex;
if(boneIndex >= 0)
{
int globalBoneIndex = m_pModel->boneLocalToGlobal[boneIndex];
pRigids[nBodyPart].m_globalBoneIndex = globalBoneIndex;
}
}
for(int nBodyPart = 0; nBodyPart < m_bodyparts.Size(); ++nBodyPart)
{
bodypart_t &bp = m_bodyparts[nBodyPart];
// we'll leave all offsets to NULL if there's no mesh for that bone
pRigids[nBodyPart].m_shapeType = bp.mesh->GetType();
stream.IStream::Link(&pRigids[nBodyPart].m_shape, bp.mesh->Serialize(&stream));
}
*(char*)stream.WriteBytes(1) = '\n'; // for debugging
for(int nBodyPart = 0; nBodyPart < m_bodyparts.Size(); ++nBodyPart)
{
bodypart_t &bp = m_bodyparts[nBodyPart];
if(bp.bone >= 0)
{
const char *name = m_pModel->localBone[bp.bone].name;
if(name)
{
int nameLen = strlen(name);
char *pNameOut = (char*)stream.WriteBytes(nameLen + 2);
stream.IStream::Link(&pRigids[nBodyPart].m_name, pNameOut);
memcpy(pNameOut, name, nameLen+1);
pNameOut[nameLen+1] = '\n'; // for debugging
}
}
}
uint nDataSize = stream.GetTotalSize();
void *pData = MemAlloc_AllocAligned(nDataSize, 16, __FILE__, __LINE__);
if(stream.Compile(pData))
{
CPlainAutoPtr< CP4File > spFile( g_p4factory->AccessFile( filename ) );
spFile->Edit();
FILE *fp = fopen( filename, "wb" );
if(fp)
{
int numWritten = fwrite(pData, nDataSize, 1, fp);
fclose(fp);
}
else
{
MdlWarning("Error writing %s!!!\n", filename );
}
}
else
{
MdlWarning("Cannot compile the phz data\n");
}
MemAlloc_FreeAligned(pData, __FILE__, __LINE__);
}