424 lines
17 KiB
C++
424 lines
17 KiB
C++
#ifndef DRAWINGUTILITIESCLASS_h
|
|
#define DRAWINGUTILITIESCLASS_h
|
|
|
|
// DirectXEffects
|
|
#include <d3dx11effect.h>
|
|
// DXUT includes
|
|
#include <DXUT.h>
|
|
#include <DXUTcamera.h>
|
|
// DirectXTK includes
|
|
#include "Effects.h"
|
|
#include "VertexTypes.h"
|
|
#include "PrimitiveBatch.h"
|
|
#include "GeometricPrimitive.h"
|
|
#include "ScreenGrab.h"
|
|
// AntTweakBar includes
|
|
#include "AntTweakBar.h"
|
|
// vector mat quat
|
|
#include "util/vectorbase.h"
|
|
#include "util/matrixbase.h"
|
|
#include "util/quaternion.h"
|
|
using namespace DirectX;
|
|
using namespace GamePhysics;
|
|
|
|
class DrawingUtilitiesClass{
|
|
|
|
public:
|
|
ID3D11Device* g_ppd3Device;
|
|
ID3D11DeviceContext* g_pd3dImmediateContext;
|
|
// Effect corresponding to "effect.fx"
|
|
ID3DX11Effect* g_pEffect;
|
|
// Tweak bar
|
|
TwBar* g_pTweakBar;
|
|
// DXUT camera
|
|
// NOTE: CModelViewerCamera does not only manage the standard view transformation/camera position
|
|
// (CModelViewerCamera::GetViewMatrix()), but also allows for model rotation
|
|
// (CModelViewerCamera::GetWorldMatrix()).
|
|
// Look out for CModelViewerCamera::SetButtonMasks(...).
|
|
CModelViewerCamera g_camera;
|
|
|
|
// DirectXTK effect, input layout and primitive batch for position/color vertices
|
|
// (for drawing multicolored & unlit primitives)
|
|
BasicEffect* g_pEffectPositionColor;
|
|
ID3D11InputLayout* g_pInputLayoutPositionColor;
|
|
PrimitiveBatch<VertexPositionColor>* g_pPrimitiveBatchPositionColor;
|
|
|
|
// DirectXTK effect, input layout and primitive batch for position/normal vertices
|
|
// (for drawing unicolor & oriented & lit primitives)
|
|
BasicEffect* g_pEffectPositionNormal;
|
|
ID3D11InputLayout* g_pInputLayoutPositionNormal;
|
|
PrimitiveBatch<VertexPositionNormal>* g_pPrimitiveBatchPositionNormal;
|
|
|
|
BasicEffect* g_pEffectPositionNormalColor;
|
|
ID3D11InputLayout* g_pInputLayoutPositionNormalColor;
|
|
PrimitiveBatch<VertexPositionNormalColor>* g_pPrimitiveBatchPositionNormalColor;
|
|
|
|
// DirectXTK simple geometric primitives
|
|
std::unique_ptr<GeometricPrimitive> g_pSphere;
|
|
std::unique_ptr<GeometricPrimitive> g_pTeapot;
|
|
|
|
// Constructor
|
|
DrawingUtilitiesClass(){
|
|
g_pTweakBar = nullptr;
|
|
}
|
|
|
|
|
|
void init(ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext)
|
|
{
|
|
g_pd3dImmediateContext = pd3dImmediateContext;
|
|
g_ppd3Device = pd3dDevice;
|
|
// Create DirectXTK geometric primitives for later usage
|
|
g_pSphere = GeometricPrimitive::CreateGeoSphere(g_pd3dImmediateContext, 2.0f, 2, false);
|
|
g_pTeapot = GeometricPrimitive::CreateTeapot(g_pd3dImmediateContext, 1.5f, 8, false);
|
|
|
|
// Create effect, input layout and primitive batch for position/color vertices (DirectXTK)
|
|
{
|
|
// Effect
|
|
g_pEffectPositionColor = new BasicEffect(pd3dDevice);
|
|
g_pEffectPositionColor->SetVertexColorEnabled(true); // triggers usage of position/color vertices
|
|
|
|
// Input layout
|
|
void const* shaderByteCode;
|
|
size_t byteCodeLength;
|
|
g_pEffectPositionColor->GetVertexShaderBytecode(&shaderByteCode, &byteCodeLength);
|
|
|
|
pd3dDevice->CreateInputLayout(VertexPositionColor::InputElements,
|
|
VertexPositionColor::InputElementCount,
|
|
shaderByteCode, byteCodeLength,
|
|
&g_pInputLayoutPositionColor);
|
|
|
|
// Primitive batch
|
|
g_pPrimitiveBatchPositionColor = new PrimitiveBatch<VertexPositionColor>(pd3dImmediateContext);
|
|
}
|
|
|
|
// Create effect, input layout and primitive batch for position/normal vertices (DirectXTK)
|
|
{
|
|
// Effect
|
|
g_pEffectPositionNormal = new BasicEffect(pd3dDevice);
|
|
g_pEffectPositionNormal->EnableDefaultLighting(); // triggers usage of position/normal vertices
|
|
g_pEffectPositionNormal->SetPerPixelLighting(true);
|
|
|
|
// Input layout
|
|
void const* shaderByteCode;
|
|
size_t byteCodeLength;
|
|
g_pEffectPositionNormal->GetVertexShaderBytecode(&shaderByteCode, &byteCodeLength);
|
|
|
|
pd3dDevice->CreateInputLayout(VertexPositionNormal::InputElements,
|
|
VertexPositionNormal::InputElementCount,
|
|
shaderByteCode, byteCodeLength,
|
|
&g_pInputLayoutPositionNormal);
|
|
|
|
// Primitive batch
|
|
g_pPrimitiveBatchPositionNormal = new PrimitiveBatch<VertexPositionNormal>(pd3dImmediateContext);
|
|
}
|
|
|
|
// Create effect, input layout and primitive batch for position/normal/color vertices (DirectXTK)
|
|
{
|
|
// Effect
|
|
g_pEffectPositionNormalColor = new BasicEffect(pd3dDevice);
|
|
g_pEffectPositionNormalColor->SetPerPixelLighting(true);
|
|
g_pEffectPositionNormalColor->EnableDefaultLighting(); // triggers usage of position/normal/color vertices
|
|
g_pEffectPositionNormalColor->SetVertexColorEnabled(true); // triggers usage of position/normal/color vertices
|
|
|
|
// Input layout
|
|
void const* shaderByteCode;
|
|
size_t byteCodeLength;
|
|
g_pEffectPositionNormalColor->GetVertexShaderBytecode(&shaderByteCode, &byteCodeLength);
|
|
|
|
pd3dDevice->CreateInputLayout(VertexPositionNormalColor::InputElements,
|
|
VertexPositionNormalColor::InputElementCount,
|
|
shaderByteCode, byteCodeLength,
|
|
&g_pInputLayoutPositionNormalColor);
|
|
|
|
// Primitive batch
|
|
g_pPrimitiveBatchPositionNormalColor = new PrimitiveBatch<VertexPositionNormalColor>(pd3dImmediateContext);
|
|
}
|
|
}
|
|
|
|
void destroy(){
|
|
SAFE_DELETE (g_pPrimitiveBatchPositionColor);
|
|
SAFE_RELEASE(g_pInputLayoutPositionColor);
|
|
SAFE_DELETE (g_pEffectPositionColor);
|
|
|
|
SAFE_DELETE (g_pPrimitiveBatchPositionNormal);
|
|
SAFE_RELEASE(g_pInputLayoutPositionNormal);
|
|
SAFE_DELETE (g_pEffectPositionNormal);
|
|
|
|
SAFE_DELETE (g_pPrimitiveBatchPositionNormalColor);
|
|
SAFE_RELEASE(g_pInputLayoutPositionNormalColor);
|
|
SAFE_DELETE (g_pEffectPositionNormalColor);
|
|
|
|
g_pSphere.reset();
|
|
g_pTeapot.reset();
|
|
|
|
SAFE_RELEASE(g_pEffect);
|
|
|
|
TwDeleteBar(g_pTweakBar);
|
|
g_pTweakBar = nullptr;
|
|
TwTerminate();
|
|
|
|
}
|
|
|
|
|
|
// Draw the edges of the bounding box [-0.5;0.5]?rotated with the cameras model tranformation.
|
|
// (Drawn as line primitives using a DirectXTK primitive batch)
|
|
void DrawBoundingBox(ID3D11DeviceContext* pd3dImmediateContext)
|
|
{
|
|
// Setup position/color effect
|
|
g_pEffectPositionColor->SetWorld(g_camera.GetWorldMatrix());
|
|
|
|
g_pEffectPositionColor->Apply(pd3dImmediateContext);
|
|
pd3dImmediateContext->IASetInputLayout(g_pInputLayoutPositionColor);
|
|
|
|
// Draw
|
|
g_pPrimitiveBatchPositionColor->Begin();
|
|
|
|
// Lines in x direction (red color)
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
g_pPrimitiveBatchPositionColor->DrawLine(
|
|
VertexPositionColor(XMVectorSet(-0.5f, (float)(i%2)-0.5f, (float)(i/2)-0.5f, 1), Colors::Red),
|
|
VertexPositionColor(XMVectorSet( 0.6f, (float)(i%2)-0.5f, (float)(i/2)-0.5f, 1), Colors::Red)
|
|
);
|
|
}
|
|
|
|
// Lines in y direction
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
g_pPrimitiveBatchPositionColor->DrawLine(
|
|
VertexPositionColor(XMVectorSet((float)(i%2)-0.5f, -0.5f, (float)(i/2)-0.5f, 1), Colors::Green),
|
|
VertexPositionColor(XMVectorSet((float)(i%2)-0.5f, 0.6f, (float)(i/2)-0.5f, 1), Colors::Green)
|
|
);
|
|
}
|
|
|
|
// Lines in z direction
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
g_pPrimitiveBatchPositionColor->DrawLine(
|
|
VertexPositionColor(XMVectorSet((float)(i%2)-0.5f, (float)(i/2)-0.5f, -0.5f, 1), Colors::Blue),
|
|
VertexPositionColor(XMVectorSet((float)(i%2)-0.5f, (float)(i/2)-0.5f, 0.6f, 1), Colors::Blue)
|
|
);
|
|
}
|
|
|
|
g_pPrimitiveBatchPositionColor->End();
|
|
}
|
|
|
|
// Draw a large, square plane at y=-1 with a checkerboard pattern
|
|
// (Drawn as multiple quads, i.e. triangle strips, using a DirectXTK primitive batch)
|
|
void DrawFloor(ID3D11DeviceContext* pd3dImmediateContext)
|
|
{
|
|
// Setup position/normal/color effect
|
|
g_pEffectPositionNormalColor->SetWorld(XMMatrixIdentity());
|
|
g_pEffectPositionNormalColor->SetEmissiveColor(Colors::Black);
|
|
g_pEffectPositionNormalColor->SetDiffuseColor(0.8f * Colors::White);
|
|
g_pEffectPositionNormalColor->SetSpecularColor(0.4f * Colors::White);
|
|
g_pEffectPositionNormalColor->SetSpecularPower(1000);
|
|
|
|
g_pEffectPositionNormalColor->Apply(pd3dImmediateContext);
|
|
pd3dImmediateContext->IASetInputLayout(g_pInputLayoutPositionNormalColor);
|
|
|
|
// Draw 4*n*n quads spanning x = [-n;n], y = -1, z = [-n;n]
|
|
const float n = 4;
|
|
XMVECTOR normal = XMVectorSet(0, 1,0,0);
|
|
XMVECTOR planecenter = XMVectorSet(0,-1,0,0);
|
|
|
|
g_pPrimitiveBatchPositionNormalColor->Begin();
|
|
for (float z = -n; z < n; z++)
|
|
{
|
|
for (float x = -n; x < n; x++)
|
|
{
|
|
// Quad vertex positions
|
|
XMVECTOR pos[] = { XMVectorSet(x , -1, z+1, 0),
|
|
XMVectorSet(x+1, -1, z+1, 0),
|
|
XMVectorSet(x+1, -1, z , 0),
|
|
XMVectorSet(x , -1, z , 0) };
|
|
|
|
// Color checkerboard pattern (white & gray)
|
|
XMVECTOR color = ((int(z + x) % 2) == 0) ? XMVectorSet(1,1,1,1) : XMVectorSet(0.6f,0.6f,0.6f,1);
|
|
|
|
// Color attenuation based on distance to plane center
|
|
float attenuation[] = {
|
|
1.0f - XMVectorGetX(XMVector3Length(pos[0] - planecenter)) / n,
|
|
1.0f - XMVectorGetX(XMVector3Length(pos[1] - planecenter)) / n,
|
|
1.0f - XMVectorGetX(XMVector3Length(pos[2] - planecenter)) / n,
|
|
1.0f - XMVectorGetX(XMVector3Length(pos[3] - planecenter)) / n };
|
|
|
|
g_pPrimitiveBatchPositionNormalColor->DrawQuad(
|
|
VertexPositionNormalColor(pos[0], normal, attenuation[0] * color),
|
|
VertexPositionNormalColor(pos[1], normal, attenuation[1] * color),
|
|
VertexPositionNormalColor(pos[2], normal, attenuation[2] * color),
|
|
VertexPositionNormalColor(pos[3], normal, attenuation[3] * color)
|
|
);
|
|
}
|
|
}
|
|
g_pPrimitiveBatchPositionNormalColor->End();
|
|
}
|
|
|
|
void updateScreenSize(int width, int height)
|
|
{
|
|
// Update camera parameters
|
|
g_camera.SetWindow(width, height);
|
|
g_camera.SetProjParams(XM_PI / 4.0f, float(width) / float(height), 0.1f, 100.0f);
|
|
|
|
// Inform AntTweakBar about back buffer resolution change
|
|
TwWindowSize(width,height);
|
|
}
|
|
|
|
void update(float fElapsedTime)
|
|
{
|
|
// Move camera
|
|
g_camera.FrameMove(fElapsedTime);
|
|
|
|
// Update effects with new view + proj transformations
|
|
g_pEffectPositionColor->SetView (g_camera.GetViewMatrix());
|
|
g_pEffectPositionColor->SetProjection (g_camera.GetProjMatrix());
|
|
|
|
g_pEffectPositionNormal->SetView (g_camera.GetViewMatrix());
|
|
g_pEffectPositionNormal->SetProjection(g_camera.GetProjMatrix());
|
|
|
|
g_pEffectPositionNormalColor->SetView(g_camera.GetViewMatrix());
|
|
g_pEffectPositionNormalColor->SetProjection(g_camera.GetProjMatrix());
|
|
}
|
|
|
|
void setUpLighting(Vec3 EmissiveColor, Vec3 SpecularColor, float SpecularPower, Vec3 DiffuseColor)
|
|
{
|
|
XMVECTOR c1 = EmissiveColor.toDirectXVector();
|
|
XMVECTOR c2 = SpecularColor.toDirectXVector();
|
|
XMVECTOR c3 = DiffuseColor.toDirectXVector();
|
|
g_pEffectPositionNormal->SetEmissiveColor(c1);
|
|
g_pEffectPositionNormal->SetSpecularColor(c2);
|
|
g_pEffectPositionNormal->SetSpecularPower(SpecularPower);
|
|
g_pEffectPositionNormal->SetDiffuseColor(c3);
|
|
}
|
|
void drawSphere(Vec3 pos, Vec3 scale)
|
|
{
|
|
drawSphere(pos.toDirectXVector(), scale.toDirectXVector());
|
|
}
|
|
void drawSphere(const XMVECTOR pos, const XMVECTOR scale)
|
|
{
|
|
// Setup position/normal effect (per object variables)
|
|
XMMATRIX s = XMMatrixScaling(XMVectorGetX(scale), XMVectorGetY(scale),XMVectorGetZ(scale));
|
|
XMMATRIX t = XMMatrixTranslation(XMVectorGetX(pos), XMVectorGetY(pos), XMVectorGetZ(pos));
|
|
g_pEffectPositionNormal->SetWorld(s * t * g_camera.GetWorldMatrix());
|
|
// Draw
|
|
// NOTE: The following generates one draw call per object, so performance will be bad for n>>1000 or so
|
|
g_pSphere->Draw( g_pEffectPositionNormal, g_pInputLayoutPositionNormal);
|
|
}
|
|
|
|
void drawTeapot(Vec3 pos,Vec3 rot,Vec3 scale)
|
|
{
|
|
drawTeapot(pos.toDirectXVector(),rot.toDirectXVector(),scale.toDirectXVector());
|
|
}
|
|
void drawTeapot(const XMVECTOR pos,const XMVECTOR rot,const XMVECTOR scale)
|
|
{
|
|
// Setup position/normal effect (per object variables)
|
|
XMMATRIX s = XMMatrixScaling(XMVectorGetX(scale), XMVectorGetY(scale),XMVectorGetZ(scale));
|
|
XMMATRIX t = XMMatrixTranslation(XMVectorGetX(pos), XMVectorGetY(pos), XMVectorGetZ(pos));
|
|
XMMATRIX r = XMMatrixRotationRollPitchYaw(XMVectorGetX(rot), XMVectorGetX(rot), XMVectorGetX(rot));
|
|
g_pEffectPositionNormal->SetWorld(r * s * t * g_camera.GetWorldMatrix());
|
|
// Draw
|
|
// NOTE: The following generates one draw call per object, so performance will be bad for n>>1000 or so
|
|
g_pTeapot->Draw( g_pEffectPositionNormal, g_pInputLayoutPositionNormal);
|
|
}
|
|
|
|
void drawRigidBody(const XMMATRIX& m_objToWorld)
|
|
{
|
|
g_pEffectPositionNormal->SetWorld(m_objToWorld);
|
|
|
|
g_pEffectPositionNormal->Apply(g_pd3dImmediateContext);
|
|
g_pd3dImmediateContext->IASetInputLayout(g_pInputLayoutPositionNormal);
|
|
|
|
// front
|
|
g_pPrimitiveBatchPositionNormal->Begin();
|
|
g_pPrimitiveBatchPositionNormal->DrawQuad(
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(0,0,-1)),
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT3(0,0,-1)),
|
|
VertexPositionNormal(XMFLOAT3(0.5f, 0.5f, -0.5f), XMFLOAT3(0,0,-1)),
|
|
VertexPositionNormal(XMFLOAT3(0.5f, -0.5f, -0.5f), XMFLOAT3(0,0,-1))
|
|
);
|
|
// back
|
|
g_pPrimitiveBatchPositionNormal->DrawQuad(
|
|
VertexPositionNormal(XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT3(0,0,1)),
|
|
VertexPositionNormal(XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT3(0,0,1)),
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT3(0,0,1)),
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT3(0,0,1))
|
|
);
|
|
// left
|
|
g_pPrimitiveBatchPositionNormal->DrawQuad(
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT3(-1,0,0)),
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT3(-1,0,0)),
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT3(-1,0,0)),
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(-1,0,0))
|
|
);
|
|
// right
|
|
g_pPrimitiveBatchPositionNormal->DrawQuad(
|
|
VertexPositionNormal(XMFLOAT3(0.5f, -0.5f, -0.5f), XMFLOAT3(1,0,0)),
|
|
VertexPositionNormal(XMFLOAT3(0.5f, 0.5f, -0.5f), XMFLOAT3(1,0,0)),
|
|
VertexPositionNormal(XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT3(1,0,0)),
|
|
VertexPositionNormal(XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT3(1,0,0))
|
|
);
|
|
// top
|
|
g_pPrimitiveBatchPositionNormal->DrawQuad(
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT3(0,1,0)),
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT3(0,1,0)),
|
|
VertexPositionNormal(XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT3(0,1,0)),
|
|
VertexPositionNormal(XMFLOAT3(0.5f, 0.5f, -0.5f), XMFLOAT3(0,1,0))
|
|
);
|
|
// bottom
|
|
g_pPrimitiveBatchPositionNormal->DrawQuad(
|
|
VertexPositionNormal(XMFLOAT3(0.5f, -0.5f, -0.5f), XMFLOAT3(0,-1,0)),
|
|
VertexPositionNormal(XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT3(0,-1,0)),
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT3(0,-1,0)),
|
|
VertexPositionNormal(XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(0,-1,0))
|
|
);
|
|
g_pPrimitiveBatchPositionNormal->End();
|
|
|
|
}
|
|
|
|
void drawRigidBody(Mat4 m_objToWorld)
|
|
{
|
|
drawRigidBody(m_objToWorld.toDirectXMatrix());
|
|
}
|
|
|
|
void DrawTriangleUsingShaders()
|
|
{
|
|
XMMATRIX world = g_camera.GetWorldMatrix();
|
|
XMMATRIX view = g_camera.GetViewMatrix();
|
|
XMMATRIX proj = g_camera.GetProjMatrix();
|
|
XMFLOAT4X4 mViewProj;
|
|
XMStoreFloat4x4(&mViewProj, world * view * proj);
|
|
g_pEffect->GetVariableByName("g_worldViewProj")->AsMatrix()->SetMatrix((float*)mViewProj.m);
|
|
g_pEffect->GetTechniqueByIndex(0)->GetPassByIndex(0)->Apply(0, g_pd3dImmediateContext);
|
|
|
|
g_pd3dImmediateContext->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
|
|
g_pd3dImmediateContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0);
|
|
g_pd3dImmediateContext->IASetInputLayout(nullptr);
|
|
g_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
g_pd3dImmediateContext->Draw(3, 0);
|
|
}
|
|
|
|
void beginLine()
|
|
{
|
|
g_pEffectPositionColor->SetWorld(g_camera.GetWorldMatrix());
|
|
g_pEffectPositionColor->Apply(g_pd3dImmediateContext);
|
|
g_pd3dImmediateContext->IASetInputLayout(g_pInputLayoutPositionColor);
|
|
// Draw
|
|
g_pPrimitiveBatchPositionColor->Begin();
|
|
}
|
|
void drawLine(Vec3 pos1,Vec3 color1, Vec3 pos2,Vec3 color2)
|
|
{
|
|
drawLine(pos1.toDirectXVector(),color1.toDirectXVector(),pos2.toDirectXVector(),color2.toDirectXVector());
|
|
}
|
|
void drawLine(const XMVECTOR pos1,const XMVECTOR &color1, const XMVECTOR pos2,const XMVECTOR &color2)
|
|
{
|
|
g_pPrimitiveBatchPositionColor->DrawLine(
|
|
VertexPositionColor(pos1, color1),
|
|
VertexPositionColor(pos2, color2)
|
|
);
|
|
}
|
|
void endLine()
|
|
{
|
|
g_pPrimitiveBatchPositionColor->End();
|
|
}
|
|
};
|
|
#endif |