The SimpleGrid Game.cpp code was becoming bloated with code for
various components. I took some time to extract the major pieces into
dedicated classes and interfaces.
Terrain
The Terrain class generates the surface, renders the polygons, and
provides information about the terrain.
Terrain.cpp:
Game.h:
typedef struct RectF_s { float left,top,right,bottom; } RECTF;
#define TERRAIN_OBJ_SIZE 72
class Terrain {
public:
Terrain(void);
~Terrain(void);
HRESULT SetGrid(const RECTF &rGrid, int cTile, float maxElevation);
HRESULT CreateShader(ID3D11Device *pDevice, ID3D11DeviceContext *pDC, BasicEffect *pEffect);
HRESULT CreateSurface();
void Reset(void);
void getVertex(float x0, float z0, VertexPositionNormalColor &vtxDst);
virtual void Render(ID3D11DeviceContext *pDC, CommonStates *pStates, const XMMATRIX &view, const XMMATRIX &proj);
private:
class nTerrain *pObj;
BYTE Obj[TERRAIN_OBJ_SIZE];
};
Terrain.cpp:
/*************************************************************************/
/** Terrain.cpp: Builds and manages the 3D terrain model. **/
/** (C)2017 nlited systems inc, chip doran **/
/*************************************************************************/
#include "pch.h"
#include <new>
#include <stdio.h>
#include <windows.h>
#include <d2d1_2.h>
#include <d2d1_1helper.h>
#include <dwrite_1.h>
#include <wrl/client.h>
#include "Game.h"
#pragma message(__FILE__": Optimizer disabled.")
#pragma optimize("",off)
using namespace DirectX;
#define Zero(O) memset(&O,0,sizeof(O))
#define CHECK_OBJ_SIZE(name,size) \
template<int N> struct name##_CheckSizeT { short operator()() { return((N+0x7FFF)-size); } }; \
static void name##_CheckSize(void) { name##_CheckSizeT<sizeof(name)>()(); }
#define SafeRelease(pObj) { if(pObj) pObj->Release(); pObj= 0; }
class nTerrain {
public:
nTerrain(void);
~nTerrain(void);
HRESULT SetGrid(const RECTF &rGrid, int cTile, float maxElevation);
HRESULT CreateShader(ID3D11Device *pDevice, ID3D11DeviceContext *pDC, BasicEffect *pEffect);
HRESULT CreateSurface(void);
void Reset(void);
VertexPositionNormalColor &getVertex(int x0, int z0) { return(vecVertex.at(z0*cTile+x0)); };
void getVertex(float x0, float z0, VertexPositionNormalColor &vtxDst);
void Render(ID3D11DeviceContext *pDC, CommonStates *pStates, const XMMATRIX &view, const XMMATRIX &proj);
private:
//Data
RECTF rGrid; //Grid boundaries
float szTile; //Dimensions of grid tiles
float maxElevation; //Maximum terrain elevation
int cTile; //Number of tiles per side.
bool doWireFrame; //Render as wireframe?
std::unique_ptr<PrimitiveBatch<VertexPositionNormalColor>> batchVertex;
std::vector<VertexPositionNormalColor> vecVertex;
ComPtr<ID3D11InputLayout> d3InputLayout;
};
// If this generates a compiler warning, DBTEXT_OBJ_SIZE needs to be updated.
CHECK_OBJ_SIZE(nTerrain,TERRAIN_OBJ_SIZE);
/*************************************************************************/
/** Public interface **/
/*************************************************************************/
Terrain::Terrain(void) {
Zero(Obj);
pObj= new(Obj) nTerrain();
}
Terrain::~Terrain(void) {
if(pObj)
pObj->~nTerrain();
pObj= 0;
}
HRESULT Terrain::SetGrid(const RECTF &rGrid, int cTile, float maxElevation) {
return(pObj ? pObj->SetGrid(rGrid,cTile,maxElevation):ERROR_INVALID_HANDLE);
}
HRESULT Terrain::CreateShader(ID3D11Device *pDevice,ID3D11DeviceContext *pDC,BasicEffect *pEffect) {
return(pObj ? pObj->CreateShader(pDevice,pDC,pEffect):ERROR_INVALID_HANDLE);
}
HRESULT Terrain::CreateSurface(void) {
return(pObj ? pObj->CreateSurface():ERROR_INVALID_HANDLE);
}
void Terrain::Reset(void) {
if(pObj)
pObj->Reset();
}
void Terrain::getVertex(float x0, float z0, VertexPositionNormalColor &vtxDst) {
return(pObj->getVertex(x0,z0,vtxDst));
}
void Terrain::Render(ID3D11DeviceContext *pDC, CommonStates *pStates, const XMMATRIX &view, const XMMATRIX &proj) {
return(pObj->Render(pDC,pStates,view,proj));
}
/*************************************************************************/
/** nTerrain **/
/*************************************************************************/
nTerrain::nTerrain(void) {
}
nTerrain::~nTerrain(void) {
}
HRESULT nTerrain::SetGrid(const RECTF &_rGrid, int _cTile, float _maxElevation) {
HRESULT Err= 0;
rGrid= _rGrid;
cTile= _cTile;
szTile= (rGrid.right - rGrid.left)/cTile;
maxElevation= _maxElevation;
return(Err);
}
HRESULT nTerrain::CreateShader(ID3D11Device *pDevice, ID3D11DeviceContext *pDC, BasicEffect *pEffect) {
HRESULT Err= 0;
void const *shaderByteCode;
size_t byteCodeLength;
pEffect->GetVertexShaderBytecode(&shaderByteCode,&byteCodeLength);
DX::ThrowIfFailed(pDevice->CreateInputLayout(
VertexPositionNormalColor::InputElements,
VertexPositionNormalColor::InputElementCount,
shaderByteCode,byteCodeLength,
d3InputLayout.ReleaseAndGetAddressOf()
));
batchVertex= std::make_unique<PrimitiveBatch<VertexPositionNormalColor>>(pDC);
return(Err);
}
void nTerrain::Reset(void) {
//TODO: Release resources.
}
HRESULT nTerrain::CreateSurface(void) {
HRESULT Err= 0;
// Create the 3D surface.
vecVertex.resize(cTile*cTile);
// Put the grid in the XZ plane with elevation along the Y axis.
// First pass calculates the position and color.
// (x0,z0) is an integer index into the X/Z plane of the grid.
for(int z0=0;z0<cTile;z0++) {
for(int x0=0;x0<cTile;x0++) {
//(x1,y1,z1) is a scaled value from 0.0 to 1.0 across the grid maximums.
float x1= (float)x0/(float)cTile;
float z1= (float)z0/(float)cTile;
float y0= -cosf(x1*4.0f*XM_PI)*sinf(z1*2.0f*XM_PI);
// z0 is the elevation function of the vertex, -PI <= z0 <= +PI
float y1= (y0+XM_PI)/(XM_PI*2.0f);
y1= (y1>1.0f) ? 1.0f : (y1<0.0f) ? 0.0f : y1;
//(x2,y2,z2) is the actual 3D position of the vertex.
float x2= x0*szTile;
float y2= y1*maxElevation;
float z2= z0*szTile;
Vector3 pos(x2,y2,z2);
Vector3 normal= -Vector3::UnitY;
// Avoid absolute black, which cannot be illuminated.
Vector3 color(0.5f,std::min(1.0f,std::max(0.0f,y1+0.10f)),0.5f);
getVertex(x0,z0)= VertexPositionNormalColor(pos,normal,color);
}
}
// Second pass calculates the normals (for lighting effects)
//Vector3 vMax= getVertex(cTile-1,cTile-1).position;
float dx= szTile;
float dz= szTile;
for(int z0=1;z0+1<cTile;z0++) {
for(int x0=1;x0+1<cTile;x0++) {
// Vertex normal is the sum of the adjacent vertices normals.
// See https://stackoverflow.com/questions/6656358/calculating-normals-in-a-triangle-mesh/
Vector3 vP= getVertex(x0,z0).position;
Vector3 vU= getVertex(x0,z0+1).position;
Vector3 vR= getVertex(x0+1,z0).position;
Vector3 vD= getVertex(x0,z0-1).position;
Vector3 vL= getVertex(x0-1,z0).position;
Vector3 sum;
sum.x= (vL.y - vR.y)/dx;
sum.z= (vD.y - vU.y)/dz;
sum.y= 2;
sum.Normalize();
getVertex(x0,z0).normal= sum;
}
}
return(Err);
}
//Returns nearest vertex
void nTerrain::getVertex(float x0, float z0, VertexPositionNormalColor &vtxDst) {
int nX= (int)(cTile*((x0-rGrid.left)/(rGrid.right - rGrid.left)));
nX= (nX<0) ? 0 : (nX>=cTile) ? cTile-1 : nX;
int nZ= (int)(cTile*((z0-rGrid.top)/(rGrid.bottom - rGrid.top)));
nZ= (nZ<0) ? 0 : (nZ>=cTile) ? cTile-1 : nZ;
vtxDst= getVertex(nX,nZ);
if(nX+1 < cTile && nZ+1 < cTile) {
// (x0,z0) will always be greater than pt(nX,nZ)
// Interpolate D(nX,nZ)+U(nX+1,nZ)+R(nX+1+nZ+1)+(nX,nZ+1)
Vector3 vT(x0,vtxDst.position.y,z0);
VertexPositionNormalColor v0(vtxDst);
VertexPositionNormalColor v1(getVertex(nX+1,nZ));;
VertexPositionNormalColor v2(getVertex(nX+1,nZ+1));
VertexPositionNormalColor v3(getVertex(nX,nZ+1));
// Determine the distance from vT to each known vertex.
float d0= SimpleMath::Vector3::Distance(vT,v0.position);
float d1= SimpleMath::Vector3::Distance(vT,v1.position);
float d2= SimpleMath::Vector3::Distance(vT,v2.position);
float d3= SimpleMath::Vector3::Distance(vT,v3.position);
float dMax= std::max(d0,std::max(d1,std::max(d2,d3)));
// Bias each vertex so that the closer vertex has more influence.
Vector3 dv0= (1.0f-(d0/dMax))*v0.position;
Vector3 dn0= (1.0f-(d0/dMax))*v0.normal;
Vector3 dv1= (1.0f-(d1/dMax))*v1.position;
Vector3 dn1= (1.0f-(d1/dMax))*v1.normal;
Vector3 dv2= (1.0f-(d2/dMax))*v2.position;
Vector3 dn2= (1.0f-(d2/dMax))*v2.normal;
Vector3 dv3= (1.0f-(d3/dMax))*v3.position;
Vector3 dn3= (1.0f-(d3/dMax))*v3.normal;
float dSum= 4.0f - (d0/dMax + d1/dMax + d2/dMax + d3/dMax);
Vector3 pos= (dv0+dv1+dv2+dv3)/dSum;
Vector3 nml= (dn0+dn1+dn2+dn3)/dSum;
vtxDst.position= pos;
vtxDst.normal= nml;
}
}
void nTerrain::Render(ID3D11DeviceContext *pDC, CommonStates *pStates, const XMMATRIX &view, const XMMATRIX &proj) {
pDC->OMSetBlendState(pStates->Opaque(),nullptr,0xFFFFFFFF);
pDC->OMSetDepthStencilState(pStates->DepthDefault(),0);
pDC->RSSetState(pStates->CullCounterClockwise());
pDC->IASetInputLayout(d3InputLayout.Get());
batchVertex->Begin();
VertexPositionNormalColor v0,v1,v2,v3;
for(int x0=0;x0+1<cTile;x0++) {
for(int z0=0;z0+1<cTile;z0++) {
v0= getVertex(x0,z0);
v1= getVertex(x0+1,z0);
v2= getVertex(x0+1,z0+1);
v3= getVertex(x0,z0+1);
if(doWireFrame) {
batchVertex->DrawLine(v0,v1);
batchVertex->DrawLine(v1,v2);
} else {
batchVertex->DrawQuad(v0,v1,v2,v3);
}
}
}
batchVertex->End();
}
//EOF: TERRAIN.CPP
Game Objects
The GameObj class is both the interface and base class for all the
various game objects. This includes two object types:
ObjSun: The sun orbits above the grid and illuminates it. It
should be the first object rendered (even before the terrain surface)
since it will set the light sources for the scene.
ObjPawn: This is a simple object that will glide across the
surface of the grid.
Game.cpp:
#include "pch.h"
#include "Game.h"
#pragma message(__FILE__": Optimizer disabled.")
#pragma optimize("",off)
extern void ExitGame();
#define Zero(O) memset(&O,0,sizeof(O))
#define DBG_RENDER 0x00000100
void Debug(DWORD Mask, const char *Fmt, ...) {
va_list ArgList;
char text[200];
int ChrCt= 0;
va_start(ArgList,Fmt);
ChrCt+= _vsnprintf(text+ChrCt,sizeof(text)-ChrCt,Fmt,ArgList);
ChrCt+= _snprintf(text+ChrCt,sizeof(text)-ChrCt,"\r\n");
OutputDebugStringA(text);
va_end(ArgList);
}
HRESULT Warn(HRESULT Err, const char *Fmt, ...) {
va_list ArgList;
char text[200];
int ChrCt= 0;
va_start(ArgList,Fmt);
ChrCt+= _snprintf(text+ChrCt,sizeof(text)-ChrCt,"WRN[%X]: ",Err);
ChrCt+= _vsnprintf(text+ChrCt,sizeof(text)-ChrCt,Fmt,ArgList);
ChrCt+= _snprintf(text+ChrCt,sizeof(text)-ChrCt,"\r\n");
OutputDebugStringA(text);
va_end(ArgList);
return(Err);
}
HRESULT Error(HRESULT Err, const char *Fmt, ...) {
va_list ArgList;
char text[200];
int ChrCt= 0;
va_start(ArgList,Fmt);
ChrCt+= _snprintf(text+ChrCt,sizeof(text)-ChrCt,"ERR[%X]: ",Err);
ChrCt+= _vsnprintf(text+ChrCt,sizeof(text)-ChrCt,Fmt,ArgList);
ChrCt+= _snprintf(text+ChrCt,sizeof(text)-ChrCt,"\r\n");
OutputDebugStringA(text);
va_end(ArgList);
return(Err);
}
/*************************************************************************/
/** DirectXTK framework **/
/** This code should be left mostly alone. **/
/*************************************************************************/
Game::Game():
hWnd(nullptr),
WndWid(800),
WndHgt(600),
d3FeatureLevel(D3D_FEATURE_LEVEL_9_1)
{
frameCt= 0;
Initialize2();
}
void Game::Initialize(HWND window, int width, int height) {
hWnd = window;
WndWid = std::max(width,1);
WndHgt = std::max(height,1);
QueryPerformanceFrequency(&clkFreq);
CreateDevice();
CreateResources();
Initialize3(window);
}
void Game::Tick(void) {
timerGame.Tick([&]() {
Update(timerGame);
});
Render();
}
void Game::Update(DX::StepTimer const& timer) {
float elapsedTime = float(timer.GetElapsedSeconds());
float totalTime= float(timer.GetTotalSeconds());
Update2(totalTime,elapsedTime);
}
HRESULT gQueryErr= 0;
void Game::Render(void) {
if(timerGame.GetFrameCount() == 0)
return;
frameCt++;
RenderTime();
Clear();
Render2();
Present();
}
void Game::Clear(void) {
d3dContext->ClearRenderTargetView(viewRT.Get(),Colors::CornflowerBlue);
d3dContext->ClearDepthStencilView(viewStencil.Get(),D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL,1.0f,0);
d3dContext->OMSetRenderTargets(1,viewRT.GetAddressOf(),viewStencil.Get());
CD3D11_VIEWPORT viewport(0.0f,0.0f,static_cast<float>(WndWid),static_cast<float>(WndHgt));
d3dContext->RSSetViewports(1,&viewport);
}
void Game::Present(void) {
HRESULT hr = swapChain->Present(1,0);
if(hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
OnDeviceLost();
} else {
DX::ThrowIfFailed(hr);
}
}
void Game::OnActivated(void) { }
void Game::OnDeactivated(void) { }
void Game::OnSuspending(void) { }
void Game::OnResuming(void) {
timerGame.ResetElapsedTime();
}
void Game::OnWindowSizeChanged(int width, int height) {
WndWid = std::max(width,1);
WndHgt = std::max(height,1);
CreateResources();
}
// Properties
void Game::GetDefaultSize(int& width, int& height) const {
width = 800;
height = 600;
}
void Game::CreateDevice(void) {
UINT creationFlags= D3D11_CREATE_DEVICE_BGRA_SUPPORT; //For Direct2D interop
#ifdef _DEBUG
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
static const D3D_FEATURE_LEVEL featureLevels[] ={
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1,
};
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
DX::ThrowIfFailed(D3D11CreateDevice(
nullptr, // specify nullptr to use the default adapter
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
creationFlags,
featureLevels,
_countof(featureLevels),
D3D11_SDK_VERSION,
device.ReleaseAndGetAddressOf(), // returns the Direct3D device created
&d3FeatureLevel, // returns feature level of device created
context.ReleaseAndGetAddressOf() // returns the device immediate context
));
#ifndef NDEBUG
ComPtr<ID3D11Debug> d3dDebug;
if(SUCCEEDED(device.As(&d3dDebug))) {
ComPtr<ID3D11InfoQueue> d3dInfoQueue;
if(SUCCEEDED(d3dDebug.As(&d3dInfoQueue))) {
#ifdef _DEBUG
d3dInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION,true);
d3dInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR,true);
#endif
D3D11_MESSAGE_ID hide[] =
{
D3D11_MESSAGE_ID_SETPRIVATEDATA_CHANGINGPARAMS,
// TODO: Add more message IDs here as needed.
};
D3D11_INFO_QUEUE_FILTER filter ={ };
filter.DenyList.NumIDs = _countof(hide);
filter.DenyList.pIDList = hide;
d3dInfoQueue->AddStorageFilterEntries(&filter);
}
}
#endif
DX::ThrowIfFailed(device.As(&d3dDevice));
DX::ThrowIfFailed(context.As(&d3dContext));
CreateDevice2();
}
void Game::CreateResources(void) {
// Clear the previous window size specific context.
ID3D11RenderTargetView* nullViews[] ={ nullptr };
d3dContext->OMSetRenderTargets(_countof(nullViews),nullViews,nullptr);
viewRT.Reset();
viewStencil.Reset();
d3dContext->Flush();
UINT backBufferWidth = static_cast<UINT>(WndWid);
UINT backBufferHeight = static_cast<UINT>(WndHgt);
DXGI_FORMAT backBufferFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
DXGI_FORMAT depthBufferFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
UINT backBufferCount = 2;
// If the swap chain already exists, resize it, otherwise create one.
if(swapChain) {
HRESULT hr = swapChain->ResizeBuffers(backBufferCount,backBufferWidth,backBufferHeight,backBufferFormat,0);
if(hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
// If the device was removed for any reason, a new device and swap chain will need to be created.
OnDeviceLost();
// Everything is set up now. Do not continue execution of this method. OnDeviceLost will reenter this method
// and correctly set up the new device.
return;
} else {
DX::ThrowIfFailed(hr);
}
} else {
// First, retrieve the underlying DXGI Device from the D3D Device.
ComPtr<IDXGIDevice1> dxgiDevice;
DX::ThrowIfFailed(d3dDevice.As(&dxgiDevice));
// Identify the physical adapter (GPU or card) this device is running on.
ComPtr<IDXGIAdapter> dxgiAdapter;
DX::ThrowIfFailed(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf()));
// And obtain the factory object that created it.
ComPtr<IDXGIFactory2> dxgiFactory;
DX::ThrowIfFailed(dxgiAdapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf())));
// Create a descriptor for the swap chain.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc ={ };
swapChainDesc.Width = backBufferWidth;
swapChainDesc.Height = backBufferHeight;
swapChainDesc.Format = backBufferFormat;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = backBufferCount;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsSwapChainDesc ={ };
fsSwapChainDesc.Windowed = TRUE;
// Create a SwapChain from a Win32 window.
DX::ThrowIfFailed(dxgiFactory->CreateSwapChainForHwnd(
d3dDevice.Get(),
hWnd,
&swapChainDesc,
&fsSwapChainDesc,
nullptr,
swapChain.ReleaseAndGetAddressOf()
));
// This template does not support exclusive fullscreen mode and prevents DXGI from responding to the ALT+ENTER shortcut.
DX::ThrowIfFailed(dxgiFactory->MakeWindowAssociation(hWnd,DXGI_MWA_NO_ALT_ENTER));
}
// Obtain the backbuffer for this window which will be the final 3D rendertarget.
ComPtr<ID3D11Texture2D> backBuffer;
DX::ThrowIfFailed(swapChain->GetBuffer(0,IID_PPV_ARGS(backBuffer.GetAddressOf())));
// Create a view interface on the rendertarget to use on bind.
DX::ThrowIfFailed(d3dDevice->CreateRenderTargetView(backBuffer.Get(),nullptr,viewRT.ReleaseAndGetAddressOf()));
// Allocate a 2-D surface as the depth/stencil buffer and
// create a DepthStencil view on this surface to use on bind.
CD3D11_TEXTURE2D_DESC depthStencilDesc(depthBufferFormat,backBufferWidth,backBufferHeight,1,1,D3D11_BIND_DEPTH_STENCIL);
ComPtr<ID3D11Texture2D> depthStencil;
DX::ThrowIfFailed(d3dDevice->CreateTexture2D(&depthStencilDesc,nullptr,depthStencil.GetAddressOf()));
CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
DX::ThrowIfFailed(d3dDevice->CreateDepthStencilView(depthStencil.Get(),&depthStencilViewDesc,viewStencil.ReleaseAndGetAddressOf()));
CreateResources2();
}
void Game::OnDeviceLost(void) {
viewStencil.Reset();
viewRT.Reset();
swapChain.Reset();
d3dContext.Reset();
d3dDevice.Reset();
OnDeviceLost2();
//Recreate everything
CreateDevice();
CreateResources();
}
/*************************************************************************/
/** TODO additions **/
/*************************************************************************/
// Game object has been created.
void Game::Initialize2(void) {
rGrid= { 0.0f, 0.0f, 5.0f, 5.0f };
tileCt= 100;
tileSz= (rGrid.right - rGrid.left)/(float)tileCt;
maxElevation= 4.0f;
}
void Game::SetVertexCt(UINT vertexCt) {
tileCt= (UINT)sqrt(vertexCt);
tileSz= (rGrid.right - rGrid.left)/(float)tileCt;
}
// Window has been created.
void Game::Initialize3(HWND window) {
keyboard= std::make_unique<Keyboard>();
mouse= std::make_unique<Mouse>();
mouse->SetWindow(window);
mouse->SetMode(Mouse::MODE_RELATIVE);
kbTab= false;
kbScrollLock= false;
doPause= false;
auto mouseState= mouse->GetState();
mousewheel= mouseState.scrollWheelValue;
}
// D3DDevice has been created.
void Game::CreateDevice2(void) {
HRESULT Err= 0;
dxStates= std::make_unique<CommonStates>(d3dDevice.Get());
dxEffect= std::make_unique<BasicEffect>(d3dDevice.Get());
dxEffect->SetVertexColorEnabled(true);
dxEffect->SetLightingEnabled(true);
dxEffect->SetLightEnabled(0,true);
dxEffect->SetLightDiffuseColor(0,Colors::White);
Vector3 light(1.0f,maxElevation*2.0f,1.0f);
light.Normalize();
dxEffect->SetLightDirection(0,light);
if(FAILED(Err= terrain.CreateShader(d3dDevice.Get(),d3dContext.Get(),dxEffect.get())))
Warn(Err,"Game:CreateDevice2: Terrain.Create() failed.");
if(FAILED(Err= GameObj::Create(pSun,"SUN",this,d3dContext.Get())))
Warn(Err,"Game:CreateDevice2: GameObj:Create(SUN) failed.");
GameObj::Create(pPawn,"PAWN",this,d3dContext.Get());
}
// 3D resources and game objects have been created.
void Game::CreateResources2(void) {
HRESULT Err= 0;
text.SetFont(L"Bitstream Vera Sans Mono",16.0f);
text.Create(d3dDevice.Get(),d3dContext.Get());
// Create the perspective.
doWireFrame= false;
viewPt= Vector3(tileCt*tileSz,maxElevation*1.50f,tileCt*tileSz);
viewVec.y= XMConvertToRadians(-170.0f);
viewVec.z= XMConvertToRadians(-2.0f);
fovAngle= XM_PI/4.0f;
fovNear= 0.1f;
fovFar= 20.0f;
mtrxProj= Matrix::CreatePerspectiveFieldOfView(fovAngle,float(WndWid)/float(WndHgt),fovNear,fovFar);
dxEffect->SetProjection(mtrxProj);
if(FAILED(Err= terrain.SetGrid(rGrid,tileCt,maxElevation)))
Err= Warn(Err,"Game:CreateResources2: Terrain:SetGrid failed.");
if(FAILED(Err= terrain.CreateSurface()))
Err= Warn(Err,"Game:CreateResources2: Terrain:CreateSurface failed.");
}
void Game::OnDeviceLost2(void) {
dxStates.reset();
dxEffect.reset();
pSun->Reset();
pPawn->Reset();
terrain.Reset();
text.Reset();
}
// Update game objects over time.
// Read keyboard and mouse inputs.
void Game::Update2(float totalTime, float elapsedTime) {
auto mouseState= mouse->GetState();
auto kb= keyboard->GetState();
// time input
if(doPause) {
elapsedTime= 0;
if(kb.OemPlus)
elapsedTime= 1/60.0f;
if(kb.OemMinus)
elapsedTime= -1/60.0f;
}
if(elapsedTime!=0) {
// Update game objects: Sun and Pawn.
pSun->Update(totalTime,elapsedTime,keyboard.get(),mouse.get());
pPawn->Update(totalTime,elapsedTime,keyboard.get(),mouse.get());
// Update light sources from sun.
//pSun->GetLights(dxEffect.get());
}
// Mouse input: yaw, pitch, and elevation.
Vector3 move= Vector3::Zero;
Vector3 delta= Vector3(float(mouseState.x),float(mouseState.y),0.0f);
viewPt.y+= float(mouseState.scrollWheelValue - mousewheel)*0.0005f;
mousewheel= mouseState.scrollWheelValue;
viewVec.y-= delta.x*0.0010f;
viewVec.z-= delta.y*0.0001f;
// "Gimbal lock" is when roll calculation becomes difficult as
// pitch approaches vertical. I avoid this by enforcing minimum
// and maximum limits on the pitch.
float limit = XM_PI/2.0f - 0.01f;
viewVec.z = std::max(-limit,viewVec.z);
viewVec.z = std::min(+limit,viewVec.z);
// Avoid spinning up yaw beyond +-2pi.
if(viewVec.y > XM_PI) {
viewVec.y -= XM_PI * 2.0f;
} else if(viewVec.y < -XM_PI) {
viewVec.y += XM_PI * 2.0f;
}
// Keyboard: forward, back, left, right, misc.
if(kb.Escape)
PostQuitMessage(0);
if(kb.Tab) {
if(!kbTab) {
doWireFrame= !doWireFrame;
kbTab= true;
}
} else {
kbTab= false;
}
if(kb.Scroll) {
if(!kbScrollLock)
doPause= !doPause;
kbScrollLock= true;
} else {
kbScrollLock= false;
}
// Navigation is easier if only yaw is used.
// Elevation (pitch) changes are controlled with the scroll wheel.
// This allows me to move around the world while looking straight down.
Quaternion moveQ= Quaternion::CreateFromYawPitchRoll(viewVec.y,0.0f,0.0f);
if(kb.Left || kb.A)
move.x= +1.0f;
if(kb.Right || kb.D)
move.x= -1.0f;
if(kb.Up || kb.W)
move.z= +1.0f;
if(kb.Down || kb.S)
move.z= -1.0f;
if(kb.PageUp)
move.y= +1.0f;
if(kb.PageDown)
move.y= -1.0f;
move= Vector3::Transform(move,moveQ)*0.010f;
viewPt+= move;
if(viewPt.y < 0.005f)
viewPt.y= 0.005f;
// Field of View adjustments
if(kb.OemComma) {
//Decrease
if(kb.LeftShift) {
//Decrease near plane
fovNear= fovNear*0.90f;
} else if(kb.RightShift) {
//Decrease far plane
fovFar= fovFar*0.90f;
} else if(kb.LeftControl || kb.RightControl) {
//Decrease angle
fovAngle-= 0.01f;
}
mtrxProj= Matrix::CreatePerspectiveFieldOfView(fovAngle,float(WndWid)/float(WndHgt),fovNear,fovFar);
}
if(kb.OemPeriod) {
if(kb.LeftShift) {
fovNear= fovNear*1.10f;
} else if(kb.RightShift) {
fovFar= fovFar*1.10f;
} else if(kb.LeftControl || kb.RightControl) {
fovAngle+= 0.01f;
}
mtrxProj= Matrix::CreatePerspectiveFieldOfView(fovAngle,float(WndWid)/float(WndHgt),fovNear,fovFar);
}
}
// Record the instantaneous frame rate for the last 10 seconds.
// I want the actual instantaneous frame rate, not an average.
// vecFrameTS is a FIFO of the high-resolution timestamps for each frame.
// Drop all timestamps that have expired (older than 10 seconds)
// The instantaneous framerate is the number of timestamps in the FIFO.
// The framerate history for the last 1000 frames is stored in vecFrameRate.
void Game::RenderTime(void) {
LARGE_INTEGER clkTick;
QueryPerformanceCounter(&clkTick);
vecFrameTS.push_front(clkTick);
INT64 clkExpire= clkTick.QuadPart - clkFreq.QuadPart*10;
while(vecFrameTS.size()>0 && vecFrameTS.back().QuadPart < clkExpire)
vecFrameTS.pop_back();
frameRate= (UINT16)(vecFrameTS.size()/10);
while(vecFrameRate.size() > 200)
vecFrameRate.pop_back();
vecFrameRate.push_front(frameRate);
}
// Draw the frame.
void Game::Render2(void) {
float r= 0.1f;
XMVECTOR lookAt= viewPt + Vector3(r*sinf(viewVec.y),sinf(viewVec.z),r*cosf(viewVec.y));
XMMATRIX view= XMMatrixLookAtRH(viewPt,lookAt,Vector3::Up);
dxEffect->SetView(view);
dxEffect->SetProjection(mtrxProj);
d3dContext->OMSetBlendState(dxStates->Opaque(),nullptr,0xFFFFFFFF);
d3dContext->OMSetDepthStencilState(dxStates->DepthDefault(),0);
d3dContext->RSSetState(dxStates->CullCounterClockwise());
dxEffect->Apply(d3dContext.Get());
// Render Sun first since it will set the lighting effects.
pSun->Render(d3dContext.Get(),dxEffect.get(),view,mtrxProj);
// Render terrain before game objects.
terrain.Render(d3dContext.Get(),dxStates.get(),view,mtrxProj);
// Render game objects.
RenderObjects(view);
RenderText();
}
// Draw the game objects.
void Game::RenderObjects(XMMATRIX &view) {
pPawn->Render(d3dContext.Get(),dxEffect.get(),view,mtrxProj);
}
// Draw 2D status text.
void Game::RenderText(void) {
//D3Text text(d3dDevice.Get(),d3dContext.Get(),swapChain.Get());
text.Begin(swapChain.Get());
text.SetColor(D2D1::ColorF(D2D1::ColorF::White,0.25f));
text.FillRect(D2D1::Point2F(0,0),D2D1::Point2F(600,64));
text.Write(L"Frame %06llu %ufps V%u\r\n",frameCt,frameRate,tileCt*tileCt);
text.Write(L"(%6.2f,%6.2f,%6.2f)",viewPt.x,viewPt.y,viewPt.z);
text.Write(L" %3.1f %3.1f\r\n",XMConvertToDegrees(viewVec.y),XMConvertToDegrees(viewVec.z));
text.Write(L"FOV %5.2f %5.2f/%5.2f\r\n",fovAngle,fovNear,fovFar);
text.DrawRectangle(D2D1::Point2F(10.0f,100.0f),D2D1::Point2F(210.0f,120.0f));
D2D1_POINT_2F pt0,pt1;
pt0= { 10.0f,120.0f-std::min((INT16)120,vecFrameRate[0])*(20.0f/120.0f) };
text.SetColor(D2D1::ColorF(D2D1::ColorF::Yellow));
for(UINT n1=1;n1<vecFrameRate.size();n1++) {
pt1= { 10.0f+n1,120.0f-std::min((INT16)120,vecFrameRate[n1])*(20.0f/120.0f) };
text.DrawLine(pt0,pt1);
pt0= pt1;
}
text.SetColor(D2D1::ColorF(D2D1::ColorF::Black));
text.End();
}
//EOF: SIMPLEGRID.CPP
This is mostly working, except that the sun is now being rendered
as a wireframe instead of a solid sphere. I suspect this is because it
is now being rendered before the terrain since the pawn, rendered
after the terrain, is appearing as a solid. Possibly the shader model
is not being set correctly? I had assumed the DirectXTK GeometricShape
class would handle everything.
WebV7 (C)2018 nlited | Rendered by tikope in 39.577ms | 18.191.200.47