Sign in to follow this  
alliedwarrior

Memory Leaks

Recommended Posts

alliedwarrior    100
I can not find where the memory leak is happening in my class (SkinnedMesh), I am releasing this:
SKINNEDMESH::~SKINNEDMESH()
{
BONE_HIERARCHY boneHierarchy;
boneHierarchy.DestroyFrame(m_pRootBone);
if(m_pAnimControl)m_pAnimControl->Release();
}

Share this post


Link to post
Share on other sites
Buckeye    10747
If you're sure the leak is due to the creation of a SKINNEDMESH instance, then either or both:

1. DestroyFrame is not deleting and releasing all objects that it should.

2. You create or new other objects in your SKINNEDMESH instance that aren't deleted or released in DestroyFrame (mesh, skininfo, textures, etc.).

Share this post


Link to post
Share on other sites
alliedwarrior    100
Quote:
Original post by Buckeye
If you're sure the leak is due to the creation of a SKINNEDMESH instance, then either or both:

1. DestroyFrame is not deleting and releasing all objects that it should.

2. You create or new other objects in your SKINNEDMESH instance that aren't deleted or released in DestroyFrame (mesh, skininfo, textures, etc.).


Yes, I did the tests and the leak is in SkinnedMesh

I would ask for your help, because I have tried several ways and found where the leak is (something is going unnoticed by me)
my class:
#ifndef SKINNED_MESH
#define SKINNED_MESH

#include <windows.h>
#include <d3dx9.h>
#include <string>
#include <vector>

struct BONE: public D3DXFRAME
{
D3DXMATRIX CombinedTransformationMatrix;
};

struct BONEMESH: public D3DXMESHCONTAINER
{
ID3DXMesh* OriginalMesh;
std::vector<D3DMATERIAL9> materials;
std::vector<IDirect3DTexture9*> textures;

DWORD NumAttributeGroups;
D3DXATTRIBUTERANGE* attributeTable;
D3DXMATRIX** boneMatrixPtrs;
D3DXMATRIX* boneOffsetMatrices;
D3DXMATRIX* currentBoneMatrices;
};

class SKINNEDMESH
{
public:
SKINNEDMESH();
~SKINNEDMESH();
void Load(char *fileName, IDirect3DDevice9 *Dev);
void Render(BONE *bone, float scale);
void UpdateAnim(bool bones, D3DXMATRIX world, ID3DXAnimationController* animControl, float time);
void SetAnimation(char name[]);
void SetPosition(D3DXVECTOR3 pos);
void SetRotation(D3DXVECTOR3 angle);
std::vector<std::string> GetAnimations();
BONE* FindBone(char name[]);

//Controller of Bones
void mouselocal(D3DXVECTOR3 pos, int Width, D3DXMATRIX pm, D3DXMATRIX vm, D3DVIEWPORT9 vp); //Testes

private:
void UpdateMatrices(bool bones, BONE* bone, D3DXMATRIX *parentMatrix);
void SetupBoneMatrixPointers(BONE *bone);

IDirect3DDevice9 *m_pDevice;
D3DXFRAME *m_pRootBone;
ID3DXAnimationController *m_pAnimControl;

D3DXVECTOR3 vPos;
D3DXVECTOR3 rotation;
float scale;

//Controller of Bones
D3DXVECTOR3 mouseLocation;
int m_Width;
D3DXMATRIX m_pm; // projection matrix
D3DXMATRIX m_vm; // view matrix
D3DVIEWPORT9 m_vp; // viewport
};

#endif


#include "skinnedMesh.h"

class BONE_HIERARCHY: public ID3DXAllocateHierarchy
{
public:
STDMETHOD(CreateFrame)(THIS_ LPCSTR Name, LPD3DXFRAME *ppNewFrame);
STDMETHOD(CreateMeshContainer)(THIS_ LPCTSTR Name, CONST D3DXMESHDATA * pMeshData, CONST D3DXMATERIAL * pMaterials, CONST D3DXEFFECTINSTANCE * pEffectInstances, DWORD NumMaterials, CONST DWORD * pAdjacency, LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER * ppNewMeshContainer);
STDMETHOD(DestroyFrame)(THIS_ LPD3DXFRAME pFrameToFree);
STDMETHOD(DestroyMeshContainer)(THIS_ LPD3DXMESHCONTAINER pMeshContainerBase);
};

HRESULT BONE_HIERARCHY::CreateFrame(LPCSTR Name, LPD3DXFRAME *ppNewFrame)
{
BONE *newBone = new BONE;
memset(newBone, 0, sizeof(BONE));

//Copy name
if(Name != NULL)
{
newBone->Name = new char[strlen(Name)+1];
strcpy(newBone->Name, Name);
}

//Set the transformation matrices
D3DXMatrixIdentity(&newBone->TransformationMatrix);
D3DXMatrixIdentity(&newBone->CombinedTransformationMatrix);

//Return the new bone...
*ppNewFrame = (D3DXFRAME*)newBone;

return S_OK;
}

HRESULT BONE_HIERARCHY::CreateMeshContainer(LPCSTR Name,
CONST D3DXMESHDATA *pMeshData,
CONST D3DXMATERIAL *pMaterials,
CONST D3DXEFFECTINSTANCE *pEffectInstances,
DWORD NumMaterials,
CONST DWORD *pAdjacency,
LPD3DXSKININFO pSkinInfo,
LPD3DXMESHCONTAINER *ppNewMeshContainer)
{
//Create new Bone Mesh
BONEMESH *boneMesh = new BONEMESH;
memset(boneMesh, 0, sizeof(BONEMESH));

//Get mesh data
boneMesh->OriginalMesh = pMeshData->pMesh;
boneMesh->MeshData.pMesh = pMeshData->pMesh;
boneMesh->MeshData.Type = pMeshData->Type;
pMeshData->pMesh->AddRef(); //Add Reference so that the mesh isnt deallocated
IDirect3DDevice9 *m_pDevice = NULL;
pMeshData->pMesh->GetDevice(&m_pDevice); //Get m_pDevice ptr from mesh

//Copy materials and load textures (just like with a static mesh)
for(int i = 0; i < NumMaterials; i++)
{
D3DXMATERIAL mtrl;
memcpy(&mtrl, &pMaterials[i], sizeof(D3DXMATERIAL));
boneMesh->materials.push_back(mtrl.MatD3D);

char textureFname[200];
//strcpy(textureFname, "mesh/");
strcat(textureFname, mtrl.pTextureFilename);

//Load texture
IDirect3DTexture9* newTexture = NULL;
D3DXCreateTextureFromFile(m_pDevice, pMaterials[i].pTextureFilename, &newTexture);
boneMesh->textures.push_back(newTexture);
}

if(pSkinInfo != NULL)
{
//Get Skin Info
boneMesh->pSkinInfo = pSkinInfo;
pSkinInfo->AddRef(); //Add reference so that the SkinInfo isnt deallocated

//Clone mesh and store in boneMesh->MeshData.pMesh
pMeshData->pMesh->CloneMeshFVF(D3DXMESH_MANAGED, pMeshData->pMesh->GetFVF(),
m_pDevice, &boneMesh->MeshData.pMesh);

//Get Attribute Table
boneMesh->MeshData.pMesh->GetAttributeTable(NULL, &boneMesh->NumAttributeGroups);
boneMesh->attributeTable = new D3DXATTRIBUTERANGE[boneMesh->NumAttributeGroups];
boneMesh->MeshData.pMesh->GetAttributeTable(boneMesh->attributeTable, NULL);

//Create bone offset and current matrices
int NumBones = pSkinInfo->GetNumBones();
boneMesh->boneOffsetMatrices = new D3DXMATRIX[NumBones];
boneMesh->currentBoneMatrices = new D3DXMATRIX[NumBones];

//Get bone offset matrices
for(int i=0;i < NumBones;i++)
boneMesh->boneOffsetMatrices[i] = *(boneMesh->pSkinInfo->GetBoneOffsetMatrix(i));
}

//Set ppNewMeshContainer to the newly created boneMesh container
*ppNewMeshContainer = boneMesh;

return S_OK;
}

HRESULT BONE_HIERARCHY::DestroyFrame(LPD3DXFRAME pFrameToFree)
{
if(pFrameToFree)
{
//Free name
if(pFrameToFree->Name != NULL)
delete [] pFrameToFree->Name;

//Free bone
delete pFrameToFree;
}
pFrameToFree = NULL;

return S_OK;
}

HRESULT BONE_HIERARCHY::DestroyMeshContainer(LPD3DXMESHCONTAINER pMeshContainerBase)
{
BONEMESH *boneMesh = (BONEMESH*)pMeshContainerBase;

//Release textures
for(int i=0;i < boneMesh->textures.size();i++)
if(boneMesh->textures[i] != NULL)
boneMesh->textures[i]->Release();

//Release mesh data
if(boneMesh->MeshData.pMesh)boneMesh->MeshData.pMesh->Release();
if(boneMesh->pSkinInfo)boneMesh->pSkinInfo->Release();
if(boneMesh->OriginalMesh)boneMesh->OriginalMesh->Release();
delete boneMesh;

return S_OK;
}


//////////////////////////////////////////////////////////////////////////////////////////////////
// SKINNED MESH //
//////////////////////////////////////////////////////////////////////////////////////////////////

struct VERTEX{
VERTEX();
VERTEX(D3DXVECTOR3 pos, D3DCOLOR col){position = pos; color = col;}
D3DXVECTOR3 position;
D3DCOLOR color;
static const DWORD FVF;
};

const DWORD VERTEX::FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;

SKINNEDMESH::SKINNEDMESH()
{
m_pRootBone = NULL;
m_pAnimControl = NULL;

vPos=D3DXVECTOR3(0.0f,0.0f,0.0f);
rotation=D3DXVECTOR3(0.0f,0.0f,0.0f);
scale = 1;

}

SKINNEDMESH::~SKINNEDMESH()
{
BONE_HIERARCHY boneHierarchy;
boneHierarchy.DestroyFrame(m_pRootBone);
if(m_pAnimControl)m_pAnimControl->Release();
}

void SKINNEDMESH::Load(char *fileName, IDirect3DDevice9 *Dev)
{
m_pDevice = Dev;
BONE_HIERARCHY boneHierarchy;

D3DXLoadMeshHierarchyFromX(fileName, D3DXMESH_MANAGED,
m_pDevice, &boneHierarchy,
NULL, &m_pRootBone, &m_pAnimControl);

SetupBoneMatrixPointers((BONE*)m_pRootBone);

//Update all the bones
D3DXMATRIX i;
D3DXMatrixIdentity(&i);
UpdateMatrices(false, (BONE*)m_pRootBone, &i);
}
/*
void SKINNEDMESH::UpdateMatrices(BONE* bone, D3DXMATRIX *parentMatrix)
{
if(bone == NULL)return;

D3DXMatrixMultiply(&bone->CombinedTransformationMatrix,
&bone->TransformationMatrix,
parentMatrix);

if(bone->pFrameSibling)UpdateMatrices((BONE*)bone->pFrameSibling, parentMatrix);
if(bone->pFrameFirstChild)UpdateMatrices((BONE*)bone->pFrameFirstChild, &bone->CombinedTransformationMatrix);
}
*/

void SKINNEDMESH::UpdateMatrices(bool bones, BONE* bone, D3DXMATRIX *parentMatrix)
{
if(bones)
{
if(parentMatrix != NULL)
{
if(bone->Name != NULL && strcmp(bone->Name,"Bip01_L_UpperArm")==0) //|| strcmp(bone->Name,"Bip01_R_UpperArm")==0)
{
D3DXMATRIX ident;
D3DXMatrixIdentity(&ident);
D3DXMATRIX final = bone->TransformationMatrix*(*parentMatrix);
D3DXVECTOR3 boneLocation(final._41,final._42,final._43);
D3DXVec3Project(&boneLocation,&boneLocation,&m_vp,&m_pm,&m_vm,&ident); // use YOUR viewport, projection and view matrices in this call
D3DXVECTOR3 dir = mouseLocation - boneLocation;
if(mouseLocation.x > m_Width/2)
{
float offset = D3DX_PI/2.0f;
float angle = offset - atan2(dir.y,dir.x); // change this when you rotate the model
D3DXMATRIX rot;
D3DXMatrixRotationY(&rot,angle);
bone->CombinedTransformationMatrix = rot*final;
}
else
{
float offset = -D3DX_PI/2.0f;
float angle = offset + atan2(dir.y,dir.x); // change this when you rotate the model
D3DXMATRIX rot;
D3DXMatrixRotationY(&rot,angle);
bone->CombinedTransformationMatrix = rot*final;
}
}
else
D3DXMatrixMultiply(&bone->CombinedTransformationMatrix,
&bone->TransformationMatrix,
parentMatrix);
}
else
bone->CombinedTransformationMatrix = bone->TransformationMatrix;

if(bone->pFrameSibling)
{
UpdateMatrices(bones, (BONE*)bone->pFrameSibling, parentMatrix);
}
if(bone->pFrameFirstChild)
{
UpdateMatrices(bones, (BONE*)bone->pFrameFirstChild, &bone->CombinedTransformationMatrix);
}
}
else
{
if(bone != NULL)
D3DXMatrixMultiply(&bone->CombinedTransformationMatrix,
&bone->TransformationMatrix,
parentMatrix);
else
bone->CombinedTransformationMatrix = bone->TransformationMatrix;

if(bone->pFrameSibling)
UpdateMatrices(bones, (BONE*)bone->pFrameSibling, parentMatrix);
if(bone->pFrameFirstChild)
UpdateMatrices(bones, (BONE*)bone->pFrameFirstChild, &bone->CombinedTransformationMatrix);
}
}
void SKINNEDMESH::Render(BONE *bone, float scale)
{
if(bone == NULL)bone = (BONE*)m_pRootBone;

// Setup world matrix
D3DXMATRIXA16 matWorld,matWorld2;

// set the rotation matrix
D3DXMatrixRotationYawPitchRoll(&matWorld2,rotation.x,rotation.y,rotation.z);

D3DXMatrixScaling(&matWorld,scale,scale,scale);

D3DXMatrixMultiply(&matWorld,&matWorld,&matWorld2);

D3DXMatrixTranslation( &matWorld2, vPos.x, vPos.y,vPos.z );

D3DXMatrixMultiply(&matWorld,&matWorld,&matWorld2);

m_pDevice->SetTransform( D3DTS_WORLD, &matWorld );

//If there is a mesh to render...
if(bone->pMeshContainer != NULL)
{
BONEMESH *boneMesh = (BONEMESH*)bone->pMeshContainer;

if (boneMesh->pSkinInfo != NULL)
{
// set up bone transforms
int numBones = boneMesh->pSkinInfo->GetNumBones();
for(int i=0;i < numBones;i++)
D3DXMatrixMultiply(&boneMesh->currentBoneMatrices[i],
&boneMesh->boneOffsetMatrices[i],
boneMesh->boneMatrixPtrs[i]);

//Update the skinned mesh
BYTE *src = NULL, *dest = NULL;
boneMesh->OriginalMesh->LockVertexBuffer(D3DLOCK_READONLY, (VOID**)&src);
boneMesh->MeshData.pMesh->LockVertexBuffer(0, (VOID**)&dest);

boneMesh->pSkinInfo->UpdateSkinnedMesh(boneMesh->currentBoneMatrices, NULL, src, dest);

boneMesh->MeshData.pMesh->UnlockVertexBuffer();
boneMesh->OriginalMesh->UnlockVertexBuffer();

//Render the mesh
for(int i=0;i < boneMesh->NumAttributeGroups;i++)
{
int mtrlIndex = boneMesh->attributeTable[i].AttribId;
m_pDevice->SetMaterial(&(boneMesh->materials[mtrlIndex]));
m_pDevice->SetTexture(0, boneMesh->textures[mtrlIndex]);
boneMesh->MeshData.pMesh->DrawSubset(mtrlIndex);
}
}
}

if(bone->pFrameSibling != NULL)Render((BONE*)bone->pFrameSibling, scale);
if(bone->pFrameFirstChild != NULL)Render((BONE*)bone->pFrameFirstChild, scale);
}

void SKINNEDMESH::SetupBoneMatrixPointers(BONE *bone)
{
if(bone->pMeshContainer != NULL)
{
BONEMESH *boneMesh = (BONEMESH*)bone->pMeshContainer;

if(boneMesh->pSkinInfo != NULL)
{
int NumBones = boneMesh->pSkinInfo->GetNumBones();
boneMesh->boneMatrixPtrs = new D3DXMATRIX*[NumBones];

for(int i=0;i < NumBones;i++)
{
BONE *b = (BONE*)D3DXFrameFind(m_pRootBone, boneMesh->pSkinInfo->GetBoneName(i));
if(b != NULL)boneMesh->boneMatrixPtrs[i] = &b->CombinedTransformationMatrix;
else boneMesh->boneMatrixPtrs[i] = NULL;
}
}
}

if(bone->pFrameSibling != NULL)SetupBoneMatrixPointers((BONE*)bone->pFrameSibling);
if(bone->pFrameFirstChild != NULL)SetupBoneMatrixPointers((BONE*)bone->pFrameFirstChild);
}

BONE* SKINNEDMESH::FindBone(char name[])
{
return (BONE*) D3DXFrameFind(m_pRootBone, name);
}

void SKINNEDMESH::UpdateAnim(bool bones, D3DXMATRIX world, ID3DXAnimationController* animControl, float time)
{
if(animControl != NULL)
animControl->AdvanceTime(time, NULL);
else m_pAnimControl->AdvanceTime(time, NULL);

UpdateMatrices(bones, (BONE*)m_pRootBone, &world);
}

void SKINNEDMESH::SetAnimation(char name[])
{
ID3DXAnimationSet *anim = NULL;

for(int i=0;i<m_pAnimControl->GetMaxNumAnimationSets();i++)
{
anim = NULL;
m_pAnimControl->GetAnimationSet(i, &anim);

if(anim != NULL)
{
if(strcmp(name, anim->GetName()) == 0)
m_pAnimControl->SetTrackAnimationSet(0, anim);
anim->Release();
}
}
}

std::vector<std::string> SKINNEDMESH::GetAnimations()
{
ID3DXAnimationSet *anim = NULL;
std::vector<std::string> animations;

for(int i=0;i<m_pAnimControl->GetMaxNumAnimationSets();i++)
{
anim = NULL;
m_pAnimControl->GetAnimationSet(i, &anim);

if(anim != NULL)
{
animations.push_back(anim->GetName());
anim->Release();
}
}

return animations;
}

void SKINNEDMESH::SetPosition(D3DXVECTOR3 pos)
{
vPos = pos;
}

void SKINNEDMESH::SetRotation(D3DXVECTOR3 angle)
{
rotation = angle;
}

void SKINNEDMESH::mouselocal(D3DXVECTOR3 pos, int Width, D3DXMATRIX pm, D3DXMATRIX vm, D3DVIEWPORT9 vp)
{
mouseLocation = pos;
m_Width = Width;
m_pm = pm;
m_vm = vm;
m_vp = vp;
}



Thank you

Share this post


Link to post
Share on other sites
Buckeye    10747
As mentioned above, DestroyFrame doesn't delete or release all the objects that were created. During the process of loading the mesh hierarchy, many objects (frames, meshes, materials, etc.) are created, and several DirectX objects are created that need to be released. Take a look at CreateFrame to see all the possibilities.

Your DestroyFrame(m_pRootBone) call merely deletes the memory allocated for the m_pRootBone frame, without deleting or releasing any of the objects that were created during the loading process.

You might try looking at the docs for D3DXFrameDestroy(..).

You can also google around for an example of using the AllocateHierchy implementation.

Share this post


Link to post
Share on other sites
alliedwarrior    100
I tried something like this:

class BONE_HIERARCHY: public ID3DXAllocateHierarchy
{
public:
STDMETHOD(CreateFrame)(THIS_ LPCSTR Name, LPD3DXFRAME *ppNewFrame);
STDMETHOD(CreateMeshContainer)(THIS_ LPCTSTR Name, CONST D3DXMESHDATA * pMeshData, CONST D3DXMATERIAL * pMaterials, CONST D3DXEFFECTINSTANCE * pEffectInstances, DWORD NumMaterials, CONST DWORD * pAdjacency, LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER * ppNewMeshContainer);
STDMETHOD(DestroyFrame)(THIS_ LPD3DXFRAME pFrameToFree);
STDMETHOD(DestroyMeshContainer)(THIS_ LPD3DXMESHCONTAINER pMeshContainerBase);
BONE_HIERARCHY(SKINNEDMESH *pSkinMesh) : m_pSkinMesh(pSkinMesh) {} //here!this line is new!!
public:
SKINNEDMESH* m_pSkinMesh; //my class of SkinnedMesh
};


and
SKINNEDMESH::~SKINNEDMESH()
{
BONE_HIERARCHY boneHierarchy(this);
boneHierarchy.DestroyFrame(m_pRootBone);
D3DXFrameDestroy(m_pRootBone, &boneHierarchy);
if(m_pAnimControl)
m_pAnimControl->Release();
}


but when I close the game (which is the time that he tries to release the skinned mesh) compiler (debug) indicates a number of crashes and errors on the line D3DXFrameDestroy (m_pRootBone, & boneHierarchy)

Share this post


Link to post
Share on other sites
Buckeye    10747
Quote:
(debug) indicates a number of crashes and errors on the line D3DXFrameDestroy (m_pRootBone, & boneHierarchy)

I would certainly expect that, since you destroy the root frame before you destroy the rest of the frames.

boneHierarchy.DestroyFrame(m_pRootBone); // destroy the root frame
D3DXFrameDestroy(m_pRootBone, &boneHierarchy); // destroy a frame which has already been destroyed

I would strongly suggest you take a good look at your code and get a good understanding of what each piece does.

My previous post:
Quote:
You can also google around for an example of using the AllocateHierchy implementation.

You really should do that.

Share this post


Link to post
Share on other sites
alliedwarrior    100
I tried some examples and articles about D3DXFrameDestroy and followed them, the problem is that when he will release to this line:


SKINNEDMESH::~SKINNEDMESH()
{
BONE_HIERARCHY boneHierarchy(this);
D3DXFrameDestroy(m_pRootBone, &boneHierarchy);
if(m_pAnimControl)
m_pAnimControl->Release();
}

HRESULT BONE_HIERARCHY::DestroyMeshContainer(LPD3DXMESHCONTAINER pMeshContainerBase)
{
UINT iMaterial;
BONEMESH *pMeshContainer = (BONEMESH*)pMeshContainerBase;

SAFE_DELETE_ARRAY( pMeshContainer->Name );
SAFE_DELETE_ARRAY( pMeshContainer->pAdjacency );
SAFE_DELETE_ARRAY( pMeshContainer->pMaterials );
SAFE_DELETE_ARRAY( pMeshContainer->boneOffsetMatrices );

// release all the allocated textures
for (iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++)
{
SAFE_RELEASE( pMeshContainer->textures[iMaterial] );
}

SAFE_DELETE_ARRAY( pMeshContainer->boneMatrixPtrs );
SAFE_RELEASE( pMeshContainer->MeshData.pMesh );
SAFE_RELEASE( pMeshContainer->pSkinInfo );
SAFE_RELEASE( pMeshContainer->OriginalMesh );
SAFE_DELETE( pMeshContainer );
}

HRESULT BONE_HIERARCHY::DestroyFrame(LPD3DXFRAME pFrameToFree)
{
SAFE_DELETE_ARRAY( pFrameToFree->Name );
SAFE_DELETE( pFrameToFree );
return S_OK;
}

Share this post


Link to post
Share on other sites
Buckeye    10747
Quote:
the problem is that when he will release to this line

What's the problem?

Just a guess, but if it's a memory access violation, it may be that you don't initialize all the variables to 0 or NULL.

Share this post


Link to post
Share on other sites
alliedwarrior    100
Quote:
Original post by Buckeye
What error do you get? You still haven't said.


sorry



and in Output:
Quote:
The thread 'Win32 Thread' (0x808) has exited with code 0 (0x0).
First-chance exception at 0x002c7fdd in Resistance.exe: 0xC0000005: Access violation reading location 0xfeeefef6.
Unhandled exception at 0x002c7fdd in Resistance.exe: 0xC0000005: Access violation reading location 0xfeeefef6.
The program '[2568] Resistance.exe: Native' has exited with code -1073741819 (0xc0000005).

Share this post


Link to post
Share on other sites
SiCrane    11839
0xfeeefeee is a bit pattern used by the debug runtime to fill freed memory. If you get a memory error regarding an address thats close to that number it means that you're doing something like freeing data twice or accessing an object after it's already been freed.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this