403 lines
12 KiB
C++
403 lines
12 KiB
C++
//========= 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__);
|
|
}
|
|
|