summaryrefslogtreecommitdiff
path: root/movieobjects/dmetestmesh.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'movieobjects/dmetestmesh.cpp')
-rw-r--r--movieobjects/dmetestmesh.cpp1757
1 files changed, 1757 insertions, 0 deletions
diff --git a/movieobjects/dmetestmesh.cpp b/movieobjects/dmetestmesh.cpp
new file mode 100644
index 0000000..f3e57af
--- /dev/null
+++ b/movieobjects/dmetestmesh.cpp
@@ -0,0 +1,1757 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+#include "movieobjects/dmetestmesh.h"
+#include "movieobjects/dmetransform.h"
+#include "movieobjects_interfaces.h"
+
+#include "tier0/dbg.h"
+#include "datamodel/dmelementfactoryhelper.h"
+#include "mathlib/vector.h"
+#include "materialsystem/imaterialsystem.h"
+#include "materialsystem/imesh.h"
+#include "datacache/imdlcache.h"
+#include "istudiorender.h"
+#include "studio.h"
+#include "bone_setup.h"
+#include "materialsystem/ivertextexture.h"
+#include "morphdata.h"
+#include "tier3/tier3.h"
+
+#include <strstream>
+#include <fstream>
+#include <algorithm>
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+// Expose this class to the scene database
+//-----------------------------------------------------------------------------
+IMPLEMENT_ELEMENT_FACTORY( DmeTestMesh, CDmeTestMesh );
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::OnConstruction()
+{
+ m_MDLHandle = MDLHANDLE_INVALID;
+ m_pMaterial = NULL;
+ m_pMesh = NULL;
+ m_pMorph = NULL;
+ m_pControlCage = NULL;
+ SetValue( "transform", g_pDataModel->IsUnserializing() ? NULL : CreateElement< CDmeTransform >( "transform", GetFileId() ) );
+ SetValue( "mdlfilename", "models/alyx.mdl" );
+ SetValue( "morphfilename", "models/alyx.morph" );
+ SetValue( "skin", 0 );
+ SetValue( "body", 0 );
+ SetValue( "sequence", 0 );
+ SetValue( "lod", 0 );
+ SetValue( "playbackrate", 1.0f );
+ SetValue( "time", 0.0f );
+ SetValue( "subdivlevel", 1 );
+}
+
+void CDmeTestMesh::OnDestruction()
+{
+ UnloadMorphData();
+ UnreferenceMDL();
+ DestroyControlCage();
+ DestroyMesh();
+}
+
+
+//-----------------------------------------------------------------------------
+// Addref/Release the MDL handle
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::ReferenceMDL( const char *pMDLName )
+{
+ if ( !g_pMDLCache )
+ return;
+
+ if ( pMDLName && pMDLName[0] )
+ {
+ Assert( m_MDLHandle == MDLHANDLE_INVALID );
+ m_MDLHandle = g_pMDLCache->FindMDL( pMDLName );
+ }
+}
+
+void CDmeTestMesh::UnreferenceMDL()
+{
+ if ( !g_pMDLCache )
+ return;
+
+ if ( m_MDLHandle != MDLHANDLE_INVALID )
+ {
+ g_pMDLCache->Release( m_MDLHandle );
+ m_MDLHandle = MDLHANDLE_INVALID;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Creates the mesh to draw
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::CreateMesh()
+{
+ DestroyMesh();
+
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ m_pMaterial = g_pMaterialSystem->FindMaterial( "shadertest/vertextexturetest", NULL, false );
+ m_pMesh = pRenderContext->CreateStaticMesh( m_pMaterial, 0, "dmemesh" );
+
+ CMeshBuilder meshBuilder;
+ meshBuilder.Begin( m_pMesh, MATERIAL_TRIANGLES, 8, 36 );
+
+ // Draw a simple cube
+ static Vector s_pPositions[8] =
+ {
+ Vector( -10, -10, -10 ),
+ Vector( 10, -10, -10 ),
+ Vector( -10, 10, -10 ),
+ Vector( 10, 10, -10 ),
+ Vector( -10, -10, 10 ),
+ Vector( 10, -10, 10 ),
+ Vector( -10, 10, 10 ),
+ Vector( 10, 10, 10 ),
+ };
+
+ static Vector2D s_pTexCoords[8] =
+ {
+ Vector2D( 0, 0 ),
+ Vector2D( 0.5, 0 ),
+ Vector2D( 0, 0.5 ),
+ Vector2D( 0.5, 0.5 ),
+ Vector2D( 0.5, 0.5 ),
+ Vector2D( 1, 0.5 ),
+ Vector2D( 0.5, 1 ),
+ Vector2D( 1, 1 ),
+ };
+
+ static unsigned char s_pColor[8][3] =
+ {
+ { 255, 255, 255 },
+ { 0, 255, 255 },
+ { 255, 0, 255 },
+ { 255, 255, 0 },
+ { 255, 0, 0 },
+ { 0, 255, 0 },
+ { 0, 0, 255 },
+ { 0, 0, 0 },
+ };
+
+ static int s_pIndices[12][3] =
+ {
+ { 0, 1, 5 }, { 0, 5, 4 },
+ { 4, 5, 7 }, { 4, 7, 6 },
+ { 0, 4, 6 }, { 0, 6, 2 },
+ { 0, 2, 3 }, { 0, 3, 1 },
+ { 1, 3, 7 }, { 1, 7, 5 },
+ { 2, 6, 7 }, { 2, 7, 3 },
+ };
+
+ for ( int i = 0; i < 8; ++i )
+ {
+ meshBuilder.Position3fv( s_pPositions[ i ].Base() );
+ meshBuilder.TexCoord2fv( 0, s_pTexCoords[ i ].Base() );
+// meshBuilder.TexCoord2f( 1, i, 0.0f );
+ meshBuilder.Color3ubv( s_pColor[ i ] );
+ meshBuilder.AdvanceVertex();
+ }
+
+ for ( int i = 0; i < 12; ++i )
+ {
+ meshBuilder.FastIndex( s_pIndices[i][0] );
+ meshBuilder.FastIndex( s_pIndices[i][1] );
+ meshBuilder.FastIndex( s_pIndices[i][2] );
+ }
+
+ meshBuilder.End();
+}
+
+void CDmeTestMesh::DestroyMesh()
+{
+ if ( m_pMesh )
+ {
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ pRenderContext->DestroyStaticMesh( m_pMesh );
+ m_pMesh = NULL;
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Morph data
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::LoadMorphData( const char *pMorphFile, int nVertexCount )
+{
+ UnloadMorphData();
+
+ IMorphData *pMorphData = CreateMorphData();
+ m_pMorph = pMorphData->Compile( pMorphFile, m_pMaterial, nVertexCount );
+ DestroyMorphData( pMorphData );
+}
+
+void CDmeTestMesh::UnloadMorphData()
+{
+ if ( m_pMorph )
+ {
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ pRenderContext->DestroyMorph( m_pMorph );
+ m_pMorph = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// This function gets called whenever an attribute changes
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::Resolve()
+{
+ CDmAttribute *pMDLFilename = GetAttribute( "mdlfilename" );
+ if ( pMDLFilename && pMDLFilename->IsFlagSet( FATTRIB_DIRTY ) )
+ {
+ UnreferenceMDL();
+ ReferenceMDL( GetValueString( "mdlfilename" ) );
+ return;
+ }
+
+ CDmAttribute *pMorphFilename = GetAttribute( "morphfilename" );
+ if ( pMorphFilename && pMorphFilename->IsFlagSet( FATTRIB_DIRTY ) )
+ {
+ CreateMesh();
+
+ UnloadMorphData();
+ LoadMorphData( GetValueString( "morphfilename" ), 8 );
+ return;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Loads the model matrix based on the transform
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::LoadModelMatrix( CDmeTransform *pTransform )
+{
+ // FIXME: Should this go into the DmeTransform node?
+ matrix3x4_t transform;
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ pTransform->GetTransform( transform );
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->LoadMatrix( transform );
+}
+
+
+//-----------------------------------------------------------------------------
+// A subvision mesh
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// NOTES:
+// The subdivision mesh is fast because it assumes a very particular ordering
+// and definition of the data so that it can determine all subdivided data by
+// inspection without any searching. Here's the layout:
+//
+// First, a face stores a list of edge indices which reference the edges
+// that make up the face. A face is assumed to traverse its vertices in CCW order.
+// We define the "relative edge index" for an edge within a face as the
+// order in which that edge is visited while traversing the edges in CCW order,
+// so 0 is the first visited edge, and 1 is the next, etc.
+//
+// First, edges are defined in a specific way. The edge is assumed to be
+// *directed*, starting at vertex 0 and leading toward vertex 1. Now imagine the
+// two faces that shared this edge and that they both traverse their edges in
+// a right-handed, or CCW direction. Face 0 associated with the edge, to maintain
+// a CCW ordering, must traverse the edge in a *reverse* direction, heading from
+// vertex 1 to vertex 0. Face 1 associated with the edge traverses the edge
+// in a forward direction, from vertex 0 to vertex 1.
+//
+// When subdivision happens, it occurs in a very specific way also. First, when
+// creating the new vertices, for uniform subdivision, we create a new vertex
+// per face, a new vertex per edge, and adjust all existing vertices. When creating
+// these vertices in the subdivided mesh, we first add the face midpoint vertices,
+// then the edge midpoint vertices, then the vertices from the un-subdivided mesh, to
+// the m_Vertices array of the subdivided mesh.
+//
+// Edge subdivision always works in a uniform way: For each edge in the unsubdivided
+// mesh, 4 edges are created from the edge midpoint, connecting to the two
+// face midpoint vertices and the two edge endpoints. In order to maintain the
+// specific ordering of the edges described above, we define the edges in the
+// following manner:
+// * Subdivided edge 0 : Starts at face 0 midpoint, ends at edge midpoint
+// * Subdivided edge 1 : Starts at edge midpoint, ends at face 1 midpoint
+// * Subdivided edge 2 : Starts at original edge's vertex 0, ends at edge midpoint
+// * Subdivided edge 3 : Starts at edge midpoint, ends at original edge's vertex 1
+//
+// Face subdivision *also* always works in a uniform way: For each face in the
+// unsubdivided mesh, N new faces are created, one for each edge in the unsubdivided
+// face. The faces are ordered in a very specific way:
+// * Subdivided face 0 : Starts at the face midpoint, goes to unsubdivided edge 0's midpoint,
+// winds around the edge until it hits unsubdivided edge 1's midpoint,
+// then heads back to the face midpoint.
+// * Subdivided face 1 : Starts at the face midpoint, goes to unsubdivided edge 1's midpoint,
+// winds around the edge until it hits unsubdivided edge 2's midpoint,
+// then heads back to the face midpoint.
+// etc.
+//-----------------------------------------------------------------------------
+struct SubdivVertex_t
+{
+ Vector m_vecPosition;
+ Vector m_vecNormal;
+ Vector m_vecTexCoord;
+ int m_nValence;
+};
+
+// NOTE: The edge is always defined such that the edge going from vertex[0] to vertex[1]
+// is counter-clockwise when seen from face[1] and and clockwise when seen from face[0].
+struct Edge_t
+{
+ int m_pFace[2];
+ int m_pRelativeEdgeIndex[2]; // Goes from 0-N always, specifies the Nth edge of the polygon it's part of for each of the two faces
+ int m_pVertex[2];
+};
+
+struct Face_t
+{
+ int m_nFirstEdgeIndex;
+ int m_nEdgeCount;
+
+ // Stores the index of the first face in the subdivided mesh
+ // isn't actually a part of the mesh data, but I'm storing it here to reduce number of allocations to make
+ mutable int m_nFirstSubdividedFace;
+};
+
+struct SubdivMesh_t
+{
+ CUtlVector<SubdivVertex_t> m_Vertices;
+ CUtlVector<Edge_t> m_Edges;
+
+ // Positive values mean read from m_Edges[x], use m_pVertex[0] for leading vertex
+ // Negative values mean read from m_Edges[-1-x], use m_pVertex[1] for leading vertex
+ CUtlVector<int> m_EdgeIndices;
+ CUtlVector<Face_t> m_Faces;
+
+ int m_nTotalIndexCount;
+ int m_nTotalLineCount;
+};
+
+
+//-----------------------------------------------------------------------------
+// Clears a mesh
+//-----------------------------------------------------------------------------
+static void ClearMesh( SubdivMesh_t &dest )
+{
+ dest.m_Vertices.RemoveAll();
+ dest.m_Edges.RemoveAll();
+ dest.m_EdgeIndices.RemoveAll();
+ dest.m_Faces.RemoveAll();
+ dest.m_nTotalIndexCount = 0;
+ dest.m_nTotalLineCount = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Gets the leading vertex of an edge
+//-----------------------------------------------------------------------------
+static inline int GetLeadingEdgeVertexIndex( const SubdivMesh_t &src, int nEdge )
+{
+ if ( nEdge >= 0 )
+ {
+ const Edge_t &edge = src.m_Edges[nEdge];
+ return edge.m_pVertex[0];
+ }
+
+ const Edge_t &edge = src.m_Edges[ -1 - nEdge ];
+ return edge.m_pVertex[1];
+}
+
+static inline const SubdivVertex_t &GetLeadingEdgeVertex( const SubdivMesh_t &src, int nEdge )
+{
+ return src.m_Vertices[ GetLeadingEdgeVertexIndex( src, nEdge ) ];
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds face midpoints to a mesh
+//-----------------------------------------------------------------------------
+static void AddFaceMidpointsToMesh( const SubdivMesh_t &src, SubdivMesh_t &dest )
+{
+ int nCurrSubdividedFace = 0;
+
+ int nSrcFaceCount = src.m_Faces.Count();
+ for ( int i = 0; i < nSrcFaceCount; ++i )
+ {
+ int nEdgeCount = src.m_Faces[i].m_nEdgeCount;
+ int nEdgeIndex = src.m_Faces[i].m_nFirstEdgeIndex;
+
+ Assert( nEdgeCount != 0 );
+
+ int v = dest.m_Vertices.AddToTail( );
+ SubdivVertex_t &vert = dest.m_Vertices[v];
+ vert.m_vecPosition.Init();
+ vert.m_vecTexCoord.Init();
+ vert.m_nValence = nEdgeCount;
+
+ for ( int j = 0; j < nEdgeCount; ++j, ++nEdgeIndex )
+ {
+ // NOTE: Instead of calling GetLeadingEdgeVertex,
+ // I could add both vertices for each edge + multiply by 0.5
+ int nEdge = src.m_EdgeIndices[nEdgeIndex];
+
+ const SubdivVertex_t &srcVert = GetLeadingEdgeVertex( src, nEdge );
+ vert.m_vecPosition += srcVert.m_vecPosition;
+ vert.m_vecTexCoord += srcVert.m_vecTexCoord;
+ }
+
+ vert.m_vecPosition /= nEdgeCount;
+ vert.m_vecTexCoord /= nEdgeCount;
+
+ // Store off the face index in the dest mesh of the first subdivided face for this guy.
+ src.m_Faces[i].m_nFirstSubdividedFace = nCurrSubdividedFace;
+ nCurrSubdividedFace += nEdgeCount;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds edge midpoints to a mesh
+//-----------------------------------------------------------------------------
+static void AddEdgeMidpointsToMesh( const SubdivMesh_t &src, SubdivMesh_t &dest )
+{
+ int nSrcEdgeCount = src.m_Edges.Count();
+ for ( int i = 0; i < nSrcEdgeCount; ++i )
+ {
+ const Edge_t &edge = src.m_Edges[i];
+
+ int v = dest.m_Vertices.AddToTail( );
+ SubdivVertex_t &vert = dest.m_Vertices[v];
+ vert.m_nValence = 4;
+
+ const SubdivVertex_t *pSrcVert = &src.m_Vertices[ edge.m_pVertex[0] ];
+ vert.m_vecPosition = pSrcVert->m_vecPosition;
+ vert.m_vecTexCoord = pSrcVert->m_vecTexCoord;
+
+ pSrcVert = &src.m_Vertices[ edge.m_pVertex[1] ];
+ vert.m_vecPosition += pSrcVert->m_vecPosition;
+ vert.m_vecTexCoord += pSrcVert->m_vecTexCoord;
+
+ // NOTE: We know that the first n vertices added to dest correspond to the src face midpoints
+ pSrcVert = &dest.m_Vertices[ edge.m_pFace[0] ];
+ vert.m_vecPosition += pSrcVert->m_vecPosition;
+ vert.m_vecTexCoord += pSrcVert->m_vecTexCoord;
+
+ pSrcVert = &dest.m_Vertices[ edge.m_pFace[1] ];
+ vert.m_vecPosition += pSrcVert->m_vecPosition;
+ vert.m_vecTexCoord += pSrcVert->m_vecTexCoord;
+
+ vert.m_vecPosition /= 4.0f;
+ vert.m_vecTexCoord /= 4.0f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds edge midpoints to a mesh
+//-----------------------------------------------------------------------------
+static void AddModifiedVerticesToMesh( const SubdivMesh_t &src, SubdivMesh_t &dest )
+{
+ int nSrcVertexCount = src.m_Vertices.Count();
+
+ // This computes the equation v(i+1) = ((N-2)/N) * v(i) + (1/N^2) * sum( ei + fi )
+ int nFirstDestVertex = dest.m_Vertices.Count();
+ for ( int i = 0; i < nSrcVertexCount; ++i )
+ {
+ int v = dest.m_Vertices.AddToTail( );
+ SubdivVertex_t &vert = dest.m_Vertices[v];
+
+ int nValence = src.m_Vertices[i].m_nValence;
+ vert.m_nValence = nValence;
+ float flScale = (float)(nValence - 2) / nValence;
+ VectorScale( src.m_Vertices[i].m_vecPosition, flScale, vert.m_vecPosition );
+ VectorScale( src.m_Vertices[i].m_vecTexCoord, flScale, vert.m_vecTexCoord );
+ }
+
+ int nSrcEdgeCount = src.m_Edges.Count();
+ for ( int i = 0; i < nSrcEdgeCount; ++i )
+ {
+ const Edge_t &edge = src.m_Edges[i];
+ for ( int j = 0; j < 2; ++j )
+ {
+ int nDestVertIndex = nFirstDestVertex + edge.m_pVertex[j];
+ SubdivVertex_t &destVertex = dest.m_Vertices[nDestVertIndex];
+
+ float ooValenceSq = 1.0f / destVertex.m_nValence;
+ ooValenceSq *= ooValenceSq;
+
+ // This adds in the contribution from the source vertex at the opposite edge
+ const SubdivVertex_t &srcOtherVert = src.m_Vertices[ edge.m_pVertex[ 1 - j ] ];
+ VectorMA( destVertex.m_vecPosition, ooValenceSq, srcOtherVert.m_vecPosition, destVertex.m_vecPosition );
+ VectorMA( destVertex.m_vecTexCoord, ooValenceSq, srcOtherVert.m_vecTexCoord, destVertex.m_vecTexCoord );
+
+ // This adds in the contribution from the two faces it's part of
+ // NOTE: Usage of dest here is correct; this grabs the vertex that
+ // was created that was in the middle of the source mesh's face
+ const SubdivVertex_t *pSrcFace = &dest.m_Vertices[ edge.m_pFace[ 0 ] ];
+ VectorMA( destVertex.m_vecPosition, 0.5f * ooValenceSq, pSrcFace->m_vecPosition, destVertex.m_vecPosition );
+ VectorMA( destVertex.m_vecTexCoord, 0.5f * ooValenceSq, pSrcFace->m_vecTexCoord, destVertex.m_vecTexCoord );
+ pSrcFace = &dest.m_Vertices[ edge.m_pFace[ 1 ] ];
+ VectorMA( destVertex.m_vecPosition, 0.5f * ooValenceSq, pSrcFace->m_vecPosition, destVertex.m_vecPosition );
+ VectorMA( destVertex.m_vecTexCoord, 0.5f * ooValenceSq, pSrcFace->m_vecTexCoord, destVertex.m_vecTexCoord );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds unique subdivided edges so they aren't repeated.
+//-----------------------------------------------------------------------------
+static void AddSubdividedEdges( const SubdivMesh_t &src, SubdivMesh_t &dest )
+{
+ // NOTE: We iterate over each edge in sequence and add edges
+ // between face 0, then face 1, then vertex 0, then vertex 1.
+ // The vertex index for the vert at the center of original face N is N.
+ // The vertex index for the vert at the center of original edge N is nSrcFaceCount + N;
+ // The vertex index for the vert at original vertex N is nSrcFaceCount + nSrcEdgeCount + N;
+ int nSrcFaceCount = src.m_Faces.Count();
+ int nSrcEdgeCount = src.m_Edges.Count();
+
+ for ( int i = 0; i < nSrcEdgeCount; ++i )
+ {
+ const Edge_t &srcEdge = src.m_Edges[i];
+
+ int e = dest.m_Edges.AddMultipleToTail( 4 );
+ Edge_t *pDstEdge = &dest.m_Edges[e];
+
+ // Grab the two source faces
+ const Face_t *pFaces[2];
+ pFaces[0] = &src.m_Faces[ srcEdge.m_pFace[0] ];
+ pFaces[1] = &src.m_Faces[ srcEdge.m_pFace[1] ];
+
+ // Get the first subdivided face index + relative edge index
+ int pSubdividedFaceIndex[2];
+ pSubdividedFaceIndex[0] = pFaces[0]->m_nFirstSubdividedFace;
+ pSubdividedFaceIndex[1] = pFaces[1]->m_nFirstSubdividedFace;
+
+ // Get the relative edge index
+ int pRelativeEdgeIndex[2];
+ pRelativeEdgeIndex[0] = srcEdge.m_pRelativeEdgeIndex[0];
+ pRelativeEdgeIndex[1] = srcEdge.m_pRelativeEdgeIndex[1];
+
+ int pPrevRelativeEdgeIndex[2];
+ pPrevRelativeEdgeIndex[0] = (srcEdge.m_pRelativeEdgeIndex[0] - 1);
+ if ( pPrevRelativeEdgeIndex[0] < 0 )
+ {
+ pPrevRelativeEdgeIndex[0] = pFaces[0]->m_nEdgeCount - 1;
+ }
+ pPrevRelativeEdgeIndex[1] = (srcEdge.m_pRelativeEdgeIndex[1] - 1);
+ if ( pPrevRelativeEdgeIndex[1] < 0 )
+ {
+ pPrevRelativeEdgeIndex[1] = pFaces[1]->m_nEdgeCount - 1;
+ }
+
+ // This ordering maintains clockwise order
+ pDstEdge[0].m_pVertex[0] = srcEdge.m_pFace[0];
+ pDstEdge[0].m_pVertex[1] = nSrcFaceCount + i;
+ pDstEdge[0].m_pFace[0] = pSubdividedFaceIndex[0] + pPrevRelativeEdgeIndex[0];
+ pDstEdge[0].m_pFace[1] = pSubdividedFaceIndex[0] + pRelativeEdgeIndex[0];
+ pDstEdge[0].m_pRelativeEdgeIndex[0] = 3;
+ pDstEdge[0].m_pRelativeEdgeIndex[1] = 0;
+
+ pDstEdge[1].m_pVertex[0] = nSrcFaceCount + i;
+ pDstEdge[1].m_pVertex[1] = srcEdge.m_pFace[1];
+ pDstEdge[1].m_pFace[0] = pSubdividedFaceIndex[1] + pRelativeEdgeIndex[1];
+ pDstEdge[1].m_pFace[1] = pSubdividedFaceIndex[1] + pPrevRelativeEdgeIndex[1];
+ pDstEdge[1].m_pRelativeEdgeIndex[0] = 0;
+ pDstEdge[1].m_pRelativeEdgeIndex[1] = 3;
+
+ pDstEdge[2].m_pVertex[0] = nSrcFaceCount + nSrcEdgeCount + srcEdge.m_pVertex[0];
+ pDstEdge[2].m_pVertex[1] = nSrcFaceCount + i;
+ pDstEdge[2].m_pFace[0] = pSubdividedFaceIndex[0] + pRelativeEdgeIndex[0];
+ pDstEdge[2].m_pFace[1] = pSubdividedFaceIndex[1] + pPrevRelativeEdgeIndex[1];
+ pDstEdge[2].m_pRelativeEdgeIndex[0] = 1;
+ pDstEdge[2].m_pRelativeEdgeIndex[1] = 2;
+
+ pDstEdge[3].m_pVertex[0] = nSrcFaceCount + i;
+ pDstEdge[3].m_pVertex[1] = nSrcFaceCount + nSrcEdgeCount + srcEdge.m_pVertex[1];
+ pDstEdge[3].m_pFace[0] = pSubdividedFaceIndex[0] + pPrevRelativeEdgeIndex[0];
+ pDstEdge[3].m_pFace[1] = pSubdividedFaceIndex[1] + pRelativeEdgeIndex[1];
+ pDstEdge[3].m_pRelativeEdgeIndex[0] = 2;
+ pDstEdge[3].m_pRelativeEdgeIndex[1] = 1;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds unique subdivided faces
+//-----------------------------------------------------------------------------
+static void AddSubdividedFaces( const SubdivMesh_t &src, SubdivMesh_t &dest )
+{
+ dest.m_nTotalIndexCount = 0;
+ dest.m_nTotalLineCount = 0;
+ int nSrcFaceCount = src.m_Faces.Count();
+ for ( int i = 0; i < nSrcFaceCount; ++i )
+ {
+ int nEdgeCount = src.m_Faces[i].m_nEdgeCount;
+ const int *pSrcEdgeIndex = &src.m_EdgeIndices[ src.m_Faces[i].m_nFirstEdgeIndex ];
+
+ int ei = dest.m_EdgeIndices.AddMultipleToTail( nEdgeCount * 4 );
+ int *pDestEdgeIndex = &dest.m_EdgeIndices[ ei ];
+ int *pPrevDestEdgeIndex = &pDestEdgeIndex[(nEdgeCount - 1) * 4];
+ for ( int j = 0; j < nEdgeCount; ++j )
+ {
+ // Add another quad.
+ dest.m_nTotalIndexCount += 6;
+ dest.m_nTotalLineCount += 4;
+
+ // Add a face for every edge. Note that subdivided face N
+ // is the face whose goes through edge N.
+ int f = dest.m_Faces.AddToTail();
+ Face_t *pDestFace = &dest.m_Faces[f];
+ pDestFace->m_nEdgeCount = 4;
+ pDestFace->m_nFirstEdgeIndex = ei + (j * 4);
+
+ // Fill it with bogus data
+ pDestFace->m_nFirstSubdividedFace = -1;
+
+ // Now add in the edge indices to refer to the edges created in AddSubdividedEdges.
+ // Note that the new edge index == the old edge index * 4, since we always
+ // create 4 edges for every edge in the source list.
+ int *pCurrDestEdgeIndex = &pDestEdgeIndex[j*4];
+ int nSrcEdgeIndex = pSrcEdgeIndex[j];
+ if ( nSrcEdgeIndex >= 0 )
+ {
+ // This means this polygon is the '1' index in the edge; it's following this edge CCW.
+ int nDestEdgeIndex = nSrcEdgeIndex * 4;
+ pCurrDestEdgeIndex[0] = -1 - (nDestEdgeIndex + 1); // We're following this edge backwards
+ pCurrDestEdgeIndex[1] = nDestEdgeIndex + 3;
+ pPrevDestEdgeIndex[2] = nDestEdgeIndex + 2;
+ pPrevDestEdgeIndex[3] = nDestEdgeIndex + 1;
+ }
+ else
+ {
+ // This means this polygon is the '0' index in the edge; it's following this edge CW.
+ int nDestEdgeIndex = (-1 - nSrcEdgeIndex) * 4;
+ pCurrDestEdgeIndex[0] = nDestEdgeIndex;
+ pCurrDestEdgeIndex[1] = -1 - (nDestEdgeIndex + 2); // We're following this edge backwards
+ pPrevDestEdgeIndex[2] = -1 - (nDestEdgeIndex + 3); // We're following this edge backwards
+ pPrevDestEdgeIndex[3] = -1 - (nDestEdgeIndex); // We're following this edge backwards
+ }
+
+ pPrevDestEdgeIndex = pCurrDestEdgeIndex;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Subdivides a mesh
+//-----------------------------------------------------------------------------
+static void SubdivideMesh( const SubdivMesh_t &src, SubdivMesh_t &dest )
+{
+ // Preallocate space for dest data
+ int nSrcFaceCount = src.m_Faces.Count();
+ int nSrcEdgeCount = src.m_Edges.Count();
+ dest.m_Vertices.EnsureCapacity( nSrcFaceCount + nSrcEdgeCount + src.m_Vertices.Count() );
+ dest.m_Edges.EnsureCapacity( nSrcEdgeCount * 4 );
+ dest.m_EdgeIndices.EnsureCapacity( nSrcFaceCount * 16 );
+ dest.m_Faces.EnsureCapacity( nSrcFaceCount * 4 ); // This is only true if we have valence 4 everywhere.
+
+ // First, compute midpoints of each face, add them to the mesh
+ AddFaceMidpointsToMesh( src, dest );
+
+ // Next, for each edge, compute a new point which is the average of the edge points and the face midpoints
+ AddEdgeMidpointsToMesh( src, dest );
+
+ // Add modified versions of the vertices in the src mesh based on the new computed points and add them to the dest mesh
+ AddModifiedVerticesToMesh( src, dest );
+
+ // Add subdivided edges based on the previous edges
+ AddSubdividedEdges( src, dest );
+
+ // Add subdivided faces referencing the subdivided edges
+ AddSubdividedFaces( src, dest );
+}
+
+
+//-----------------------------------------------------------------------------
+// Creates/destroys the subdiv control cage
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::CreateControlCage( )
+{
+ DestroyControlCage();
+ m_pControlCage = new SubdivMesh_t;
+
+ // Draw a simple cube
+ static Vector s_pPositions[8] =
+ {
+ Vector( -30, -30, -30 ),
+ Vector( 30, -30, -30 ),
+ Vector( -30, 30, -30 ),
+ Vector( 30, 30, -30 ),
+ Vector( -30, -30, 30 ),
+ Vector( 30, -30, 30 ),
+ Vector( -30, 30, 30 ),
+ Vector( 30, 30, 30 ),
+ };
+
+ static Vector2D s_pTexCoords[8] =
+ {
+ Vector2D( 0, 0 ),
+ Vector2D( 0.5, 0 ),
+ Vector2D( 0, 0.5 ),
+ Vector2D( 0.5, 0.5 ),
+ Vector2D( 0.5, 0.5 ),
+ Vector2D( 1, 0.5 ),
+ Vector2D( 0.5, 1 ),
+ Vector2D( 1, 1 ),
+ };
+
+ // Indices into the vertex array
+ static int s_pEdges[12][2] =
+ {
+ { 0, 4 }, { 4, 6 }, { 6, 2 }, { 2, 0 }, // 0 -> -x
+ { 1, 3 }, { 3, 7 }, { 7, 5 }, { 5, 1 }, // 1 -> +x
+ { 0, 1 }, { 5, 4 }, // 2 -> -y
+ { 6, 7 }, { 3, 2 }, // 3 -> +y
+ // 4 -> -z
+ // 5 -> +z
+ };
+
+ // Indices into the face array associated w/ the edges above
+ static int s_pEdgeFaces[12][2] =
+ {
+ { 2, 0 }, { 5, 0 }, { 3, 0 }, { 4, 0 }, // 0 -> -x
+ { 4, 1 }, { 3, 1 }, { 5, 1 }, { 2, 1 }, // 1 -> +x
+ { 4, 2 }, { 5, 2 }, // 2 -> -y
+ { 5, 3 }, { 4, 3 }, // 3 -> +y
+ // 4 -> -z
+ // 5 -> +z
+ };
+
+ // In what order does edge s_pEdges[i] appear on faces s_pEdgeFaces[i][0] and s_pEdgeFaces[i][1]
+ // in the list s_pIndices[s_pEdgeFaces[i][j]] below? Note the #s 0, 1, 2, and 3 should appear 6 times each in this array
+ // representing the fact that each face has a 0th,1st,2nd, and 3rd edge.
+ static int s_pRelativeEdgeIndex[12][2] =
+ {
+ { 3, 0 }, { 3, 1 }, { 0, 2 }, { 0, 3 }, // 0 -> -x
+ { 2, 0 }, { 2, 1 }, { 1, 2 }, { 1, 3 }, // 1 -> +x
+ { 3, 0 }, { 0, 2 }, // 2 -> -y
+ { 2, 1 }, { 1, 3 }, // 3 -> +y
+ // 4 -> -z
+ // 5 -> +z
+ };
+
+ static int s_pIndices[6][5] =
+ {
+ { 0, 4, 6, 2, 0 }, // 0 -> -x
+ { 1, 3, 7, 5, 1 }, // 1 -> +x
+ { 0, 1, 5, 4, 0 }, // 2 -> -y
+ { 2, 6, 7, 3, 2 }, // 3 -> +y
+ { 0, 2, 3, 1, 0 }, // 4 -> -z
+ { 4, 5, 7, 6, 4 }, // 5 -> +z
+ };
+
+ // Add vertices
+ int i;
+ for ( i = 0; i < 8; ++i )
+ {
+ int v = m_pControlCage->m_Vertices.AddToTail();
+ SubdivVertex_t &vert = m_pControlCage->m_Vertices[v];
+ vert.m_vecPosition = s_pPositions[i];
+ vert.m_vecNormal = vec3_origin;
+ vert.m_vecTexCoord.AsVector2D() = s_pTexCoords[i];
+ vert.m_nValence = 3;
+ }
+
+ // Add unique edges
+ for ( i = 0; i < 12; ++i )
+ {
+ int e = m_pControlCage->m_Edges.AddToTail();
+ Edge_t &edge = m_pControlCage->m_Edges[e];
+ edge.m_pVertex[0] = s_pEdges[i][0];
+ edge.m_pVertex[1] = s_pEdges[i][1];
+ edge.m_pFace[0] = s_pEdgeFaces[i][0];
+ edge.m_pFace[1] = s_pEdgeFaces[i][1];
+ edge.m_pRelativeEdgeIndex[0] = s_pRelativeEdgeIndex[i][0];
+ edge.m_pRelativeEdgeIndex[1] = s_pRelativeEdgeIndex[i][1];
+ }
+
+ m_pControlCage->m_nTotalIndexCount = 0;
+ m_pControlCage->m_nTotalLineCount = 0;
+ for ( i = 0; i < 6; ++i )
+ {
+ int f = m_pControlCage->m_Faces.AddToTail();
+ Face_t &face = m_pControlCage->m_Faces[f];
+ face.m_nFirstEdgeIndex = m_pControlCage->m_EdgeIndices.Count();
+ face.m_nEdgeCount = 4;
+
+ // Place an invalid value here
+ face.m_nFirstSubdividedFace = -1;
+
+ // Two triangles per quad
+ m_pControlCage->m_nTotalIndexCount += 6;
+ m_pControlCage->m_nTotalLineCount += 4;
+
+ for ( int j = 0; j < 4; ++j )
+ {
+ int k;
+ for ( k = 0; k < 12; ++k )
+ {
+ if ( (s_pIndices[i][j] == s_pEdges[k][0]) && (s_pIndices[i][j+1] == s_pEdges[k][1]) )
+ {
+ m_pControlCage->m_EdgeIndices.AddToTail( k );
+ break;
+ }
+ if ( (s_pIndices[i][j] == s_pEdges[k][1]) && (s_pIndices[i][j+1] == s_pEdges[k][0]) )
+ {
+ m_pControlCage->m_EdgeIndices.AddToTail( -1-k );
+ break;
+ }
+ }
+ Assert( k != 12 );
+ }
+ }
+}
+
+void CDmeTestMesh::DestroyControlCage( )
+{
+ if ( m_pControlCage )
+ {
+ delete m_pControlCage;
+ m_pControlCage = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Draws a subdiv mesh
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::DrawSubdivMesh( const SubdivMesh_t &mesh )
+{
+ if ( !g_pMaterialSystem )
+ return;
+
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+
+ IMaterial *pMaterial = g_pMaterialSystem->FindMaterial( "debug/debugwireframe", NULL, false );
+ pRenderContext->Bind( pMaterial );
+ IMesh *pMesh = pRenderContext->GetDynamicMesh();
+ CMeshBuilder meshBuilder;
+
+ int nVertexCount = mesh.m_Vertices.Count();
+
+// meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertexCount, mesh.m_nTotalIndexCount );
+ meshBuilder.Begin( pMesh, MATERIAL_LINES, nVertexCount, mesh.m_nTotalLineCount * 2 );
+
+ for ( int i = 0; i < nVertexCount; ++i )
+ {
+ meshBuilder.Position3fv( mesh.m_Vertices[ i ].m_vecPosition.Base() );
+ meshBuilder.TexCoord2fv( 0, mesh.m_Vertices[ i ].m_vecTexCoord.Base() );
+ meshBuilder.TexCoord2f( 1, i, 0.0f );
+ meshBuilder.Color3ub( 255, 255, 255 );
+ meshBuilder.AdvanceVertex();
+ }
+
+ int nFaceCount = mesh.m_Faces.Count();
+ for ( int i = 0; i < nFaceCount; ++i )
+ {
+ int nEdgeCount = mesh.m_Faces[i].m_nEdgeCount;
+ const int *pEdgeIndex = &mesh.m_EdgeIndices[ mesh.m_Faces[i].m_nFirstEdgeIndex ];
+ int nPrevIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[nEdgeCount-1] );
+ for ( int j = 0; j < nEdgeCount; ++j )
+ {
+ int nCurrIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[j] );
+ meshBuilder.FastIndex( nPrevIndex );
+ meshBuilder.FastIndex( nCurrIndex );
+ nPrevIndex = nCurrIndex;
+ }
+ }
+
+ /*
+ int nFaceCount = mesh.m_Faces.Count();
+ for ( int i = 0; i < nFaceCount; ++i )
+ {
+ int nEdgeCount = mesh.m_Faces[i].m_nEdgeCount;
+ const int *pEdgeIndex = &mesh.m_EdgeIndices[ mesh.m_Faces[i].m_nFirstEdgeIndex ];
+ int nRootIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[0] );
+ int nPrevIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[1] );
+ for ( int j = 0; j < nEdgeCount - 2; ++j )
+ {
+ int nCurrIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[j+2] );
+ meshBuilder.FastIndex( nRootIndex );
+ meshBuilder.FastIndex( nPrevIndex );
+ meshBuilder.FastIndex( nCurrIndex );
+ nPrevIndex = nCurrIndex;
+ }
+ }
+ */
+
+ meshBuilder.End();
+ pMesh->Draw();
+}
+
+
+//-----------------------------------------------------------------------------
+// Draws a subdivided box
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::DrawSubdividedBox()
+{
+ if ( !g_pMaterialSystem )
+ return;
+
+ if ( !m_pControlCage )
+ {
+ CreateControlCage( );
+ }
+
+ int nSubdivLevel = GetValue<int>( "subdivlevel" );
+ if ( nSubdivLevel == 0 )
+ {
+ DrawSubdivMesh( *m_pControlCage );
+ return;
+ }
+
+ // Construct the initial mesh
+ SubdivMesh_t subdivMesh[2];
+ SubdivideMesh( *m_pControlCage, subdivMesh[0] );
+
+ // Compute the subdivided vertices
+ int nCurrMesh = 0;
+ while ( --nSubdivLevel > 0 )
+ {
+ ClearMesh( subdivMesh[1 - nCurrMesh] );
+ SubdivideMesh( subdivMesh[nCurrMesh], subdivMesh[1 - nCurrMesh] );
+ if (( subdivMesh[1 - nCurrMesh].m_nTotalLineCount * 2 >= 32768 ) || ( subdivMesh[1 - nCurrMesh].m_Vertices.Count() >= 32768 ))
+ break;
+ nCurrMesh = 1 - nCurrMesh;
+ }
+
+ // Draw the subdivided mesh
+ DrawSubdivMesh( subdivMesh[nCurrMesh] );
+}
+
+
+//-----------------------------------------------------------------------------
+// Draws the mesh
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::DrawBox( CDmeTransform *pTransform )
+{
+ if ( !g_pMaterialSystem )
+ return;
+
+ // FIXME: Hack!
+ if ( !m_pMorph || !m_pMesh )
+ return;
+
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ // Set up morph factors
+ float pMorphFactors[32];
+ for ( int i = 0; i < 32; ++i )
+ {
+ pMorphFactors[i] = 0.5f + 0.5f * sin( 2 * 3.14 * ( Plat_FloatTime() / 5.0f + (float)i / 32.0f ) );
+ }
+ pMorphFactors[1] = 1.0f - pMorphFactors[0];
+ pRenderContext->SetMorphTargetFactors( 0, pMorphFactors, 32 );
+
+ // FIXME: Should this call be made from the application rendering the mesh?
+ LoadModelMatrix( pTransform );
+
+ pRenderContext->BindMorph( m_pMorph );
+
+ pRenderContext->Bind( m_pMaterial );
+ m_pMesh->Draw();
+
+ pRenderContext->BindMorph( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Draws the mesh
+//-----------------------------------------------------------------------------
+void CDmeTestMesh::Draw( const matrix3x4_t& shapeToWorld, CDmeDrawSettings *pDrawSettings )
+{
+ if ( !g_pMaterialSystem || !g_pMDLCache || !g_pStudioRender )
+ return;
+
+#if 0
+// DrawSubdividedBox( pTransform );
+ DrawBox( pTransform );
+ return;
+
+#elif 0
+ if ( m_MDLHandle == MDLHANDLE_INVALID )
+ return;
+
+ // Color + alpha modulation
+ Vector white(1.0f, 1.0f, 1.0f);
+ g_pStudioRender->SetColorModulation( white.Base() );
+ g_pStudioRender->SetAlphaModulation( 1.0f );
+
+ DrawModelInfo_t info;
+ info.m_pStudioHdr = g_pMDLCache->GetStudioHdr( m_MDLHandle );
+ info.m_pHardwareData = g_pMDLCache->GetHardwareData( m_MDLHandle );
+ info.m_Decals = STUDIORENDER_DECAL_INVALID;
+ info.m_Skin = GetAttributeValueInt( "skin" );
+ info.m_Body = GetAttributeValueInt( "body" );
+ info.m_HitboxSet = 0;
+ info.m_pClientEntity = NULL;
+ info.m_ppColorMeshes = NULL;
+ info.m_bStaticLighting = false;
+ info.m_Lod = GetAttributeValueInt( "lod" );
+
+ // FIXME: Deal with lighting
+ for ( int i = 0; i < 6; ++ i )
+ {
+ info.m_vecAmbientCube[i].Init( 1, 1, 1 );
+ }
+
+ info.m_nLocalLightCount = 0;
+// info.m_LocalLightDescs;
+
+ matrix3x4_t *pBoneToWorld = g_pStudioRender->LockBoneMatrices( info.m_pStudioHdr->numbones );
+ SetUpBones( pTransform, info.m_pStudioHdr->numbones, pBoneToWorld );
+ g_pStudioRender->UnlockBoneMatrices();
+
+ // Root transform
+ matrix3x4_t rootToWorld;
+ pTransform->GetTransform( rootToWorld );
+
+ Vector vecModelOrigin;
+ MatrixGetColumn( rootToWorld, 3, vecModelOrigin );
+ g_pStudioRender->DrawModel( NULL, info, pBoneToWorld, vecModelOrigin, STUDIORENDER_DRAW_ENTIRE_MODEL );
+#else
+
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+
+#if 1
+ matrix3x4_t mat;
+ if ( m_bones.size() == 1 )
+ {
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ m_bones[0]->GetTransform( mat );
+ pRenderContext->LoadMatrix( mat );
+// pRenderContext->LoadMatrix( m_bones[0] ); // m_PoseToWorld[0]
+ }
+
+ pRenderContext->SetNumBoneWeights( 2 ); // pStrip->numBones
+
+ uint bn = m_bones.size();
+ for ( uint bi = 0; bi < bn; ++bi )
+ {
+ m_bones[bi]->GetTransform( mat );
+
+#if 0 // hack to see whether bones are actually affecting the model
+ float f = 100.0f;
+ Vector translation;
+ MatrixGetColumn( mat, 3, &translation );
+ translation.x += (bi&1) ? f : -f;
+ translation.y += (bi&2) ? f : -f;
+ translation.z += (bi&4) ? f : -f;
+ MatrixSetColumn( translation, 3, mat );
+#endif
+ pRenderContext->LoadBoneMatrix( bi, mat );
+ }
+#else
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ matrix3x4_t mat;
+ Assert( !m_bones.empty() );
+ m_bones[0]->GetTransform( mat );
+ pRenderContext->LoadMatrix( mat );
+#endif
+
+ IMaterial *pMaterial = g_pMaterialSystem->FindMaterial( "Models/shadertest/unlitgenericmodel", NULL, false );
+// IMaterial *pMaterial = g_pMaterialSystem->FindMaterial( "debug/debugwireframevertexcolor", NULL, false );
+// IMaterial *pMaterial = g_pMaterialSystem->FindMaterial( "debug/debugwireframe", NULL, false );
+ pRenderContext->Bind( pMaterial );
+
+ IMesh *pMesh = pRenderContext->GetDynamicMesh();
+
+ int mn = m_submeshes.size();
+ for ( int mi = 0; mi < mn; ++mi )
+ {
+ CMeshBuilder meshBuilder;
+ std::vector< int > &indices = m_submeshes[mi]->indices;
+ std::vector< vertex_t > &vertices = m_submeshes[mi]->vertices;
+
+ meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, vertices.size(), indices.size() );
+
+ int vn = vertices.size();
+ for ( int vi = 0; vi < vn; ++vi )
+ {
+ vertex_t &vertex = vertices[vi];
+ meshBuilder.Position3fv( vertex.coord.Base() );
+ meshBuilder.Normal3fv ( vertex.normal.Base() );
+ meshBuilder.TexCoord2fv( 0, vertex.texcoord.Base() );
+ switch ( vertex.skinning[0].index )
+ {
+ case 0: meshBuilder.Color3f(1,0,0); break;
+ case 1: meshBuilder.Color3f(0,1,0); break;
+ case 2: meshBuilder.Color3f(0,0,1); break;
+ case 3: meshBuilder.Color3f(1,1,0); break;
+ case 4: meshBuilder.Color3f(0,1,1); break;
+ case 5: meshBuilder.Color3f(1,0,1); break;
+ case 6: meshBuilder.Color3f(0,0,0); break;
+ case 7: meshBuilder.Color3f(1,1,1); break;
+ default: meshBuilder.Color3f(0.5f,0.5f,0.5f); break;
+ }
+
+ int bn = vertex.skinning.size();
+ for ( int bi = 0; bi < bn; ++bi )
+ {
+ meshBuilder.BoneMatrix( bi, vertex.skinning[bi].index );
+ meshBuilder.BoneWeight( bi, vertex.skinning[bi].weight );
+ }
+
+ meshBuilder.AdvanceVertex();
+ }
+
+ int in = indices.size();
+ for ( int ii = 0; ii < in; ++ii )
+ {
+ meshBuilder.FastIndex( indices[ii] );
+ }
+
+ meshBuilder.End();
+ pMesh->Draw();
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns a mask indicating which bones to set up
+//-----------------------------------------------------------------------------
+int CDmeTestMesh::BoneMask( void )
+{
+ int nLod = GetValue<int>( "lod" );
+ return BONE_USED_BY_VERTEX_AT_LOD( nLod );
+}
+
+void CDmeTestMesh::SetUpBones( CDmeTransform *pTransform, int nMaxBoneCount, matrix3x4_t *pBoneToWorld )
+{
+ // Default to middle of the pose parameter range
+ float pPoseParameter[MAXSTUDIOPOSEPARAM];
+ for ( int i = 0; i < MAXSTUDIOPOSEPARAM; ++i )
+ {
+ pPoseParameter[i] = 0.5f;
+ }
+
+ CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_MDLHandle ), g_pMDLCache );
+
+ int nSequence = GetValue<int>( "sequence" );
+ float flPlaybackRate = GetValue<float>( "playbackrate" );
+ float flTime = GetValue<float>( "time" );
+
+ int nFrameCount = Studio_MaxFrame( &studioHdr, nSequence, pPoseParameter );
+ if ( nFrameCount == 0 )
+ {
+ nFrameCount = 1;
+ }
+ float flCycle = ( flTime * flPlaybackRate ) / nFrameCount;
+
+ // FIXME: We're always wrapping; may want to determing if we should clamp
+ flCycle -= (int)(flCycle);
+
+ Vector pos[MAXSTUDIOBONES];
+ Quaternion q[MAXSTUDIOBONES];
+
+ IBoneSetup boneSetup( &studioHdr, BoneMask(), pPoseParameter );
+ boneSetup.InitPose( pos, q );
+ boneSetup.AccumulatePose( pos, q, nSequence, flCycle, 1.0f, flTime, NULL );
+
+ // FIXME: Try enabling this?
+// CalcAutoplaySequences( pStudioHdr, NULL, pos, q, pPoseParameter, BoneMask( ), flTime );
+
+ // Root transform
+ matrix3x4_t rootToWorld;
+ pTransform->GetTransform( rootToWorld );
+
+ if ( studioHdr.numBones() < nMaxBoneCount )
+ {
+ nMaxBoneCount = studioHdr.numBones();
+ }
+
+ for ( int i = 0; i < nMaxBoneCount; i++ )
+ {
+ // If it's not being used, fill with NAN for errors
+#ifdef _DEBUG
+ if ( !(studioHdr.pBone( i )->flags & BoneMask()))
+ {
+ int j, k;
+ for (j = 0; j < 3; j++)
+ {
+ for (k = 0; k < 4; k++)
+ {
+ pBoneToWorld[i][j][k] = VEC_T_NAN;
+ }
+ }
+ continue;
+ }
+#endif
+
+ matrix3x4_t boneMatrix;
+ QuaternionMatrix( q[i], boneMatrix );
+ MatrixSetColumn( pos[i], 3, boneMatrix );
+
+ if (studioHdr.pBone(i)->parent == -1)
+ {
+ ConcatTransforms (rootToWorld, boneMatrix, pBoneToWorld[ i ]);
+ }
+ else
+ {
+ ConcatTransforms ( pBoneToWorld[ studioHdr.pBone(i)->parent ], boneMatrix, pBoneToWorld[ i ] );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// FIXME: This trashy glue code is really not acceptable. Figure out a way of making it unnecessary.
+//-----------------------------------------------------------------------------
+const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *pModelName ) const
+{
+ MDLHandle_t handle = g_pMDLCache->FindMDL( pModelName );
+ *cache = (void*)handle;
+ return g_pMDLCache->GetStudioHdr( handle );
+}
+
+virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const
+{
+ return g_pMDLCache->GetVirtualModel( (MDLHandle_t)virtualModel );
+}
+
+byte *studiohdr_t::GetAnimBlock( int i ) const
+{
+ return g_pMDLCache->GetAnimBlock( (MDLHandle_t)virtualModel, i );
+}
+
+int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const
+{
+ return g_pMDLCache->GetAutoplayList( (MDLHandle_t)virtualModel, pOut );
+}
+
+const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const
+{
+ return g_pMDLCache->GetStudioHdr( (MDLHandle_t)cache );
+}
+
+//-----------------------------------------------------------------------------
+// First attempt at making a hacky SMD loader - clean this up later
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// SMD format:
+//
+// format key:
+// #n = integer
+// .x = float
+// 'a' = literal string
+// $s = string
+// " = the literal quote character
+// // = comment - not in file!!!
+//
+// 'version' #version // right now, #version = 1
+//
+// 'nodes' // bone naming and hierarchy
+// #bone "$bonename" #parent // one of these per bone - can be in any order, but generally sequential
+// 'end'
+//
+// 'skeleton' // joint animation (and begin pose)
+// 'time' #time // repeat time + joints block once per frame
+// #bone .x .y .z .rx .ry .rz // bone/translation/rotation - can traverse bones in any order, and even skip them
+// 'end'
+//
+// 'triangles' // actual vertex data - as non-indexed triangle lists
+// $texturefilename // repeat texture + 3 vertex lines for each triangle
+// #bone .x .y .z .nx .ny .nz .tu .tv #count #bone0 .weight0 // boneN & weightN may or may not exist for N={0..511}
+// #bone .x .y .z .nx .ny .nz .tu .tv #count #bone0 .weight0 // boneN & weightN may or may not exist for N={0..511}
+// #bone .x .y .z .nx .ny .nz .tu .tv #count #bone0 .weight0 // boneN & weightN may or may not exist for N={0..511}
+// 'end'
+//
+// 'vertexanimation' // morph targets
+// 'time' #time // repeat time + vertices block once per vertex
+// #vertex .x .y .z .nx .ny .nz // vertex/position/normal
+// 'end'
+//
+//-----------------------------------------------------------------------------
+
+// TODO - check out lookup_index for whether it's looking for exact vertex matches, or within a float tolerance
+// DONE - lookup_index checks materiaks, coords and texcoords for exact match, and normals for within 2 degrees
+
+const int MAXNAME = 128;
+const int MAXLINE = 4096;
+const int MAXCMD = 1024;
+const int MAXBONEWEIGHTS = 3;
+const int MAXTEXNAME = 64;
+
+void ReadBonesFromSMD( std::vector< CDmeTransform* > &bones, std::istream &is, DmFileId_t fileid )
+{
+ uint index;
+ int parent;
+ char name[ MAXNAME ];
+
+ char line[ MAXLINE ];
+
+ while ( is.getline( line, MAXLINE ) )
+ {
+ if ( sscanf( line, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3 )
+ {
+ if ( index != bones.size() )
+ {
+ Warning( "ReadBonesFromSMD: reading node %d out of order\n", index );
+ }
+ if ( index >= bones.size() )
+ {
+ bones.resize( index + 1 );
+ }
+
+ bones[index] = CreateElement< CDmeTransform >( name, fileid );
+ if ( parent > 0 )
+ {
+ if ( ( uint( parent ) >= bones.size() ) || ( bones[ parent ] == NULL ) )
+ {
+ Warning( "ReadBonesFromSMD: reading node %d before parent\n", index, parent );
+ }
+ else
+ {
+ Assert( 0 ); // this code is so badly bit-rotten...
+// bones[parent]->AddChild( bones[index]->GetHandle() );
+ }
+ }
+ }
+ else
+ {
+ if ( strncmp( line, "end", 3 ) != 0 )
+ {
+ Warning( "ReadBonesFromSMD: expected 'end' or bone, found %s\n", line );
+ }
+ return;
+ }
+ }
+}
+
+void clip_rotations( RadianEuler& rot )
+{
+ // remap rotations to [ -M_PI .. M_PI )
+ for ( int j = 0; j < 3; j++ ) {
+ if ( rot[j] != -M_PI ) // keep -M_PI as is
+ {
+ rot[j] = fmod( (double)rot[j], M_PI );
+ }
+ }
+}
+
+void ReadSkeletalAnimationFromSMD( std::vector< CDmeTransform* > &bones, std::istream &is )
+{
+ char line[ MAXLINE ];
+
+ char cmd[ MAXCMD ];
+
+ int time = INT_MIN;
+ int startframe = -1;
+ int endframe = -1;
+
+#if 1
+ // Root transform
+ matrix3x4_t rootToWorld;
+ SetIdentityMatrix( rootToWorld );
+// GetTransform()->GetTransform( rootToWorld );
+#endif
+
+ while ( is.getline( line, MAXLINE ) )
+ {
+ int index;
+ Vector pos;
+ RadianEuler rot;
+
+ if ( sscanf( line, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7 )
+ {
+ if ( startframe < 0 )
+ {
+ Warning( "ReadSkeletalAnimationFromSMD: missing frame start\n" );
+ }
+
+// clip_rotations( rot );
+ Quaternion quat;
+ AngleQuaternion( rot, quat );
+#if 0
+ matrix3x4_t boneMatrix;
+ QuaternionMatrix( quat, boneMatrix );
+ MatrixSetColumn( pos, 3, boneMatrix );
+
+ if ( bones[index]->NumParents() > 0 )
+ {
+ DmElementHandle_t hParent = bones[index]->GetParent( 0 );
+ CDmeTransform *parentXform = GetElement< CDmeTransform >( hParent );
+ matrix3x4_t parentMatrix, newMatrix;
+ parentXform->GetTransform( parentMatrix );
+// ConcatTransforms( parentMatrix, boneMatrix, newMatrix );
+ SetIdentityMatrix( newMatrix );
+ MatrixAngles( newMatrix, quat, pos );
+ }
+ else
+ {
+ matrix3x4_t parentMatrix, newMatrix;
+// ConcatTransforms( rootToWorld, boneMatrix, newMatrix );
+ SetIdentityMatrix( newMatrix );
+ MatrixAngles( newMatrix, quat, pos );
+ }
+#endif
+ bones[index]->SetValue( "orientation", quat );
+ bones[index]->SetValue( "position", pos );
+
+ // TODO - save animation data - currently just overwriting w/ last frame
+ }
+ else if ( sscanf( line, "%1023s %d", cmd, &index ) )
+ {
+ if ( strcmp( cmd, "time" ) == 0 )
+ {
+ time = index;
+ if ( startframe == -1 )
+ {
+ startframe = index;
+ }
+ if ( time < startframe )
+ {
+ Error( "ReadSkeletalAnimationFromSMD: time %d found after time %d\n", time, startframe );
+ }
+ if ( time > endframe )
+ {
+ endframe = time;
+ }
+ time -= startframe;
+ /*
+ if ( time != anim.size() )
+ {
+ Warning( "ReadSkeletalAnimationFromSMD: reading keyframe %d out of order\n", time );
+ }
+ if ( time >= anim.size() )
+ {
+ anim.resize( time + 1 );
+ anim[time] = new bone_t[nodes.size()];
+ }
+
+ if ( time > 0 )
+ {
+ if ( anim[time-1] )
+ {
+ std::copy( anim[time-1], anim[time-1] + nodes.size(), anim[time] );
+ }
+ else
+ {
+ Warning( "ReadSkeletalAnimationFromSMD: missing skeletal keyframe %d\n", time-1 );
+ }
+ }
+ */
+ }
+ else if ( strcmp( cmd, "end" ) == 0 )
+ {
+// Build_Reference( nodes, anim, matrices ); // skip - leave this for dmemesh generation
+ return;
+ }
+ else
+ {
+ Warning( "ReadSkeletalAnimationFromSMD: expected bone, time or end, found %s\n", line );
+ }
+ }
+ else
+ {
+ Warning( "ReadSkeletalAnimationFromSMD: expected bone, time or end, found %s\n", line );
+ }
+ }
+ Error( "ReadSkeletalAnimationFromSMD: unexpected EOF\n" );
+}
+
+float vertex_t::normal_tolerance = cos( DEG2RAD( 2.0f ));
+
+void SortAndBalanceBones( std::vector< skinning_info_t > &skinning )
+{
+ // TODO - studiomdl collapses (sums) duplicate bone weights - is this necessary?!?!
+
+ std::sort( skinning.begin(), skinning.end() );
+
+ // throw away bone weights < 0.05f
+ while ( skinning.size() > 1 && skinning.back().weight >= 0.05f )
+ {
+ skinning.pop_back();
+ }
+ Assert( !skinning.empty() );
+
+ if ( skinning.size() > MAXBONEWEIGHTS )
+ {
+ skinning.resize( MAXBONEWEIGHTS );
+ }
+
+ float weightSum = 0.0f;
+ for ( uint i = 0; i < skinning.size(); ++i )
+ {
+ weightSum += skinning[i].weight;
+ }
+
+ if ( weightSum <= 0.0f )
+ {
+ for ( uint i = 0; i < skinning.size(); ++i )
+ {
+ skinning[i].weight = weightSum;
+ }
+ }
+ else
+ {
+ float weightScale = 1.0f / weightSum;
+ for ( uint i = 0; i < skinning.size(); ++i )
+ {
+ skinning[i].weight *= weightScale;
+ }
+ }
+}
+
+int ReadVertexFromSMD( std::vector< vertex_t > &vertices, int numbones, std::istream &is )
+{
+ int boneIndex;
+ is >> boneIndex;
+
+ if ( boneIndex < 0 || boneIndex >= numbones )
+ {
+ Error( "ReadVertexFromSMD: invalid bone index: %d\n", boneIndex );
+ }
+
+ vertex_t vert;
+ is >> vert.coord.x >> vert.coord.y >> vert.coord.z;
+ is >> vert.normal.x >> vert.normal.y >> vert.normal.z;
+ is >> vert.texcoord.x >> vert.texcoord.y;
+
+ // invert v
+ vert.texcoord.y = 1.0f - vert.texcoord.y;
+
+ char line[MAXLINE];
+ is.getline( line, MAXLINE );
+ std::istrstream istr( line );
+
+ int nBones = 0;
+ istr >> nBones;
+ Assert( istr.good() || nBones == 0 );
+
+ if ( nBones == 0 )
+ {
+ vert.skinning.push_back( skinning_info_t( boneIndex, 1.0f ) );
+ }
+ else
+ {
+ vert.skinning.reserve( nBones );
+ for ( int i = 0; i < nBones; ++i )
+ {
+ skinning_info_t info;
+ istr >> info.index >> info.weight;
+ vert.skinning.push_back( info );
+
+ if ( info.index < 0 || info.index >= numbones )
+ {
+ Error( "ReadVertexFromSMD: invalid bone index: %d\n", info.index );
+ }
+ }
+ }
+
+ std::vector< vertex_t >::iterator vi = std::find( vertices.begin(), vertices.end(), vert );
+ if ( vi != vertices.end() )
+ return vi - vertices.begin();
+
+ SortAndBalanceBones( vert.skinning );
+
+ vertices.push_back( vert );
+ return vertices.size() - 1;
+}
+
+bool IsEnd( char const* pLine )
+{
+ if ( strncmp( "end", pLine, 3 ) != 0 )
+ return false;
+ return ( pLine[3] == '\0' ) || ( pLine[3] == '\n' );
+}
+
+void ReadTrianglesFromSMD( std::vector< submesh_t* > &meshes, int numbones, std::istream &is )
+{
+ Vector vmin( FLT_MAX, FLT_MAX, FLT_MAX );
+ Vector vmax( -FLT_MAX, -FLT_MAX, -FLT_MAX );
+
+ char line[ MAXLINE ];
+
+ char texname[ MAXTEXNAME ];
+
+ while ( is.getline( line, MAXLINE ) )
+ {
+ if ( IsEnd( line ) )
+ break;
+
+ int lineLen = is.gcount();
+ if ( lineLen >= MAXTEXNAME )
+ {
+ Warning( "ReadTrianglesFromSMD: expected a texture name, found %s\n", line );
+ continue;
+ }
+
+ // the studiomdl comment here is "strip off trailing smag" whatever smag is...
+ strncpy( texname, line, MAXTEXNAME );
+ int i;
+ for ( i = strlen( texname ) - 1; i >= 0 && ! isgraph( texname[i] ); i-- )
+ {
+ }
+ texname[i + 1] = '\0';
+
+ // Skip empty names (studiomdl comment: "weird source problem, skip them")
+ // Skip null texture references
+ if ( texname[0] == '\0' ||
+ stricmp( texname, "null.bmp" ) == 0 ||
+ stricmp( texname, "null.tga" ) == 0 )
+ {
+ is.getline( line, MAXLINE );
+ is.getline( line, MAXLINE );
+ is.getline( line, MAXLINE );
+ continue;
+ }
+
+ // find mesh with matching texture - starting with last one created
+ int mi;
+ for ( mi = meshes.size() - 1; mi >= 0; --mi )
+ {
+ if ( stricmp( meshes[mi]->texname.c_str(), texname ) == 0 )
+ break;
+ }
+
+ // if no mesh with texname found, create a new one
+ if ( mi < 0 )
+ {
+ mi = meshes.size();
+ meshes.push_back( new submesh_t( texname ) );
+ }
+ submesh_t *mesh = meshes[mi];
+
+ mesh->indices.push_back( ReadVertexFromSMD( mesh->vertices, numbones, is ) );
+ mesh->indices.push_back( ReadVertexFromSMD( mesh->vertices, numbones, is ) );
+ mesh->indices.push_back( ReadVertexFromSMD( mesh->vertices, numbones, is ) );
+
+#if 0
+ // flip triangle - the default in studiomdl
+ int numIndices = mesh->indices.size();
+ std::swap( mesh->indices[numIndices-1], mesh->indices[numIndices-2] );
+#endif
+ }
+}
+
+void RemapBonesOnSubmesh( submesh_t *pMesh, std::vector< CDmeTransform* > &bones )
+{
+ std::vector<int> vertsPerBone( bones.size() ); // initializes all counts to 0
+
+ // find vertex-per-bone counts
+ int vn = pMesh->vertices.size();
+ for ( int vi = 0; vi < vn; ++vi )
+ {
+ vertex_t &vert = pMesh->vertices[vi];
+ int bn = vert.skinning.size();
+ for ( int bi = 0; bi < bn; ++bi )
+ {
+ ++vertsPerBone[vert.skinning[bi].index];
+ }
+ }
+
+ std::vector<int> boneMap( bones.size() );
+
+ // copy only used bones into mesh's internal bone list and write mapping
+ int bn = vertsPerBone.size();
+ for ( int bi = 0; bi < bn; ++bi )
+ {
+ if ( vertsPerBone[bi] == 0 )
+ {
+ boneMap[bi] = -1;
+ }
+ else
+ {
+ boneMap[bi] = pMesh->bones.size();
+ pMesh->bones.push_back( bones[bi] );
+ }
+ }
+
+ // remap mesh's verts to use the interal bone indexing
+ for ( int vi = 0; vi < vn; ++vi )
+ {
+ vertex_t &vert = pMesh->vertices[vi];
+ int bn = vert.skinning.size();
+ for ( int bi = 0; bi < bn; ++bi )
+ {
+ vert.skinning[bi].index = boneMap[vert.skinning[bi].index];
+ }
+ }
+}
+
+CDmeTestMesh *CDmeTestMesh::ReadMeshFromSMD( char *pFilename, DmFileId_t fileid )
+{
+ std::ifstream is( pFilename );
+ if ( !is )
+ {
+ Warning( "Unable to open file %s\n", pFilename );
+ return NULL;
+ }
+
+ CDmeTestMesh *pMesh = CreateElement< CDmeTestMesh >( "New Mesh", fileid );
+
+ char line[ MAXLINE ];
+
+ char cmd[ MAXCMD ];
+ int option;
+
+ while ( is.getline( line, MAXLINE ) )
+ {
+ int numRead = sscanf( line, "%1023s %d", cmd, &option );
+
+ if ( ( numRead == EOF ) || ( numRead == 0 ) )
+ continue; // blank line
+
+ if ( strcmp( cmd, "version" ) == 0 )
+ {
+ if ( option != 1 )
+ {
+ Error( "ReadMeshFromSMD: bad version\n" );
+ }
+ }
+ else if ( strcmp( cmd, "nodes" ) == 0 )
+ {
+ pMesh->m_bones.clear();
+ ReadBonesFromSMD( pMesh->m_bones, is, fileid );
+ }
+ else if ( strcmp( cmd, "skeleton" ) == 0 )
+ {
+ ReadSkeletalAnimationFromSMD( pMesh->m_bones, is );
+ }
+ else if ( strcmp( cmd, "triangles" ) == 0 )
+ {
+ ReadTrianglesFromSMD( pMesh->m_submeshes, pMesh->m_bones.size(), is );
+ }
+ else if ( strcmp( cmd, "vertexanimation" ) == 0 )
+ {
+// Grab_Vertexanimation( psource );
+ return pMesh; // TODO - implement Grab_Vertexanimation!!!
+ }
+ else
+ {
+ Warning( "unknown studio command\n" );
+ }
+ }
+
+#if 0
+ // remap only the needed bones to hopefully fit within maxbone contraints
+ int mn = pMesh->m_submeshes.size();
+ for ( int mi = 0; mi < mn; ++mi)
+ {
+ RemapBonesOnSubmesh( pMesh->m_submeshes[mi], pMesh->m_bones );
+ Msg( "remapping %d bones on mesh to %d bones on submesh %d\n",
+ pMesh->m_bones.size(),
+ pMesh->m_submeshes[mi]->bones.size(),
+ mi );
+ }
+#endif
+
+ return pMesh;
+}