Jump to content
  • Advertisement
Sign in to follow this  

Weird mesh!!! ... Should I use .x format?

This topic is 3955 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello everyone. I'm trying to make a casual game using DirectX 9.0c but now I'm getting problem on meshes. I slightly ammended the code from "Advanced Animation with DirectX (Jim Adams)" ,and with the aid of Panda Exporter , my model began to 'rock'. However, when there are more than one objects (parsed in skinned mesh), the model looks terribly bad as shown. The left is the original biped model in 3DSMAx and the right one is the 'dismembered cow' ... Is that the bug from .x file? Or did I do something wrong ? Apart from fixing the above problem, I was wondering if .x format can help me to achieve 3 tasks: 1) changing facial texture (I just want my character can textually blink its eyes ... Any ideas?) 2) allow alpha blending (Actually the white floor had alpha channel with white stroke but the result is completely opaque [Floor.png]) 3) support Cartoon shader (so far I had googled around about shader and I know the basics) Floor.png Should I give up .x file and managed to make home exporter (using IGameExporter) to store the model in my own format? Any help is greatly appreciated. Thanks! <P.S. : How to set the width of the source code tables below? > ------------------------------------------- Enclosed source code: MeshStruct.h
#ifndef _MESH_STRUCT_H_
#define _MESH_STRUCT_H_

struct D3DXMESHCONTAINER_EX;
struct D3DXFRAME_EX;


////////////////////////////////////////////////////////////////////////////////////////
//
// -------------D3DXMESHCONTAINER_EX--------------------------
//
// Declare an extended version of D3DXMESHCONTAINER that contains a constructor
// and destructor as well as an array of textures, a mesh object that contains
// the generated skin mesh, and matrices that map to the frame hierarchy's and
// for updating bones.
//
////////////////////////////////////////////////////////////////////////////////////////
struct D3DXMESHCONTAINER_EX : D3DXMESHCONTAINER
{
  IDirect3DTexture9 **pTextures;
  ID3DXMesh          *pSkinMesh;

  D3DXMATRIX        **ppFrameMatrices;
  D3DXMATRIX         *pBoneMatrices;

public:
	D3DXMESHCONTAINER_EX();
	~D3DXMESHCONTAINER_EX();
	 D3DXMESHCONTAINER_EX *Find(char *MeshName);

};

inline D3DXMESHCONTAINER_EX::D3DXMESHCONTAINER_EX()
  {
    Name               = NULL;
    MeshData.pMesh     = NULL;
    pMaterials         = NULL;
    pEffects           = NULL;
    NumMaterials       = 0;
    pAdjacency         = NULL;
    pSkinInfo          = NULL;
    pNextMeshContainer = NULL;
    pTextures          = NULL;
    pSkinMesh          = NULL;
    ppFrameMatrices    = NULL;
    pBoneMatrices      = NULL;
  }

inline D3DXMESHCONTAINER_EX::~D3DXMESHCONTAINER_EX()
  {
    if(pTextures && NumMaterials) {
      for(DWORD i=0;i<NumMaterials;i++)
        ReleaseCOM(pTextures);
    }
    delete [] pTextures;       pTextures = NULL;
    NumMaterials = 0;

    delete [] Name;            Name = NULL;
    delete [] pMaterials;      pMaterials = NULL;
    delete pEffects;           pEffects = NULL;

    delete [] pAdjacency;      pAdjacency = NULL;
    delete [] ppFrameMatrices; ppFrameMatrices = NULL;
    delete [] pBoneMatrices;   pBoneMatrices = NULL;

    ReleaseCOM(MeshData.pMesh);
    ReleaseCOM(pSkinInfo);
    ReleaseCOM(pSkinMesh);

    delete pNextMeshContainer; pNextMeshContainer = NULL;
  }

inline D3DXMESHCONTAINER_EX *D3DXMESHCONTAINER_EX::Find(char *MeshName)
  {
    D3DXMESHCONTAINER_EX *pMesh, *pMeshPtr;

    // Return this mesh instance if name matched
    if(Name && MeshName && !strcmp(MeshName, Name))
      return this;

    // Scan next in list
    if((pMeshPtr = (D3DXMESHCONTAINER_EX*)pNextMeshContainer)) {
      if((pMesh = pMeshPtr->Find(MeshName)))
        return pMesh;
    }

    // Return none found
    return NULL;
  }


///////////////////////////////////////////////////////////////////////////////////////
//
// -------------D3DXFRAME_EX--------------------------
//
// Declare an extended version of D3DXFRAME
// that contains a constructor and destructor
// as well as a combined transformation matrix
//
///////////////////////////////////////////////////////////////////////////////////////
struct D3DXFRAME_EX : D3DXFRAME 
{
  // Combined matrix :
  // --------------------------------------------------------------------------------
  //   This matrix will be used to make sure that transformation, scaling, or rotation
  //   is passed down the entire hierarchy of the "object".  
  D3DXMATRIX matCombined;   
 
  // Original transformation from .X
  D3DXMATRIX matOriginal;  

public :
  D3DXFRAME_EX();
  ~D3DXFRAME_EX();

  D3DXFRAME_EX *Find(const char *FrameName);
  void Reset();
  void UpdateHierarchy(D3DXMATRIX *matTransformation = NULL);
  void Count(DWORD *Num);

};
  
inline   D3DXFRAME_EX::D3DXFRAME_EX()
  {
    Name = NULL;								   //The name of the frame
    pMeshContainer = NULL;						   //The mesh container
    pFrameSibling = pFrameFirstChild = NULL;	   //A frame on the same level & one level down
    D3DXMatrixIdentity(&matCombined);
    D3DXMatrixIdentity(&matOriginal);
    D3DXMatrixIdentity(&TransformationMatrix);		//The frame transform
  }

inline   D3DXFRAME_EX::~D3DXFRAME_EX()
  { 
    delete [] Name;          Name = NULL;
    delete pFrameSibling;    pFrameSibling = NULL;
    delete pFrameFirstChild; pFrameFirstChild = NULL;
  }

// Function to scan hierarchy for matching frame name
inline D3DXFRAME_EX *D3DXFRAME_EX::Find(const char *FrameName)
  {
    D3DXFRAME_EX *pFrame, *pFramePtr;

    // Return this frame instance if name matched
    if(Name && FrameName && !strcmp(FrameName, Name))
      return this;

    // Scan siblings
    if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling)) {
      if((pFrame = pFramePtr->Find(FrameName)))
        return pFrame;
    }

    // Scan children
    if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild)) {
      if((pFrame = pFramePtr->Find(FrameName)))
        return pFrame;
    }

    // Return none found
    return NULL;
  }

  // Reset transformation matrices to originals
inline void D3DXFRAME_EX::Reset()
  {
    // Copy original matrix
    TransformationMatrix = matOriginal;

    // Reset sibling frames
    D3DXFRAME_EX *pFramePtr;
    if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling))
      pFramePtr->Reset();

    // Reset child frames
    if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild))
      pFramePtr->Reset();
  }

// Function to combine matrices in frame hiearchy
inline void D3DXFRAME_EX::UpdateHierarchy(D3DXMATRIX *matTransformation)
  {
    D3DXFRAME_EX *pFramePtr;
    D3DXMATRIX matIdentity;

    // Use an identity matrix if none passed
    if(!matTransformation) {
      D3DXMatrixIdentity(&matIdentity);
      matTransformation = &matIdentity;
    }

    // Combine matrices w/supplied transformation matrix
    matCombined = TransformationMatrix * (*matTransformation);

    // Combine w/sibling frames
    if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling))
      pFramePtr->UpdateHierarchy(matTransformation);

    // Combine w/child frames
    if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild))
      pFramePtr->UpdateHierarchy(&matCombined);
  }

inline void D3DXFRAME_EX::Count(DWORD *Num)
{
    // Error checking
    if(!Num)
      return;

    // Increase count of frames
    (*Num)+=1;

    // Process sibling frames
    D3DXFRAME_EX *pFrame;
    if((pFrame=(D3DXFRAME_EX*)pFrameSibling))
      pFrame->Count(Num);

    // Process child frames
    if((pFrame=(D3DXFRAME_EX*)pFrameFirstChild))
      pFrame->Count(Num);
}


#endif






Mesh.cpp
#include "GameGlobal.h"


cMesh::cMesh()
{
  // graphics device
  m_Graphics = NULL;

  // init number of meshes & mesh collection 
  m_NumMeshes = 0;
  m_Mesh = NULL;

 // m_PolyCount = 0;

  // init number of frames & frame hierarchy
  m_NumFrames = 0;
  m_Frame = NULL;

  // Calculate bounding area
  m_Min.x = m_Min.y = m_Min.z = 0.0f;
  m_Max.x = m_Max.y = m_Max.z = 0.0f;
  
  // Bounding radius of the object
  m_MeshRadius = 0.0f;

}


cMesh::~cMesh()
{
  Free();
}


BOOL cMesh::Create(cGraphics *Graphics, TCHAR *Filename, TCHAR *TexturePath)
{
  // Free prior mesh object data
  Free();

  // Error checking
  if((m_Graphics = Graphics) == NULL)
	   return FALSE;

  // Error checking
  if(!Filename || !TexturePath)
    return FALSE;

   LoadMesh(&m_Mesh, &m_Frame, Filename, TexturePath);

   ComputeBoundingSphere();

   return TRUE;

}


// Fetch the root frame for animation
D3DXFRAME_EX *cMesh::GetFrame()
{
  if(m_Frame == NULL)
    return NULL;

  return m_Frame;
}


BOOL cMesh::Free()
{
  m_Graphics = NULL;
 
  // Free Meshes Collection
  m_NumMeshes = 0;
  delete m_Mesh;
  m_Mesh = NULL;
 
  // Free Frame Hierarchy
  m_NumFrames = 0;
  delete m_Frame;
  m_Frame = NULL;

  // Reset bounding box 
  m_Min.x = m_Min.y = m_Min.z = 0.0f;
  m_Max.x = m_Max.y = m_Max.z = 0.0f;
  m_MeshRadius = 0.0f;

  return TRUE;
}



HRESULT cMesh::LoadMesh(D3DXMESHCONTAINER_EX **ppMesh,
                 D3DXFRAME_EX **ppFrame,
                 char *Filename,
                 char *TexturePath,
                 DWORD NewFVF,
                 DWORD LoadFlags)
{
  cXInternalParser Parser;

  // Set parser data
  Parser.m_pD3DDevice  = m_Graphics->GetDeviceCOM();
  Parser.m_TexturePath = TexturePath;
  Parser.m_NewFVF      = NewFVF;
  Parser.m_LoadFlags   = LoadFlags;
  Parser.m_Flags       = ((!ppMesh)?0:1) | ((!ppFrame)?0:2);

  // Clear mesh and frame pointers
  Parser.m_RootFrame   = NULL;
  Parser.m_RootMesh    = NULL;

  // Parse the file
  Parser.ParseXFile(Filename);

  // Map the matrices to the frames and create an array of bone 
  // matrices, but only if user passed pointers to receive and 
  // the loader found some meshes and frames.
  if(ppMesh && ppFrame && 
	 Parser.m_RootMesh && 
	 Parser.m_RootFrame) {

    // Scan through all meshes
    D3DXMESHCONTAINER_EX *pMesh = Parser.m_RootMesh;
    while(pMesh) {

      // Does this mesh use skinning?
      if(pMesh->pSkinInfo) {

        // Get the number of bones
        DWORD NumBones = pMesh->pSkinInfo->GetNumBones();

        // Allocate the matrix pointers and bone matrices
        pMesh->ppFrameMatrices = new D3DXMATRIX*[NumBones];
        pMesh->pBoneMatrices   = new D3DXMATRIX[NumBones];

        // Match matrix pointers to frames
        for(DWORD i=0; i<NumBones; i++) {

          // Get bone name
           const char *BoneName = pMesh->pSkinInfo->GetBoneName(i);

          // Find matching name in frames
          D3DXFRAME_EX *pFrame = Parser.m_RootFrame->Find(BoneName);

          // Match frame to bone
          if(pFrame)
            pMesh->ppFrameMatrices = &pFrame->matCombined;
          else
            pMesh->ppFrameMatrices = NULL;
        }
      }

      // Go to next mesh
      pMesh = (D3DXMESHCONTAINER_EX*)pMesh->pNextMeshContainer;
    } 
  }

  // Copy the pointers into passed variables
  if(ppMesh) {
    // Assign mesh list pointer
    *ppMesh = Parser.m_RootMesh;
    Parser.m_RootMesh = NULL;
  } else {
    // Delete list of meshes in case any were loaded
    // and were not needed.
    delete Parser.m_RootMesh;
    Parser.m_RootMesh = NULL;
  }

  if(ppFrame) {
    // Assign frame hierarchy pointer
    *ppFrame = Parser.m_RootFrame;
    Parser.m_RootFrame = NULL;
  } else {
    // Delete frame hierarchy in case it was loaded
    // and it was not needed.
    delete Parser.m_RootFrame;
    Parser.m_RootFrame = NULL;
  }

  return S_OK;
}

BOOL cMesh::ComputeBoundingSphere()
{
  // Get the bounding radius of the object
  ID3DXMesh     *pMeshSysMem = NULL;
  

  D3DXMESHCONTAINER_EX *pMesh = m_Mesh;
  while(pMesh) {

    // Lock the vertex buffer, get its radius, and unlock buffer
    if((pMeshSysMem = pMesh->MeshData.pMesh) != NULL) {
      D3DXVECTOR3 *pVertices, vecCenter;
      float Radius;
      pMeshSysMem->LockVertexBuffer(D3DLOCK_READONLY, (void**)&pVertices);
	  
	  D3DXComputeBoundingBox(pVertices, 
							  pMeshSysMem->GetNumVertices(), 
							  D3DXGetFVFVertexSize(pMeshSysMem->GetFVF()),
							  &m_Min, &m_Max);


      D3DXComputeBoundingSphere(pVertices, 
                                pMeshSysMem->GetNumVertices(),
                                D3DXGetFVFVertexSize(pMeshSysMem->GetFVF()),
                                &vecCenter, &Radius);

      pMeshSysMem->UnlockVertexBuffer();

      // Update radius
      if(Radius > m_MeshRadius)
        m_MeshRadius = Radius; 
    }

    // Go to next mesh
    pMesh = (D3DXMESHCONTAINER_EX*)pMesh->pNextMeshContainer;
  }

  return TRUE;
}


///////////////////////////////////////////////////////////
//
// Update a skinned mesh
//
///////////////////////////////////////////////////////////
HRESULT cMesh::UpdateMesh()
{
  D3DXMESHCONTAINER_EX *pMesh = m_Mesh;

  // Error checking
  if(!pMesh)
    return E_FAIL;
  if(!pMesh->MeshData.pMesh || !pMesh->pSkinMesh || !pMesh->pSkinInfo)
    return E_FAIL;
  if(!pMesh->pBoneMatrices || !pMesh->ppFrameMatrices)
    return E_FAIL;

  // Copy the bone matrices over (must have been combined before call DrawMesh)
  for(DWORD i=0;i<pMesh->pSkinInfo->GetNumBones();i++) {

    // Start with bone offset matrix
    pMesh->pBoneMatrices = (*pMesh->pSkinInfo->GetBoneOffsetMatrix(i));

    // Apply frame transformation
    if(pMesh->ppFrameMatrices)
      pMesh->pBoneMatrices *= (*pMesh->ppFrameMatrices);
  }

  // Lock the meshes' vertex buffers
  void *SrcPtr, *DestPtr;
  pMesh->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&SrcPtr);
  pMesh->pSkinMesh->LockVertexBuffer(0, (void**)&DestPtr);

  // Update the skinned mesh using provided transformations
  pMesh->pSkinInfo->UpdateSkinnedMesh(pMesh->pBoneMatrices, NULL, SrcPtr, DestPtr);

  // Unlock the meshes vertex buffers
  pMesh->pSkinMesh->UnlockVertexBuffer();
  pMesh->MeshData.pMesh->UnlockVertexBuffer();

  // Return success
  return S_OK;
}


///////////////////////////////////////////////////////////
//
// Draw mesh functions
//
///////////////////////////////////////////////////////////
HRESULT cMesh::DrawMesh(D3DXMESHCONTAINER_EX *pMesh)
{
  IDirect3DDevice9 *pD3DDevice;
  DWORD LastState, OldAlphaState, OldSrcBlend, OldDestBlend;

  // Error checking
  if(!pMesh)
    return E_FAIL;
  if(!pMesh->MeshData.pMesh)
    return E_FAIL;
  if(!pMesh->NumMaterials || !pMesh->pMaterials)
    return E_FAIL;

  // Get the device interface
  pMesh->MeshData.pMesh->GetDevice(&pD3DDevice);

  // Release vertex shader if being used
  pD3DDevice->SetVertexShader(NULL);
  pD3DDevice->SetVertexDeclaration(NULL);

  // Save render states
  pD3DDevice->GetRenderState(D3DRS_ALPHABLENDENABLE, &OldAlphaState);
  pD3DDevice->GetRenderState(D3DRS_SRCBLEND, &OldSrcBlend);
  pD3DDevice->GetRenderState(D3DRS_DESTBLEND, &OldDestBlend);
  LastState = OldAlphaState;

  // Setup pointer for mesh to draw, either regular or skinned
  ID3DXMesh *pDrawMesh = (!pMesh->pSkinMesh)?pMesh->MeshData.pMesh:pMesh->pSkinMesh;

  // Look through all subsets
  for(DWORD iMaterial=0; iMaterial<pMesh->NumMaterials; iMaterial++) {

    // Set material and texture
    pD3DDevice->SetMaterial(&pMesh->pMaterials[iMaterial].MatD3D);
    pD3DDevice->SetTexture(0, pMesh->pTextures[iMaterial]);

    // Enable or disable alpha blending per material
    if(pMesh->pMaterials[iMaterial].MatD3D.Diffuse.a != 1.0f) {
      if(LastState != TRUE) {
        LastState = TRUE;
		m_Graphics->EnableAlphaBlending(TRUE, D3DBLEND_SRCCOLOR, D3DBLEND_ONE);

      }
    } else {
      if(LastState != FALSE) {
        LastState = FALSE;
        m_Graphics->EnableAlphaBlending(FALSE);
      }
    }

    // Draw the mesh subset
    pDrawMesh->DrawSubset(iMaterial);
  }

  // Restore alpha blending states
  if(LastState != OldAlphaState) {
    pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, OldAlphaState);
    pD3DDevice->SetRenderState(D3DRS_SRCBLEND, OldSrcBlend);
    pD3DDevice->SetRenderState(D3DRS_DESTBLEND, OldDestBlend);
  }

  // Make sure to release the device object!
  pD3DDevice->Release();

  // Return success
  return S_OK;
}



HRESULT cMesh::DrawMeshVS(D3DXMESHCONTAINER_EX *pMesh,
				 IDirect3DVertexShader9 *pShader, 
                 IDirect3DVertexDeclaration9 *pDecl)
{
  IDirect3DDevice9 *pD3DDevice;
  DWORD LastState, OldAlphaState, OldSrcBlend, OldDestBlend;


  // Error checking
  if(!pMesh || !pShader || !pDecl)
    return E_FAIL;
  if(!pMesh->MeshData.pMesh)
    return E_FAIL;
  if(!pMesh->NumMaterials || !pMesh->pMaterials)
    return E_FAIL;

  // Get the device interface
  pMesh->MeshData.pMesh->GetDevice(&pD3DDevice);

  // Save render states
  pD3DDevice->GetRenderState(D3DRS_ALPHABLENDENABLE, &OldAlphaState);
  pD3DDevice->GetRenderState(D3DRS_SRCBLEND, &OldSrcBlend);
  pD3DDevice->GetRenderState(D3DRS_DESTBLEND, &OldDestBlend);
  LastState = OldAlphaState;

  // Get mesh buffer pointers
  IDirect3DVertexBuffer9 *pVB = NULL;
  IDirect3DIndexBuffer9 *pIB  = NULL;
  pMesh->MeshData.pMesh->GetVertexBuffer(&pVB);
  pMesh->MeshData.pMesh->GetIndexBuffer(&pIB);

  // Get attribute table
  DWORD NumAttributes;
  D3DXATTRIBUTERANGE *pAttributes = NULL;
  pMesh->MeshData.pMesh->GetAttributeTable(NULL, &NumAttributes);
  pAttributes = new D3DXATTRIBUTERANGE[NumAttributes];
  pMesh->MeshData.pMesh->GetAttributeTable(pAttributes, &NumAttributes);

  // Use the vertex shader interface passed
  pD3DDevice->SetFVF(NULL);
  pD3DDevice->SetVertexShader(pShader);
  pD3DDevice->SetVertexDeclaration(pDecl);

  // Set stream sources
  pD3DDevice->SetStreamSource(0, pVB, 0, D3DXGetFVFVertexSize(pMesh->MeshData.pMesh->GetFVF()));
  pD3DDevice->SetIndices(pIB);

  // Go through each attribute group and render
  for(DWORD i=0;i<NumAttributes;i++) {

    if(pAttributes.FaceCount) {

      // Get material number
      DWORD MatNum = pAttributes.AttribId;

      // Set texture
      pD3DDevice->SetTexture(0, pMesh->pTextures[MatNum]);

      // Enable or disable alpha blending per material
      if(pMesh->pMaterials.MatD3D.Diffuse.a != 1.0f) {
        if(LastState != TRUE) {
          LastState = TRUE;
          pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
          pD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);//SRCCOLOR);
          pD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_DESTCOLOR);
        }
      } else {
        if(LastState != FALSE) {
          LastState = FALSE;
          pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
        }
      }

      // Draw the mesh subset
      pD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0,
                                       pAttributes.VertexStart,
                                       pAttributes.VertexCount,
                                       pAttributes.FaceStart * 3,
                                       pAttributes.FaceCount);
    }
  }

  // Clear stream uses
  pD3DDevice->SetStreamSource(0, NULL, 0, 0);
  pD3DDevice->SetIndices(NULL);

  // Free resources
  ReleaseCOM(pVB);
  ReleaseCOM(pIB);
  delete [] pAttributes;

  // Restore alpha blending states
  if(LastState != OldAlphaState) {
    pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, OldAlphaState);
    pD3DDevice->SetRenderState(D3DRS_SRCBLEND, OldSrcBlend);
    pD3DDevice->SetRenderState(D3DRS_DESTBLEND, OldDestBlend);
  }

  // Make sure to release the device object!
  pD3DDevice->Release();

  // Release vertex shader and declaration mapping
  pD3DDevice->SetVertexShader(NULL);
  pD3DDevice->SetVertexDeclaration(NULL);

  return S_OK;
}



HRESULT cMesh::Render()
{
  D3DXMESHCONTAINER_EX *MeshPtr = m_Mesh;

  // Loop through all meshes in list
  while(MeshPtr) {

    // Draw mesh, returning on error
    HRESULT hr = DrawMesh(MeshPtr);
    if(FAILED(hr))
      return hr;

    // Go to next mesh
    MeshPtr = (D3DXMESHCONTAINER_EX*)MeshPtr->pNextMeshContainer;
  }

  // Return success
  return S_OK;
}
/*
HRESULT cMesh::DrawMeshes(D3DXMESHCONTAINER_EX *pMesh,
                   IDirect3DVertexShader9 *pShader,
                   IDirect3DVertexDeclaration9 *pDecl)
{
  D3DXMESHCONTAINER_EX *MeshPtr = pMesh;

  // Loop through all meshes in list
  while(MeshPtr) {

    // Draw mesh, returning on error
    HRESULT hr = DrawMesh(MeshPtr, pShader, pDecl);
    if(FAILED(hr))
      return hr;

    // Go to next mesh
    MeshPtr = (D3DXMESHCONTAINER_EX*)MeshPtr->pNextMeshContainer;
  }

  // Return success
  return S_OK;
}
*/



///////////////////////////////////////////////////////////
//
// Load a vertex shader function
//
///////////////////////////////////////////////////////////
HRESULT cMesh::LoadVertexShader(IDirect3DVertexShader9 **ppShader, 
                                char *Filename,
								D3DVERTEXELEMENT9 *pElements,
								IDirect3DVertexDeclaration9 **ppDecl)
{
  HRESULT hr;

  // Error checking
  if(!ppShader || !Filename)
    return E_FAIL;

  // Load and assemble the shader
  ID3DXBuffer *pCode;
  if(FAILED(hr=D3DXAssembleShaderFromFile(Filename, NULL, NULL, 0, &pCode, NULL)))
    return hr;
  if(FAILED(hr=m_Graphics->GetDeviceCOM()->CreateVertexShader((DWORD*)pCode->GetBufferPointer(), ppShader)))
    return hr;
  pCode->Release();

  // Create the declaration interface if needed
  if(pElements && ppDecl)
    m_Graphics->GetDeviceCOM()->CreateVertexDeclaration(pElements, ppDecl);

  // Return success
  return S_OK;
}



// Fetch the value of bounding box and bounding sphere for collision detection
BOOL cMesh::GetBounds(D3DXVECTOR3 *vMin,  D3DXVECTOR3 *vMax,  float *Radius)
{
	if(vMin != NULL){
	    *vMin = D3DXVECTOR3(m_Min.x, m_Min.y, m_Min.z);
	}

	if(vMax != NULL){
	    *vMax = D3DXVECTOR3(m_Max.x, m_Max.y, m_Max.z);
	}

    if(Radius != NULL)
	  *Radius = m_MeshRadius;

  return TRUE;
}



///////////////////////////////////////////////////////////
//
// Generic .X parser class code
//
///////////////////////////////////////////////////////////
cXInternalParser::cXInternalParser()
{
  m_pD3DDevice  = NULL;
  m_TexturePath = NULL;
  m_Flags       = 0;
  m_RootMesh    = NULL;
  m_RootFrame   = NULL;
}

cXInternalParser::~cXInternalParser()
{
  delete m_RootMesh;  m_RootMesh  = NULL;
  delete m_RootFrame; m_RootFrame = NULL;
}


BOOL cXInternalParser::ParseXFile(char *Filename, void **Data)
{
  ID3DXFile           *pDXFile = NULL;
  ID3DXFileEnumObject *pDXEnum = NULL;
  ID3DXFileData       *pDXData = NULL;

  // Error checking
  if(Filename == NULL)
    return FALSE;

  // Create the file object
  if(FAILED(D3DXFileCreate(&pDXFile)))
    return FALSE;

  // Register the common templates
  if(FAILED(pDXFile->RegisterTemplates(                                            (LPVOID)D3DRM_XTEMPLATES,                                     D3DRM_XTEMPLATE_BYTES))) {
    pDXFile->Release();
    return FALSE;
  }

  // Create an enumeration object
  if(FAILED(pDXFile->CreateEnumObject((LPVOID)Filename,                                             DXFILELOAD_FROMFILE,                                          &pDXEnum))) {
    pDXFile->Release();
    return FALSE;
  }
  
//  if(BeginParse(Data) == TRUE) 
  {
	  // Loop through all top-level objects, breaking on errors
	  BOOL ParseResult;
	  SIZE_T nChildren = 0;
	  pDXEnum->GetChildren(&nChildren);
	  for(DWORD childCount = 0; childCount < nChildren; ++childCount)
	  {
			pDXEnum->GetChild(childCount, &pDXData);
			ParseResult = ParseObject(pDXData, NULL, 0, Data, FALSE);
			ReleaseCOM(pDXData);
			if(ParseResult == FALSE)
				break;
	  }
  }
//  EndParse(Data);

  // Release used COM objects
  ReleaseCOM(pDXEnum);
  ReleaseCOM(pDXFile);

  return TRUE;
}



BOOL cXInternalParser::ParseObject(ID3DXFileData *pDataObj,
                                     ID3DXFileData *pParentDataObj,
                                     DWORD Depth,
                                     void **Data, BOOL Reference)
{ 
  GUID Type = GUID_NULL;

  // Get the template type
  if(FAILED(pDataObj->GetType(&Type)))
    return NULL;


  // Process templates based on their type

  // Build on to frame hierarchy (ony non-referenced frames)
  if(Type == TID_D3DRMFrame && Reference == FALSE && m_Flags & 2) {

	if( pDataObj->IsReference() )
		return TRUE;

    // Allocate a frame
    D3DXFRAME_EX *pFrame = new D3DXFRAME_EX();

    // Get the frame's name (if any)
    pFrame->Name = GetObjectName(pDataObj);

    // Link frame into hierarchy
    if(Data == NULL) {
      // Link as sibling of root
      pFrame->pFrameSibling = m_RootFrame;
      m_RootFrame = pFrame; 
	  pFrame = NULL;

      Data = (void**)&m_RootFrame;

    } else {

      // Link as child of supplied frame
      D3DXFRAME_EX *pFramePtr = (D3DXFRAME_EX*)*Data;
      pFrame->pFrameSibling = pFramePtr->pFrameFirstChild;
      pFramePtr->pFrameFirstChild = pFrame; 
	  pFrame = NULL;

      Data = (void**)&pFramePtr->pFrameFirstChild;
    }
  }


  // Set a frame transformation matrix
  if(Type == TID_D3DRMFrameTransformMatrix && Reference == FALSE && m_Flags & 2 && Data) {

	D3DXFRAME_EX *pDataFrame = ( D3DXFRAME_EX* )( *Data ); 
		
	// fetch the size of the data buffer (4x4 matrix)
	DWORD size = 0;
	LPCVOID buffer = NULL;
	if( FAILED ( pDataObj->Lock( &size, &buffer )))
		return false;

	// copy data
	if( size == sizeof( D3DXMATRIX ) )
	{
		memcpy( &pDataFrame->TransformationMatrix, buffer, size );
		pDataObj->Unlock(); 
		pDataFrame->matOriginal = pDataFrame->TransformationMatrix;
	}

  }

  // Load a mesh (skinned or regular)
  if(Type == TID_D3DRMMesh && m_Flags & 1) {

    // Only load non-referenced skin meshes into memory
    if(Reference == FALSE) {
      
      // Load the mesh using the data object load method
      D3DXMESHCONTAINER_EX *pMesh = NULL;
      LoadMeshData(&pMesh, pDataObj, m_TexturePath, m_NewFVF, m_LoadFlags);

      // Link mesh to head of list of meshes
      if(pMesh) {
        pMesh->pNextMeshContainer = m_RootMesh;
        m_RootMesh = pMesh; pMesh = NULL;

        // Link mesh to frame if needed
        if(Data) {
          D3DXFRAME_EX *pFrame = (D3DXFRAME_EX*)*Data;
          if(m_Flags & 2 && pFrame)
            pFrame->pMeshContainer = m_RootMesh;
        }
      }
    } else {

      // If referenced, then check if wanting to link to frame
      if(Data) {
        D3DXFRAME_EX *pFrame = (D3DXFRAME_EX*)*Data;
        if(m_Flags & 2 && m_RootMesh && pFrame) {

          // Get name of mesh reference to link to
          char *Name = GetObjectName(pDataObj);
          if(Name) {
            // Find matching mesh by name and store result
            pFrame->pMeshContainer = m_RootMesh->Find(Name);

            // Clear name
            delete [] Name; Name = NULL;
          }
        }
      }
    }
  }

  // Parse child templates
  return ParseChildObjects(pDataObj, Depth, Data, Reference);
}



BOOL cXInternalParser::ParseChildObjects(ID3DXFileData *pDataObj,
								 DWORD Depth, 
								 void **Data,
								 BOOL ForceReference)

{
  ID3DXFileData *pSubData = NULL;
  BOOL  ParseResult = TRUE;
  BOOL  bReference = FALSE;
  
  DWORD nChildObjects;
  pDataObj->GetChildren(&nChildObjects);

  for(UINT nChildCount = 0; nChildCount < nChildObjects; ++nChildCount)
  {
    pDataObj->GetChild(nChildCount, &pSubData);
    if(pDataObj->IsReference())
      bReference = true;
    else
      bReference = ForceReference;

    ParseResult = ParseObject(pSubData, pDataObj, Depth+1, Data, bReference);
    ReleaseCOM(pSubData);

    if(!ParseResult)
      return FALSE;
  }
  return TRUE;
}


char *cXInternalParser::GetObjectName(ID3DXFileData *pDataObj)
{
  char  *Name = NULL;
  DWORD  Size = 0;

  // Error checking
  if(pDataObj == NULL)
    return NULL;

  // Get the template name (if any)
  if(FAILED(pDataObj->GetName(NULL, &Size)))
    return NULL;

  // Allocate a name buffer and retrieve name
  if(Size) {
    if((Name = new char[Size]) != NULL)
      pDataObj->GetName(Name, &Size);
  }

  return Name;
}


HRESULT cXInternalParser::LoadMeshData(D3DXMESHCONTAINER_EX **ppMesh,
							 ID3DXFileData *pDataObj,
							 char *TexturePath,
							 DWORD NewFVF,
							 DWORD LoadFlags)
{
  ID3DXMesh *pLoadMesh = NULL;
  ID3DXSkinInfo *pSkin = NULL;
  HRESULT hr;

  // Error checking
  if(!ppMesh || !pDataObj || !TexturePath)
    return E_FAIL;

  // Use system memory if converting FVF
  DWORD TempLoadFlags = LoadFlags;
  if(NewFVF)
    TempLoadFlags = D3DXMESH_SYSTEMMEM;

  // Load the mesh using the D3DX skinned mesh interface
  ID3DXBuffer *MaterialBuffer = NULL, *AdjacencyBuffer = NULL;
  DWORD NumMaterials;
  if(FAILED(hr=D3DXLoadSkinMeshFromXof(pDataObj, TempLoadFlags,
                                       m_pD3DDevice,
									   &AdjacencyBuffer,
                                       &MaterialBuffer, NULL,
                                       &NumMaterials, &pSkin,
                                       &pLoadMesh)))
    return hr;

  // Free skin info if no bones
  if(pSkin && !pSkin->GetNumBones())
    ReleaseCOM(pSkin);

  // Convert to new FVF first as needed (not w/skinned models)
  if(NewFVF) {
    ID3DXMesh *pTempMesh = NULL;

    // Use CloneMeshFVF to convert mesh
    if(FAILED(hr=pLoadMesh->CloneMeshFVF(LoadFlags, NewFVF, m_pD3DDevice, &pTempMesh))) {
      ReleaseCOM(pLoadMesh);
      ReleaseCOM(pSkin);
      ReleaseCOM(MaterialBuffer);
      ReleaseCOM(AdjacencyBuffer);
      return hr;
    }

    // Free prior mesh and store new pointer
    ReleaseCOM(pLoadMesh);
    pLoadMesh = pTempMesh; pTempMesh = NULL;
  }
 
  // Allocate a D3DXMESHCONTAINER_EX structure
  D3DXMESHCONTAINER_EX *pMesh = new D3DXMESHCONTAINER_EX();
  *ppMesh = pMesh;

  // Store mesh template name, type, and mesh pointers
  DWORD Size;
  pDataObj->GetName(NULL, &Size);
  if(Size) {
    pMesh->Name = new char[Size];
    pDataObj->GetName(pMesh->Name, &Size);
  }
  pMesh->MeshData.Type = D3DXMESHTYPE_MESH;
  pMesh->MeshData.pMesh = pLoadMesh; pLoadMesh = NULL;
  pMesh->pSkinInfo = pSkin; pSkin = NULL;

  // Store adjacency buffer
  DWORD AdjSize = AdjacencyBuffer->GetBufferSize();
  if(AdjSize) {
    pMesh->pAdjacency = (DWORD*)new char[AdjSize];
    memcpy(pMesh->pAdjacency, AdjacencyBuffer->GetBufferPointer(), AdjSize);
  }
  ReleaseCOM(AdjacencyBuffer);

  // Create a duplicate mesh in case skinning is used
  if(pMesh->pSkinInfo)
    pMesh->MeshData.pMesh->CloneMeshFVF(0, //D3DXMESH_MANAGED, 
                                        pMesh->MeshData.pMesh->GetFVF(), 
                                        m_pD3DDevice, &pMesh->pSkinMesh);

  // Build material list
  if(!(pMesh->NumMaterials = NumMaterials)) {
    
    // Create a default material
    pMesh->NumMaterials = 1;
    pMesh->pMaterials   = new D3DXMATERIAL[1];
    pMesh->pTextures    = new IDirect3DTexture9*[1];

    ZeroMemory(&pMesh->pMaterials[0], sizeof(D3DXMATERIAL));
    pMesh->pMaterials[0].MatD3D.Diffuse.r = 1.0f;
    pMesh->pMaterials[0].MatD3D.Diffuse.g = 1.0f;
    pMesh->pMaterials[0].MatD3D.Diffuse.b = 1.0f;
    pMesh->pMaterials[0].MatD3D.Diffuse.a = 1.0f;
    pMesh->pMaterials[0].MatD3D.Ambient   = pMesh->pMaterials[0].MatD3D.Diffuse;
    pMesh->pMaterials[0].MatD3D.Specular  = pMesh->pMaterials[0].MatD3D.Diffuse;
    pMesh->pMaterials[0].pTextureFilename = NULL;
    pMesh->pTextures[0]                   = NULL;

  } else {

    // Load the materials
    D3DXMATERIAL *Materials = (D3DXMATERIAL*)MaterialBuffer->GetBufferPointer();
    pMesh->pMaterials = new D3DXMATERIAL[pMesh->NumMaterials];
    pMesh->pTextures  = new IDirect3DTexture9*[pMesh->NumMaterials];

    for(DWORD i=0;i<pMesh->NumMaterials;i++) {
      pMesh->pMaterials.MatD3D = Materials.MatD3D;
      pMesh->pMaterials.MatD3D.Ambient = pMesh->pMaterials.MatD3D.Diffuse;

      // Load the texture if one exists
      pMesh->pTextures = NULL;
      if(Materials.pTextureFilename) {
        char TextureFile[MAX_PATH];
        sprintf_s(TextureFile, "%s%s", TexturePath, 
                             Materials.pTextureFilename);
        D3DXCreateTextureFromFile(m_pD3DDevice,
                                  TextureFile,
                                  &pMesh->pTextures);
      }
    }
  }
  ReleaseCOM(MaterialBuffer);

  // Optimize the mesh for better attribute access
  pMesh->MeshData.pMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL);

  // Clear pMesh pointer just in case
  pMesh = NULL;

  return S_OK;
}








Mesh.h
#ifndef _DIRECT3D_H_
#define _DIRECT3D_H_

#include "GameGlobal.h"
#include "dxfile.h"
#include "XFile.h"


// Declare an internal .X file parser class for loading meshes and frames
class cXInternalParser
{
  public:
    // Information passed from calling function
    IDirect3DDevice9     *m_pD3DDevice;
    char                 *m_TexturePath;
    DWORD                 m_NewFVF;
    DWORD                 m_LoadFlags;

    // Flags for which data to load
    // 1 = mesh, 2 = frames, 3= both
    DWORD                 m_Flags;

    // Hierarchies used during loading
    D3DXMESHCONTAINER_EX *m_RootMesh;
    D3DXFRAME_EX         *m_RootFrame;

  protected:
    // Function called for every template found
    BOOL ParseObject(ID3DXFileData *pDataObj,
                       ID3DXFileData *pParentDataObj,
                       DWORD Depth,
                       void **Data, BOOL Reference);

    // Function called to enumerate child templates
    BOOL ParseChildObjects(ID3DXFileData *pDataObj,
                             DWORD Depth, void **Data,
                             BOOL ForceReference = FALSE);

  public:
    // Constructor and destructor
    cXInternalParser();
    ~cXInternalParser();

    // Function to start parsing an .X file
    BOOL ParseXFile(char *Filename, void **Data = NULL);

    // Functions to help retrieve template information
    //GUID GetObjectGUID(ID3DXFileData *pDataObj);
    char *GetObjectName(ID3DXFileData *pDataObj);
   // void *GetObjectData(ID3DXFileData *pDataObj, DWORD *Size);

	// Load a single mesh (regular or skinned) from a mesh template
	HRESULT LoadMeshData(D3DXMESHCONTAINER_EX **ppMesh,
						 ID3DXFileData *pDataObj,
						 char *TexturePath = ".\\",
						 DWORD NewFVF = 0,
						 DWORD LoadFlags = D3DXMESH_SYSTEMMEM);

};




class cMesh
{
  public:
    cGraphics			*m_Graphics;
	
	// Mesh collection (regular mesh/ skinned mesh/ progressive mesh)
	// Mesh collection and frame hierarchy
	D3DXMESHCONTAINER_EX   *m_Mesh;
	D3DXFRAME_EX		   *m_Frame;

    // Store the Num of mesh collection & frame hierarchy 
	long       m_NumMeshes;		// Local version of mesh, copied on resize
	long       m_NumFrames;

	// Bounding Box & bounding Radius for collision detection
    D3DXVECTOR3    m_Min, m_Max;
    float          m_MeshRadius;
  

  public:
    cMesh();
    ~cMesh();


	D3DXFRAME_EX *GetFrame();

    BOOL    Create(cGraphics *Graphics, TCHAR *Filename, TCHAR *TexturePath = ".\\");


	// Load all meshes and frames from an .X file
	HRESULT LoadMesh(D3DXMESHCONTAINER_EX **ppMesh,
					 D3DXFRAME_EX **ppFrame,
					 char *Filename,
					 char *TexturePath = ".\\",
					 DWORD NewFVF = 0,
					 DWORD LoadFlags = D3DXMESH_SYSTEMMEM);

	// Load a vertex shader
	HRESULT LoadVertexShader(IDirect3DVertexShader9 **ppShader, 
							 char *Filename,
							 D3DVERTEXELEMENT9 *pElements = NULL,
							 IDirect3DVertexDeclaration9 **ppDecl = NULL);



	// Update a skinned mesh
	HRESULT UpdateMesh();

	// Draw the first mesh in a linked list of objects
	HRESULT DrawMesh(D3DXMESHCONTAINER_EX *pMesh);

	// Draw the first mesh in a linked list of objects
	// using the specified vertex shader and declaration
	HRESULT DrawMeshVS(D3DXMESHCONTAINER_EX *pMesh,
					 IDirect3DVertexShader9 *pShader,
					 IDirect3DVertexDeclaration9 *pDecl);

	// Draw all meshes in a linked list of objects
	HRESULT Render();

	// Draw all meshes in a linked list of objects
	// using the specified vertex shader and declaration
//	HRESULT RenderVS(  IDirect3DVertexShader9 *pShader,
//					   IDirect3DVertexDeclaration9 *pDecl);

	BOOL ComputeBoundingSphere();

	BOOL GetBounds(D3DXVECTOR3 *vMin,  D3DXVECTOR3 *vMax,  float *Radius);


	BOOL Free();
};


#endif







SkeletalAnim.cpp
#include "GameGlobal.h"


cAnimation::cAnimation()
{
  m_Name = NULL;
  m_Bone = NULL;
  m_Next = NULL;

  m_NumTranslationKeys = 0;
  m_NumScaleKeys       = 0;
  m_NumRotationKeys    = 0;
  m_NumMatrixKeys      = 0;

  m_TranslationKeys = NULL;
  m_ScaleKeys       = NULL;
  m_RotationKeys    = NULL;
  m_MatrixKeys      = NULL;
}

cAnimation::~cAnimation()
{
  delete [] m_Name; m_Name = NULL;
  delete m_Next;    m_Next = NULL;
  delete [] m_TranslationKeys;
  delete [] m_ScaleKeys;
  delete [] m_RotationKeys;
  delete [] m_MatrixKeys;
}

cAnimationSet::cAnimationSet()
{
  m_Name          = NULL;
  m_Length        = 0;
  m_Next          = NULL;
  m_NumAnimations = 0;
  m_Animations    = NULL;
}

cAnimationSet::~cAnimationSet()
{
  delete [] m_Name;    m_Name = NULL;
  delete m_Next;       m_Next = NULL;
  delete m_Animations; m_Animations = NULL;
}

cAnimationCollection::cAnimationCollection()
{
  m_NumAnimationSets = 0;
  m_AnimationSets    = NULL;
}

cAnimationCollection::~cAnimationCollection()
{
  Free();
}


BOOL cAnimationCollection::ParseObject(IDirectXFileData *pDataObj,IDirectXFileData *pParentDataObj,DWORD Depth, void **Data, BOOL Reference)
{
  const GUID *Type = GetObjectGUID(pDataObj);
  DWORD i;

  // Check if template is AnimationSet type
  if(*Type == TID_D3DRMAnimationSet) {

    // Create and link in a cAnimationSet object
    cAnimationSet *AnimSet = new cAnimationSet();
    AnimSet->m_Next = m_AnimationSets;
    m_AnimationSets = AnimSet;

    // Increase # of animation sets
    m_NumAnimationSets++;

    // Set animation set name
    AnimSet->m_Name = GetObjectName(pDataObj);
  }

  // Check if template is Animation type
  if(*Type == TID_D3DRMAnimation && m_AnimationSets) {

    // Add a cAnimation class to top-level cAnimationSet
    cAnimation *Anim = new cAnimation();
    Anim->m_Next = m_AnimationSets->m_Animations;
    m_AnimationSets->m_Animations = Anim;

    // Increase # of animations
    m_AnimationSets->m_NumAnimations++;
  }

  // Check if a frame reference inside animation template
  if(*Type == TID_D3DRMFrame && Reference == TRUE && m_AnimationSets &&  m_AnimationSets->m_Animations) {

    // Make sure parent object is an Animation template
    if(pParentDataObj && *GetObjectGUID(pParentDataObj) == TID_D3DRMAnimation) {

      // Get name of frame and store it as animation
      m_AnimationSets->m_Animations->m_Name = GetObjectName(pDataObj);
    }

    // Don't process child of reference frames
    return TRUE;
  }

  // Check if template is AnimationKey type
  if(*Type == TID_D3DRMAnimationKey && m_AnimationSets &&                                 m_AnimationSets->m_Animations) {

    // Get a pointer to top-level animation object
    cAnimation *CurrentAnim = m_AnimationSets->m_Animations;

    // Get a data pointer
    DWORD *DataPtr = (DWORD*)GetObjectData(pDataObj, NULL);

    // Get key type
    DWORD Type = *DataPtr++;

    // Get # of keys to follow
    DWORD NumKeys = *DataPtr++;

    // Branch based on key type
    switch(Type) {
      case 0: // Rotation
        delete [] CurrentAnim->m_RotationKeys;
	
        CurrentAnim->m_NumRotationKeys = NumKeys;
        
		if ((CurrentAnim->m_RotationKeys = new cAnimationQuaternionKey[NumKeys]) == NULL){
			return FALSE;
		}
        for(i=0;i<NumKeys;i++) {
          // Get time
          CurrentAnim->m_RotationKeys.m_Time = *DataPtr++;
          if(CurrentAnim->m_RotationKeys.m_Time > m_AnimationSets->m_Length)
            m_AnimationSets->m_Length = CurrentAnim->m_RotationKeys.m_Time;

          // Skip # keys to follow (should be 4)
          DataPtr++;

          // Get rotational values
          float *fPtr = (float*)DataPtr;
          CurrentAnim->m_RotationKeys.m_quatKey.w = *fPtr++;
          CurrentAnim->m_RotationKeys.m_quatKey.x = *fPtr++;
          CurrentAnim->m_RotationKeys.m_quatKey.y = *fPtr++;
          CurrentAnim->m_RotationKeys.m_quatKey.z = *fPtr++;
          DataPtr+=4;
        }
        break;

      case 1: // Scaling
        delete [] CurrentAnim->m_ScaleKeys;
        CurrentAnim->m_NumScaleKeys = NumKeys;
		if ((CurrentAnim->m_ScaleKeys = new cAnimationVectorKey[NumKeys]) == NULL){
			return FALSE;
		}
        for(i=0;i<NumKeys;i++) {
          // Get time
          CurrentAnim->m_ScaleKeys.m_Time = *DataPtr++;
          if(CurrentAnim->m_ScaleKeys.m_Time > m_AnimationSets->m_Length)
            m_AnimationSets->m_Length = CurrentAnim->m_ScaleKeys.m_Time;

          // Skip # keys to follow (should be 3)
          DataPtr++;

          // Get scale values
          D3DXVECTOR3 *vecPtr = (D3DXVECTOR3*)DataPtr;
          CurrentAnim->m_ScaleKeys.m_vecKey = *vecPtr;
          DataPtr+=3;
        }
        break;

      case 2: // Translation
        delete [] CurrentAnim->m_TranslationKeys;
        CurrentAnim->m_NumTranslationKeys = NumKeys;
		if ((CurrentAnim->m_TranslationKeys = new cAnimationVectorKey[NumKeys]) == NULL){
			return FALSE;
		}
        for(i=0;i<NumKeys;i++) {
          // Get time
          CurrentAnim->m_TranslationKeys.m_Time = *DataPtr++;
          if(CurrentAnim->m_TranslationKeys.m_Time >m_AnimationSets->m_Length)
            m_AnimationSets->m_Length = CurrentAnim->m_TranslationKeys.m_Time;

          // Skip # keys to follow (should be 3)
          DataPtr++;

          // Get translation values
          D3DXVECTOR3 *vecPtr = (D3DXVECTOR3*)DataPtr;
          CurrentAnim->m_TranslationKeys.m_vecKey = *vecPtr;
          DataPtr+=3;
        }
        break;

      case 4: // Transformation matrix
        delete [] CurrentAnim->m_MatrixKeys;
        CurrentAnim->m_NumMatrixKeys = NumKeys;
        if ((CurrentAnim->m_MatrixKeys = new cAnimationMatrixKey[NumKeys]) == NULL){
			return FALSE;
		}
        for(i=0;i<NumKeys;i++) {
          // Get time
          CurrentAnim->m_MatrixKeys.m_Time = *DataPtr++;

          if(CurrentAnim->m_MatrixKeys.m_Time >m_AnimationSets->m_Length)
            m_AnimationSets->m_Length = CurrentAnim->m_MatrixKeys.m_Time;

          // Skip # keys to follow (should be 16)
          DataPtr++;

          // Get matrix values
          D3DXMATRIX *mPtr = (D3DXMATRIX *)DataPtr;
          CurrentAnim->m_MatrixKeys.m_matKey = *mPtr;
          DataPtr += 16;
        }
        break;
    }
  }

  return ParseChildObjects(pDataObj, Depth, Data, Reference);
}

BOOL cAnimationCollection::Load(char *Filename)
{
  // Free a prior loaded collection
  Free();

  // Parse the file
  return Parse(Filename);
}

void cAnimationCollection::Free()
{
  m_NumAnimationSets = 0;
  delete m_AnimationSets; m_AnimationSets = NULL;
}

void cAnimationCollection::Map(D3DXFRAME_EX *RootFrame)
{
  // Go through each animation set
  cAnimationSet *AnimSet = m_AnimationSets;
  while(AnimSet != NULL) {

    // Go through each animation object
    cAnimation *Anim = AnimSet->m_Animations;
    while(Anim != NULL) {

      // Go through all frames and look for match
      Anim->m_Bone = RootFrame->Find(Anim->m_Name);

      // Go to next animation object
      Anim = Anim->m_Next;
    }

    // Go to next animation set object
    AnimSet = AnimSet->m_Next;
  }
}


void cAnimationCollection::Update(char *AnimationSetName,                                       DWORD Time, BOOL Loop, float Blend)
{
  cAnimationSet *AnimSet = m_AnimationSets;

  // Look for matching animation set name if used
  if(AnimationSetName) {

    // Find matching animation set name
    while(AnimSet != NULL) {

      // Break when match found
      if(!_stricmp(AnimSet->m_Name, AnimationSetName))
        break;

      // Go to next animation set object
      AnimSet = AnimSet->m_Next;
    }
  }

  // Return no set found
  if(AnimSet == NULL)
    return;

  // Bounds time to animation length
  if(Time > AnimSet->m_Length)
    Time = (Loop==TRUE)?Time%(AnimSet->m_Length+1):AnimSet->m_Length;

  // Go through each animation
  cAnimation *Anim = AnimSet->m_Animations;
  while(Anim) {

    // Only process if it's attached to a bone
    if(Anim->m_Bone) {

      // Reset transformation
      D3DXMATRIX matAnimation;
      D3DXMatrixIdentity(&matAnimation);

      // Apply various matrices to transformation

	  // Rotation
      if(Anim->m_NumRotationKeys && Anim->m_RotationKeys) {

        // Loop for matching rotation key
        DWORD Key1 = 0, Key2 = 0;
        for(DWORD i=0;i<Anim->m_NumRotationKeys;i++) {
          if(Time >= Anim->m_RotationKeys.m_Time)
            Key1 = i;
        }

        // Get 2nd key number
        Key2 = (Key1>=(Anim->m_NumRotationKeys-1))?Key1:Key1+1;

        // Get difference in keys' times
        DWORD TimeDiff = Anim->m_RotationKeys[Key2].m_Time-
                         Anim->m_RotationKeys[Key1].m_Time;
        if(!TimeDiff)
          TimeDiff = 1;

        // Calculate a scalar value to use
        float Scalar = (float)(Time - Anim->m_RotationKeys[Key1].m_Time) / (float)TimeDiff;

        // slerp rotation values
        D3DXQUATERNION quatRotation;
        D3DXQuaternionSlerp(&quatRotation,
                            &Anim->m_RotationKeys[Key1].m_quatKey,
                            &Anim->m_RotationKeys[Key2].m_quatKey,
                            Scalar);

        // Create rotation matrix and combine with transformation
        D3DXMATRIX matRotation;
        D3DXMatrixRotationQuaternion(&matRotation, &quatRotation);
        matAnimation *= matRotation;
      }

      // Scaling
      if(Anim->m_NumScaleKeys && Anim->m_ScaleKeys) {

        // Loop for matching scale key
        DWORD Key1 = 0, Key2 = 0;
        for(DWORD i=0; i<Anim->m_NumScaleKeys; i++) {
          if(Time >= Anim->m_ScaleKeys.m_Time)
            Key1 = i;
        }

        // Get 2nd key number
        Key2 = (Key1>=(Anim->m_NumScaleKeys-1))?Key1:Key1+1;

        // Get difference in keys' times
        DWORD TimeDiff = Anim->m_ScaleKeys[Key2].m_Time-
                         Anim->m_ScaleKeys[Key1].m_Time;
        if(!TimeDiff)
          TimeDiff = 1;

        // Calculate a scalar value to use
        float Scalar = (float)(Time - Anim->m_ScaleKeys[Key1].m_Time) / (float)TimeDiff;

        // Calculate interpolated scale values
        D3DXVECTOR3 vecScale = Anim->m_ScaleKeys[Key2].m_vecKey - 
                               Anim->m_ScaleKeys[Key1].m_vecKey;
        vecScale *= Scalar;
        vecScale += Anim->m_ScaleKeys[Key1].m_vecKey;

        // Create scale matrix and combine with transformation
        D3DXMATRIX matScale;
        D3DXMatrixScaling(&matScale, vecScale.x, vecScale.y, vecScale.z);
        matAnimation *= matScale;
      }

      

      // Translation
      if(Anim->m_NumTranslationKeys && Anim->m_TranslationKeys) {

        // Loop for matching translation key
        DWORD Key1 = 0, Key2 = 0;
        for(DWORD i=0;i<Anim->m_NumTranslationKeys;i++) {
          if(Time >= Anim->m_TranslationKeys.m_Time)
            Key1 = i;
        }

        // Get 2nd key number
        Key2 = (Key1>=(Anim->m_NumTranslationKeys-1))?Key1:Key1+1;

        // Get difference in keys' times
        DWORD TimeDiff = Anim->m_TranslationKeys[Key2].m_Time-
                         Anim->m_TranslationKeys[Key1].m_Time;
        if(!TimeDiff)
          TimeDiff = 1;

        // Calculate a scalar value to use
        float Scalar = (float)(Time - Anim->m_TranslationKeys[Key1].m_Time) / (float)TimeDiff;

        // Calculate interpolated vector values
        D3DXVECTOR3 vecPos = Anim->m_TranslationKeys[Key2].m_vecKey - 
                             Anim->m_TranslationKeys[Key1].m_vecKey;
        vecPos *= Scalar;
        vecPos += Anim->m_TranslationKeys[Key1].m_vecKey;

        // Create translation matrix and combine with transformation
        D3DXMATRIX matTranslation;
        D3DXMatrixTranslation(&matTranslation, vecPos.x, vecPos.y, vecPos.z);
        matAnimation *= matTranslation;
      }

      // Matrix
      if(Anim->m_NumMatrixKeys && Anim->m_MatrixKeys) {
        // Loop for matching matrix key
        DWORD Key1 = 0, Key2 = 0;
        for(DWORD i=0; i<Anim->m_NumMatrixKeys; i++) {
          if(Time >= Anim->m_MatrixKeys.m_Time)
            Key1 = i;
        }

        // Get 2nd key number
        Key2 = (Key1 >=(Anim->m_NumMatrixKeys-1))? Key1 : Key1+1;

        // Get difference in keys' times
        DWORD TimeDiff = Anim->m_MatrixKeys[Key2].m_Time-
                         Anim->m_MatrixKeys[Key1].m_Time;
        if(!TimeDiff)
          TimeDiff = 1;

        // Calculate a scalar value to use
        float Scalar = (float)(Time - Anim->m_MatrixKeys[Key1].m_Time) / (float)TimeDiff;

        // Calculate interpolated matrix
        D3DXMATRIX matDiff = Anim->m_MatrixKeys[Key2].m_matKey - 
                             Anim->m_MatrixKeys[Key1].m_matKey;
        matDiff *= Scalar;
        matDiff += Anim->m_MatrixKeys[Key1].m_matKey;

        // Combine with transformation
        matAnimation *= matDiff;
      }

      // Get the difference in transformations 
     // D3DXMATRIX matDiff = matAnimation - Anim->m_Bone->matOriginal;

      // Adjust by blending amount
     // matDiff *= Blend;

      // Add to transformation matrix
      Anim->m_Bone->TransformationMatrix = matAnimation;//matDiff;
    }

    // Go to next animation
    Anim = Anim->m_Next;
  }
}







SkeletalAnim.h
#ifndef _SKELETALANIM_H_
#define _SKELETALANIM_H_

#include "dxfile.h"
#include "XParser.h"
#include "XFile.h"


///////////////////////////////////////////////////////////
//
// Animation key type class containers
//
///////////////////////////////////////////////////////////
class cAnimationVectorKey
{
  public:
    DWORD       m_Time;
    D3DXVECTOR3 m_vecKey;
};

class cAnimationQuaternionKey
{
  public:
    DWORD          m_Time;
    D3DXQUATERNION m_quatKey;
};

class cAnimationMatrixKey
{
  public:
    DWORD      m_Time;
    D3DXMATRIX m_matKey;
};


///////////////////////////////////////////////////////////
//
// Animation class container
//
///////////////////////////////////////////////////////////
class cAnimation
{
  public:
    char          *m_Name;  // Bone's name
    D3DXFRAME_EX  *m_Bone;  // Pointer to bone frame
    cAnimation    *m_Next;  // Next animation object in list

    // # each key type and array of each type's keys
    DWORD                    m_NumTranslationKeys;
    cAnimationVectorKey     *m_TranslationKeys;
    DWORD                    m_NumScaleKeys;
    cAnimationVectorKey     *m_ScaleKeys;
    DWORD                    m_NumRotationKeys;
    cAnimationQuaternionKey *m_RotationKeys;
    DWORD                    m_NumMatrixKeys;
    cAnimationMatrixKey     *m_MatrixKeys;

  public:
    cAnimation();
    ~cAnimation();
};


///////////////////////////////////////////////////////////
//
// AnimationSet class container
//
///////////////////////////////////////////////////////////
class cAnimationSet
{
  public:
    char          *m_Name;   // Name of animation
    DWORD          m_Length; // Length of animation
    cAnimationSet *m_Next;   // Next set in linked list

    DWORD          m_NumAnimations; // # animations in set
    cAnimation    *m_Animations;    // Animations

  public:
    cAnimationSet();
    ~cAnimationSet();
};


///////////////////////////////////////////////////////////
//
// AnimationCollection class container
//
///////////////////////////////////////////////////////////
class cAnimationCollection : public cXParser
{
  public:
    DWORD          m_NumAnimationSets;  // # animation sets
    cAnimationSet *m_AnimationSets;     // Animation sets

  protected:
    // Parse an .X file for mass and spring data
    BOOL ParseObject(IDirectXFileData *pDataObj,
                       IDirectXFileData *pParentDataObj,
                       DWORD Depth,
                       void **Data, BOOL Reference);

    // Find a frame by name
    D3DXFRAME_EX *FindFrame(D3DXFRAME *Frame, char *Name);
    
  public:
    cAnimationCollection();
    ~cAnimationCollection();

    BOOL Load(char *Filename);
    void Free();

    void Map(D3DXFRAME_EX *RootFrame);
    void Update(char *AnimationSetName, DWORD Time, BOOL Loop, float Blend = 1.0f);
};

#endif







XParser.cpp
#include "XParser.h"

BOOL cXParser::Parse(char *Filename, void **Data)
{
  IDirectXFile           *pDXFile = NULL;
  IDirectXFileEnumObject *pDXEnum = NULL;
  IDirectXFileData       *pDXData = NULL;

  // Error checking
  if(Filename == NULL)
    return FALSE;

  // Create the file object
  if(FAILED(DirectXFileCreate(&pDXFile)))
    return FALSE;

  // Register the common templates
  if(FAILED(pDXFile->RegisterTemplates(                                            (LPVOID)D3DRM_XTEMPLATES,                                     D3DRM_XTEMPLATE_BYTES))) {
    pDXFile->Release();
    return FALSE;
  }

  // Create an enumeration object
  if(FAILED(pDXFile->CreateEnumObject((LPVOID)Filename,                                             DXFILELOAD_FROMFILE,                                          &pDXEnum))) {
    pDXFile->Release();
    return FALSE;
  }
  
  // Call the begin parse function, continuing if allowed
  if(BeginParse(Data) == TRUE) {

    // Loop through all top-level objects, breaking on errors
    BOOL ParseResult;
    while(SUCCEEDED(pDXEnum->GetNextDataObject(&pDXData))) {
      ParseResult = ParseObject(pDXData, NULL, 0, Data, FALSE);
      XPReleaseCOM(pDXData);
      if(ParseResult == FALSE)
        break;
    }
  }

  // Call end parse function
  EndParse(Data);

  // Release used COM objects
  XPReleaseCOM(pDXEnum);
  XPReleaseCOM(pDXFile);

  return TRUE;
}

BOOL cXParser::ParseChildObjects(                                                    IDirectXFileData *pDataObj,                                   DWORD Depth, void **Data,                                     BOOL ForceReference)
{
  IDirectXFileObject        *pSubObj  = NULL;
  IDirectXFileData          *pSubData = NULL;
  IDirectXFileDataReference *pDataRef = NULL;
  BOOL                       ParseResult = TRUE;

  // Scan for embedded templates
  while(SUCCEEDED(pDataObj->GetNextObject(&pSubObj))) {

    // Process embedded references
    if(SUCCEEDED(pSubObj->QueryInterface(                                                   IID_IDirectXFileDataReference,                                (void**)&pDataRef))) {

      // Resolve the data object
      if(SUCCEEDED(pDataRef->Resolve(&pSubData))) {

        // Parse the object, remembering the return code
        ParseResult = ParseObject(pSubData, pDataObj,                                           Depth+1, Data, TRUE);
        XPReleaseCOM(pSubData);
      }
      XPReleaseCOM(pDataRef);

      // Return on parsing failure
      if(ParseResult == FALSE)
        return FALSE;
    } else

    // Process non-referenced embedded templates
    if(SUCCEEDED(pSubObj->QueryInterface(                                                   IID_IDirectXFileData,                                         (void**)&pSubData))) {

      // Parse the object, remembering the return code
      ParseResult = ParseObject(pSubData, pDataObj,                                           Depth+1, Data,                                                ForceReference);
      XPReleaseCOM(pSubData);
    }

    // Release the data object
    XPReleaseCOM(pSubObj);

    // Return on parsing failure
    if(ParseResult == FALSE)
      return FALSE;
  }

  return TRUE;
}

const GUID *cXParser::GetObjectGUID(                                                IDirectXFileData *pDataObj)
{
  const GUID *Type = NULL;

  // Error checking
  if(pDataObj == NULL)
    return NULL;

  // Get the template type
  if(FAILED(pDataObj->GetType(&Type)))
    return NULL;

  return Type;
}

char *cXParser::GetObjectName(IDirectXFileData *pDataObj)
{
  char  *Name = NULL;
  DWORD  Size = 0;

  // Error checking
  if(pDataObj == NULL)
    return NULL;

  // Get the template name (if any)
  if(FAILED(pDataObj->GetName(NULL, &Size)))
    return NULL;

  // Allocate a name buffer and retrieve name
  if(Size) {
    if((Name = new char[Size]) != NULL)
      pDataObj->GetName(Name, &Size);
  }

  return Name;
}


void *cXParser::GetObjectData(                                                IDirectXFileData *pDataObj,                                   DWORD *Size)
{
  void *TemplateData = NULL;
  DWORD TemplateSize = 0;

  // Error checking
  if(pDataObj == NULL)
    return NULL;

  // Get a data pointer to template
  pDataObj->GetData(NULL,&TemplateSize,(PVOID*)&TemplateData);

  // Save size if needed
  if(Size != NULL)
    *Size = TemplateSize;

  return TemplateData;
}






XParser.h
#ifndef _XPARSER_H_
#define _XPARSER_H_

#include <windows.h>
#include "dxfile.h"
#include "XFile.h"

// A macro to quickly release and NULL a COM interface
#define XPReleaseCOM(x) { if(x) { x->Release(); x= NULL; } }

class cXParser
{
  protected:
    // Functions called when parsing begins and end
    virtual BOOL BeginParse(void **Data) { return TRUE; }
    virtual BOOL EndParse(void **Data)   { return TRUE; }

    // Function called for every template found
    virtual BOOL ParseObject(                                                  IDirectXFileData *pDataObj,                                   IDirectXFileData *pParentDataObj,                             DWORD Depth,                                                  void **Data, BOOL Reference)
            { 
              return ParseChildObjects(pDataObj, Depth,                                              Data, Reference);
            }

    // Function called to enumerate child templates
    BOOL ParseChildObjects(IDirectXFileData *pDataObj,                                   DWORD Depth, void **Data,                                     BOOL ForceReference = FALSE);


  public:
    // Function to start parsing an .X file
    BOOL Parse(char *Filename, void **Data = NULL);

    // Functions to help retrieve template information
    const GUID *GetObjectGUID(IDirectXFileData *pDataObj);
    char *GetObjectName(IDirectXFileData *pDataObj);
    void *GetObjectData(IDirectXFileData *pDataObj,                                   DWORD *Size);
};

#endif





XFile.cpp
#include "rmxftmpl.h"







XFile.h
#include "rmxfguid.h"
extern unsigned char D3DRM_XTEMPLATES[];
#define D3DRM_XTEMPLATE_BYTES 3278






gViewer.cpp
 

BOOL cGameApp::GameInit()
{
  m_FloorMesh.Create(m_Graphics, "..\\Data\\floor.x", "..\\Data\\");

  m_ObjectMesh.Create(m_Graphics, "..\\Data\\cow.x", "..\\Data\\");
  
 // Load an animation collection
  g_Anim.Load("..\\Data\\cow.x");

  // Map the animation to the frame hierarchy
  g_Anim.Map(m_ObjectMesh.GetFrame());


  return TRUE;
}

BOOL cGameApp::GameUpdate()
{
    static DWORD StartTime = timeGetTime();
	 DWORD ThisTime = timeGetTime();



  // Update the animation (convert to 30 fps)
  g_Anim.Update(NULL, (ThisTime-StartTime)*7, TRUE);

  // Rebuild the frame hierarchy transformations
  if(m_ObjectMesh.GetFrame())
    m_ObjectMesh.GetFrame()->UpdateHierarchy();

  // Build the skinned mesh
  m_ObjectMesh.UpdateMesh();

  // Calculate a view transformation matrix
  // Using the mesh's bounding radius to position the viewer
  D3DXVECTOR3 m_Min, m_Max;
  float m_MeshRadius;
  m_ObjectMesh.GetBounds(&m_Min, &m_Max, &m_MeshRadius);

  float Distance = m_MeshRadius * 3.0f;
  float Angle = (float)timeGetTime() / 2000.0f;
  D3DXMATRIX matView;
  D3DXMatrixLookAtLH(&matView,
                     &D3DXVECTOR3((float)cos(Angle) * Distance, m_MeshRadius, (float)sin(Angle) * Distance),
                     &D3DXVECTOR3(0.0f, 0.0f, 0.0f),
                     &D3DXVECTOR3(0.0f, 1.0f, 0.0f));
  m_Graphics->GetDeviceCOM()->SetTransform(D3DTS_VIEW, &matView);

  // Set a world transformation
  D3DXMATRIX matWorld;
  D3DXMatrixIdentity(&matWorld);
  m_Graphics->GetDeviceCOM()->SetTransform(D3DTS_WORLD, &matWorld);


   return TRUE;
}

BOOL cGameApp::GameRender()
{

  // clearing Z buffer before everything is drawn for the next frame
  m_Graphics->ClearZBuffer();
  m_Graphics->Clear(D3DCOLOR_RGBA(0,69,133,230)); //setting background to blue


  m_FloorMesh.Render();


// Render skinned mesh
  m_ObjectMesh.Render();

 return TRUE;
}







[Edited by - rickyo on July 12, 2007 3:17:13 PM]

Share this post


Link to post
Share on other sites
Advertisement
The short answer is: if you can use another animation format, then use it. The X format's only benefit (IMO) is that it's part of the SDK.

Share this post


Link to post
Share on other sites
.X is is a very good format; in addition to having ready-made loading code available in the SDK, it natively supports a lot of modern features, its very extensible, and widely supported by a lot of toolsets.

I suspect, anyway, that your problem is with your modified loading/animation code, not the .X file itself(*), in which case switching to another file format will only exacerbate the problem. I don't have time to look over your code right now, but I'll try and come back to this thread in a little while.

(*) Because of the extensibility of the format, many introductory or tutorial-level loading/animation/etc code snippets fail to properly handle the full suite of .X features.

Share this post


Link to post
Share on other sites
If you are wondering about the X file integrity, try loading it with the DX Viewer (I forget the full name) in the utils directory of the SDK. I suspect it is in your code. Make a set of cubes and export them in the same way, so that multiple meshes are saved to the DX file. Offset the cubes so that they have a frame offset.

Verify the following:
1) Render the cubes and see if they too are broken.
2) If they are not broken, it is probably a skinned mesh issue. Did you convert the skinned mesh to a blended mesh, and are you updating it properly.
3) If they are broken, it's a framing issue. Verify step by step when you load it that the frame's transforms are being saved for the right mesh containers.

Hopefully that will get your foot in the door.

Share this post


Link to post
Share on other sites
Thanks Supernat02

You have gave me a big hints. I have checked out the model in MS MViewer and I found that the X file is OK! After struggling for 2 weeks to study about skinned mesh ...

http://www.moon-labs.com/resources/d3dx_skinnedmesh.pdf

I suspect the problem is due to updating mesh before render, so I did something like this ...

+--------------------------------------------------------------------+
| D3DXMESHCONTAINER_EX *MeshPtr = m_Mesh; |
| // Loop through all meshes in list |
| while(MeshPtr) { |
| |
| UpdateMesh(MeshPtr); |
| |
| // Go to next mesh |
| MeshPtr = (D3DXMESHCONTAINER_EX*)MeshPtr->pNextMeshContainer; |
| } |
+--------------------------------------------------------------------+
and finally the cow comes 'alive' ^^





Hereby I would also like to thank jpetrie and ET3D for your responses
My next step is "Multiple animation blending" -> "Ragdoll" -> "Shaders"

I think I could change the file to my own format after I can managed Xfile and its functions.

Thank you very much!



Share this post


Link to post
Share on other sites
Thanks Supernat02

You have gave me a big hints. I have checked out the model in MS MViewer and I found that the X file is OK! After struggling for 2 weeks to study about skinned mesh ...

http://www.moon-labs.com/resources/d3dx_skinnedmesh.pdf

I suspect the problem is due to updating mesh before render, so I did something like this ...

+--------------------------------------------------------------------+
| D3DXMESHCONTAINER_EX *MeshPtr = m_Mesh; |
| // Loop through all meshes in list |
| while(MeshPtr) { |
| |
| UpdateMesh(MeshPtr); |
| |
| // Go to next mesh |
| MeshPtr = (D3DXMESHCONTAINER_EX*)MeshPtr->pNextMeshContainer; |
| } |
+--------------------------------------------------------------------+
and finally the cow comes 'alive' ^^





Hereby I would also like to thank jpetrie and ET3D for your responses
My next step is "Multiple animation blending" -> "Ragdoll" -> "Shaders"

I think I could change the file to my own format after I can managed Xfile and its functions.

Thank you very much!



Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!