This tutorial is part of a Collection: 02. DirectX 10 - Braynzar Soft Tutorials
rate up
0
rate down
3319
views
bookmark
20. Picking

Here we will build off the previous lesson, the skybox. We will learn how to translate a point on the screen (mouse position), to a ray which we will then find out whether it intersects with an object on the screen (if the object was "picked") or not. We will also learn how to go fullscreen!

There are no files for this tutorial
This Lesson builds off the last lesson, where we learned how to make a skybox using cube mapping. Although the end program I give you here is quite boring, picking is a much more exciting thing as it gives our game interaction. The result of this one is basically the same as the last lesson, but here we will create a vector of our spheres, so we have a couple targets to "pick". When we click on a sphere, it will disappear! So, How do we do this? Well, first we take the position of our mouse relative to our window. Then we take those two dimensional coordinates (x, y) and turn them into a 3 Dimensional ray in "View" space. After we have that 3D view space ray, we turn that into a 3D "World" space ray, where all the objects in the scene are. Then, we take that 3D world space ray, and turn it into a 3D "Local" or "Model" space ray for EACH of the objects on the scene. And finally, we check to see if the ray in that objects models space intersects with the model. Turning the 2D mouse coordinates all the way into a 3D World space ray only has to be done one time per mouse click (if a mouse click is what you want), but turning the 3D world space ray into a 3D Models space array, then checking if the ray intersects with the mesh, must be done for EVERY object (you wish to check if picked) in the scene! Now if your scene has a hundred thousand models, and each model has more than a thousand triangles, this can get very, very, very computationally expensive (not to mention collision detection, where every object that has a chance of coliding with something must be checked against everything possible to colide with), which is why there are techniques to cut down the amount of processing for things such as this. Such a technique might include Bounding Volumes, Such as a sphere or cube surrounding every object, where instead of testing whether the ray intersected with one of the thousands of triangles in a mesh, you only check if it intersected with a cube, or most popular, a sphere (or radius). We will not learn this technique in this exact lesson, but I promise you I will have one out soon on this. Other techniques might be in a category called Scene Management, where you divide your scene into sections, such as a bunch of rooms, and only check the objects in the sections that might have some sort of chance of being picked or colided with. Think about a house, or even better a castle. Maybe this castle is HUGE, with thousands of guards and bad guys. Instead of checking every possible person in the entire castle if they were picked, you could just check the room your in, which might contain two or three guards. Now you can imagine what kind of difference Scene Management, along with bounding volumes can give frames per second. Just a little info I thought might be nice to think about. To get the 2D coordinates of the mouse to View space is more mathematically complicated than I'd like to get into, so I'm not going to explain it in detail. But to get from view space to world space, then to local space is not quite as complicated. All you need to do is take the Inverse if the view space matrix to get the world matrix using D3DXMatrixInverse(), and transform the ray with D3DXVec3TransformCoord( ). Then to get it into Model space, transform the world space ray with the inverse of the models "local" space matrix. After we have the ray transformed into the models local space, we use the Intersect() of the ID3DXMesh interface. Lets get started. Also, I want to go into fullscreen. We will go into full screen mode for this so we don't have to worry about the window size and position changing. Here are our globals. The first is a true or false variable, which we will use to make sure you can't just hold down the mouse button and scroll over all the objects in the scene. The second is our pick function. The last three are used to make copies of our sphere model, The first is the number of spheres we want, the second is an arry that will hold a 0 or 1 for each sphere, depending on if it was picked or not, next is an array for each of the spheres local spaces. The last two integers are going to store the width and height of our client area, where direct3d draws to. They will be updated whenever the screen is resized, and will be used to calculate our picking ray. bool isShoot = false; void pick(float mouseX, float mouseY); int numSpheres = 20; int spheres[20]; D3DXMATRIX SphereLocalSpace[20]; int ClientWidth = 0; int ClientHeight = 0; We are going to set fullscreen mode to true when initializing direct3d. We do this when we are creating our swap chain description. scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; scd.BufferCount = 1; scd.OutputWindow = hwnd; scd.Windowed = false; scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; scd.Flags = 0; Now, if you have already tried to go full screen, you may have noticed that the application might freeze if you try to press the escape button, and exit your application. To fix this, we will set our application to windowed mode right before we exit the application when we press the escape key. Go down to the direct input function, where it checks for the escape key, and add that new line. if(keyboardState[DIK_ESCAPE] & 0x80) { SwapChain->SetFullscreenState(false, NULL); PostMessage(hwnd, WM_DESTROY, 0, 0); } Now lets go down all the way to our pick function. Like I said earlier, first we get the coordinates of the mouse (passed in from our direct input function), and turn them into a 3D ray in view space. We set Z to 1.0f because that is how deep our back buffer is in view space. Then we set our ray position and direction. Position is at point (0,0,0) because we are in view space at this time. If you are creating a first person shooter, it is possible you will not be using the actual mouses coordinates, although it may seem that way a lot of times when playing a first person shooter. Instead, you are probably going to be using the CENTER of the screen, where you will have an image of a crosshair or something. To get the center of the screen is actually easier than getting the mouse coordinates. Instead of "mouseX" and "mouseY", you would put "(Width/2)" and "(Height/2)". And remember to change the Mouse CooperativeLevel in the direct input init function from DISCL_NONEXCLUSIVE to DISCL_EXCLUSIVE to remove the mouse from the screen. void pick(float mouseX, float mouseY) { //Transform 2D pick position on screen space to 3D ray in View space D3DXVECTOR3 pickRayViewSpace; pickRayViewSpace.x = ((( 2.0f * mouseX) / ClientWidth ) - 1 ) / Projection(0,0); pickRayViewSpace.y = -((( 2.0f * mouseY) / ClientHeight) - 1 ) / Projection(1,1); pickRayViewSpace.z = 1.0f; D3DXVECTOR3 pickRayViewSpacePos(0.0f, 0.0f, 0.0f); D3DXVECTOR3 pickRayViewSpaceDir(pickRayViewSpace.x, pickRayViewSpace.y, pickRayViewSpace.z); Now we create a matrix to hold the inverse of our view space matrix, then transform our view space ray position and direction into this new matrix. This transforms our view space ray into a world space ray. As you can see, we use the function D3DXVec3TransformNormal(). This is because the TransformCoord function understands W = 1 so is used to transform points, and the TransformNormal function understands W = 0 so is used to transform vectors. // Transform 3D Ray from View space to 3D ray in World space D3DXMATRIX pickRayWorldSpace; D3DXMatrixInverse(&pickRayWorldSpace, 0, &View); D3DXVec3TransformCoord(&pickRayViewSpacePos, &pickRayViewSpacePos, &pickRayWorldSpace); D3DXVec3TransformNormal(&pickRayViewSpaceDir, &pickRayViewSpaceDir, &pickRayWorldSpace); This is the part that can give you a larger than prefered fps. In other words, this is the part than can really slow down your game. First we create two variables. One to represent the closest sphere we have picked (closest as closest to the camera), and one to represent the distance of the closest object. We set the closest object to -1, since arrays do not contain a -1, and we set the closest objects distance to FLT_MAX, which is the largest float value possible. Then as the loop checks each object for an intersection, it will first load the distance to the object that is first intersected, and the value of the closest object distance will change to the distance of any other object that has been intersected afterwards that is closer to the camera. Each time an object has been intersected which is closer than the previous intersected object, closestObject is updated with the index value of that object in the sphere array. Now we enter the loop for each object that is to be checked for an intersection with the ray. // Transform 3D Ray from World space to each objects/models own local space int closestObject = -1; float closestObjectDist = FLT_MAX; for(int i = 0; i < numSpheres; i++) { We first check to make sure that that sphere in the sphere array has not already been picked, because if it has, it would be pointless to check again. Then we create a matrix to hold the inverse of our objects local space matrix. We then create two new vectors, one for the local space ray direction, and one for the position. We then transform these two vectors using the inverse matrix of the objects local space matrix. if(spheres[i] == 1) { D3DXMATRIX pickRayLocalSpace; D3DXMatrixInverse(&pickRayLocalSpace,NULL,&SphereLocalSpace[i]); D3DXVECTOR3 pickRayLocalSpacePos,pickRayLocalSpaceDir; D3DXVec3TransformCoord(&pickRayLocalSpacePos,&pickRayViewSpacePos,&pickRayLocalSpace); D3DXVec3TransformNormal(&pickRayLocalSpaceDir,&pickRayViewSpaceDir,&pickRayLocalSpace); D3DXVec3Normalize(&pickRayLocalSpaceDir,&pickRayLocalSpaceDir); After we have transformed the ray into an objects local space, we need to test for an intersection. First we create an unsigned int to hold the number of times the mesh was hit. We need this to know if the mesh was hit at least one time. Nest we make a float variable, which will hold the distance to the actual intersection. Then, we check for the intersection. Before we check for an intersection with the actual mesh, this is where we would would want to first check with its bounding volume, such as a sphere, to see if the ray was even close to it. If it intersected with the bounding volume, then we would check the actual mesh. We might also check for other things before checking for an intersection for the mesh, like if the mesh was even in the same room or a possible target. Anyway, we use the Intersect method of the ID3DX10Mesh interface to check for an intersection. Here it is: HRESULT Intersect( [in] D3DXVECTOR3 *pRayPos, [in] D3DXVECTOR3 *pRayDir, [in] UINT *pHitCount, [in] UINT *pFaceIndex, [in] float *pU, [in] float *pV, [in] float *pDist, [out] ID3D10Blob **ppAllHits ); **pRayPos **- This is the Position of our ray in the objects local space. **pRayDir **- This is the Direction of our ray in the objects local space. **pHitCount **- This a pointer to a unsigned integer which stores the number of times the object was intersected by the ray. **pFaceIndex **- This is a pointer to an unsigned integer which holds the index value in an index array of the triangle that was intersected closest to the rays origin. **pU **- Pointer to a barycentric hit coordinate, U. **pV **- Pointer to a barycentric hit coordinate, V. **pDist **- This is a pointer to a float value wich holds the distance to the nearest face to the ray's origin that the ray intersected. **ppAllHits** - This is a pointer to an ID3D10Blob Interface, which holds all the information about every intersection the ray made with the mesh. The U,V barycentric coordinates are a way to describe where exactly the triange was hit. If you are interested, here is a link I found on msdn which explains them quite well. Mathworld's Barycentric Coordinates Description UINT numHits = 0; float hitDistance; meshes[1]->Intersect(&pickRayLocalSpacePos, &pickRayLocalSpaceDir, &numHits, 0, 0, 0, &hitDistance, 0); Now we check to see if this particular mesh was intersected. If it was, we check to see if the distance to the intersection is closer than the distance previously stored. And if it is, we set the closest object to the current objects index (i), and the distance to this objects intersection. //Find nearest object hit, if any if(numHits > 0) { if(hitDistance < closestObjectDist) { closestObject = i; closestObjectDist = hitDistance; } } } } After we check all the objects we want, we check to make sure an object was hit. If an object was hit, this is where we would do what we want, and in this case, set the current spheres index value to zero, meaning it will not be drawn on the screen the next frame, and will not be checked for an intersection next time. if(closestObject >= 0) { spheres[closestObject] = 0; } } Now we go to our direct input function, where we check to make sure a mouse button was pressed. If it was, we create a point variable to store the mouses coordinates, get the mouses current screen coordinates, then convert the screen coordinates to our applications client area coordinates. Then we set mousex and mousey to the x and y coordinates of our mouse, then call our pick function. After that we set isShoot to be true, so we can't just hold down the mouse button and pick every object we scroll over. After that we check to make sure the mouse button was let up, if it was, we set the isShoot value back to false, so we can pick another object. You may wonder why we are not using a direct input function to find the position of the mouse. Well, we could actually use direct input, but it's easier this way because Direct input uses "relative" mouse coordinates, where as this way takes "absolute" mouse coordinates. What this means is to find the ACTUAL position of the mouse using direct input, we would need to have kept track of all its movements from the beginning of the application, which is not hard to impliment of course. This way we just get the exact coordinates without worrying about adding and subtracting the mouses movements to find it actual position on the screen. if(mouseCurrState.rgbButtons[0]) { if(isShoot == false) { POINT mousePos; GetCursorPos(&mousePos); ScreenToClient(hwnd, &mousePos); int mousex = mousePos.x; int mousey = mousePos.y; pick(mousex, mousey); isShoot = true; } } if(!mouseCurrState.rgbButtons[0]) { isShoot = false; } Well, thats basically it for all the new stuff. I'll continue to show you whats up though. Here, in the initScene function, we are setting every value in the spheres array to 0, meaning they will all be drawn to the screen at first. for( int i = 0; i < numSpheres; i++) { spheres[i] = 1; } Its not too hard to see whats happening here. We create a couple floats to store the positions of our spheres. then we enter a loop which will go through the number of spheres in the spheres array. We update each spheres local space using those floats, then draw each sphere IF they have not already been picked. float sphereXPos = -30.0f; float sphereZPos = 30.0f; float sxadd = 0.0f; float szadd = 0.0f; for(int i = 0; i < numSpheres; i++) { sxadd++; if(sxadd == 10) { szadd -= 1.0f; sxadd = 0; } D3DXMatrixScaling( &Scale, 2.0f, 2.0f, 2.0f ); D3DXMatrixTranslation( &Translation, sphereXPos + sxadd*10.0f, 1.0f, sphereZPos + szadd*10.0f ); SphereLocalSpace[i] = Scale * Translation; WVP = SphereLocalSpace[i] * View * Projection; fxWVPVar->SetMatrix((float*)&WVP); //draw sphere if(spheres[i] == 1) { for( UINT p = 0; p < techDesc.Passes; ++p ) { for(UINT subsetID = 0; subsetID < meshSubsets[1]; ++subsetID) { fxDiffuseMapVar->SetResource(TextureResourceViews[subsetID + meshSubsets[0]]); Technique->GetPassByIndex( p )->Apply( 0 ); meshes[1]->DrawSubset(subsetID); } } } } Here we are at the last new thing. In our windows proc function, We need to check for a new message called "WM_SIZE". This message is sent whenever our window has been resized. We need to check every time our window is resized, so we can update the client area and keep our picking ray presice. This will also be called when we first create our window, so this is where we can set the size of our ClientWidth and ClientHeight variables. case WM_SIZE: ClientWidth = LOWORD(lParam); ClientHeight = HIWORD(lParam); return 0; } Now we know how to pick objects with our mouse! I hope you found this helpfull! Here's the final code: main.cpp #include <Windows.h> #include <d3d10.h> #include <d3dx10.h> #include <string> #include <vector> #include <dinput.h> #include <fstream> #include <istream> #pragma comment (lib, "dinput8.lib") #pragma comment (lib, "dxguid.lib") #pragma comment(lib, "D3D10.lib") #pragma comment(lib, "d3dx10d.lib") using namespace std; LPCTSTR WndClassName = L"firstwindow"; HWND hwnd = NULL; const int Width = 512; const int Height = 512; bool InitializeWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed); HRESULT hr; ID3D10Device* d3dDevice; IDXGISwapChain* SwapChain; ID3D10RenderTargetView* RenderTargetView; ID3D10Effect* FX; ID3D10InputLayout* VertexLayout; ID3D10Buffer* VertexBuffer; ID3D10Buffer* IndexBuffer; ID3D10EffectTechnique* Technique; ID3D10DepthStencilView* DepthStencilView; ID3D10Texture2D* DepthStencilBuffer; ID3D10EffectShaderResourceVariable* fxDiffuseMapVar; ID3D10EffectMatrixVariable* fxWVPVar; D3DXMATRIX WVP; D3DXMATRIX World; D3DXMATRIX View; D3DXMATRIX Projection; ID3D10EffectVariable* fxLightVar; IDirectInputDevice8* DIKeyboard; IDirectInputDevice8* DIMouse; DIMOUSESTATE mouseLastState; LPDIRECTINPUT8 DirectInput; vector<ID3DX10Mesh*> meshes; int meshCount; bool LoadMesh(wstring filename); int meshTextures = 0; vector<ID3D10ShaderResourceView*> TextureResourceViews; vector<UINT> meshSubsets; DWORD NumVertices; DWORD NumFaces; ID3D10EffectShaderResourceVariable* fxSkyMapVar; ID3D10ShaderResourceView* smrv = 0; ID3D10EffectTechnique* SkyMapTechnique; void CreateSphere(int LatLines, int LongLines); /////////////////////////new///////////////////////////////////////////////// bool isShoot = false; void pick(float mouseX, float mouseY); int numSpheres = 20; int spheres[20]; D3DXMATRIX SphereLocalSpace[20]; int ClientWidth = 0; int ClientHeight = 0; /////////////////////////new///////////////////////////////////////////////// float moveLeftRight = 0.0f; float moveBackForward = 0.0f; D3DXVECTOR3 Position = D3DXVECTOR3( 0.0f, 1.0f, 0.0f ); D3DXVECTOR3 Target = D3DXVECTOR3( 0.0f, 0.0f, 1.0f); D3DXVECTOR3 Up = D3DXVECTOR3( 0.0f, 1.0f, 0.0f ); D3DXVECTOR3 DefaultForward = D3DXVECTOR3(0.0f,0.0f,1.0f); D3DXVECTOR3 DefaultRight = D3DXVECTOR3(1.0f,0.0f,0.0f); D3DXVECTOR3 Forward = D3DXVECTOR3(0.0f,0.0f,1.0f); D3DXVECTOR3 Right = D3DXVECTOR3(1.0f,0.0f,0.0f); D3DXMATRIX rotationMatrix; float yaw = 0.0f; float pitch = 0.0f; void UpdateCamera(); D3DXMATRIX Scale; D3DXMATRIX Translation; D3DXMATRIX Transformations; bool InitializeDirect3dApp(HINSTANCE hInstance); bool InitDirectInput(HINSTANCE hInstance); void DetectInput(); bool InitScene(); void DrawScene(); bool ReleaseObjects(); int messageloop(); LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); struct Vertex { Vertex(){} Vertex(float x, float y, float z, float u, float v, float nx, float ny, float nz) : pos(x,y,z), texCoord(u,v), normal(nx,ny,nz){} D3DXVECTOR3 pos; D3DXVECTOR2 texCoord; D3DXVECTOR3 normal; }; D3D10_INPUT_ELEMENT_DESC layout[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 }, {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0}, {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D10_INPUT_PER_VERTEX_DATA, 0} }; struct Light { Light() { ZeroMemory(this, sizeof(Light)); } D3DXVECTOR3 dir; float pad; D3DXCOLOR ambient; D3DXCOLOR diffuse; }; Light light; int WINAPI WinMain(HINSTANCE hInstance, //Main windows function HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { /////////////////////////new///////////////////////////////////////////////// if(!InitializeWindow(hInstance, nShowCmd, Width, Height, false)) { MessageBox(0, L"Window Initialization - Failed", L"Error", MB_OK); return 0; } /////////////////////////new///////////////////////////////////////////////// if(!InitializeDirect3dApp(hInstance)) { MessageBox(0, L"Direct3D Initialization - Failed", L"Error", MB_OK); return 0; } if(!InitDirectInput(hInstance)) { MessageBox(0, L"Direct Input Initialization - Failed", L"Error", MB_OK); return 0; } if(!InitScene()) { MessageBox(0, L"Scene Initialization - Failed", L"Error", MB_OK); return 0; } messageloop(); if(!ReleaseObjects()) { MessageBox(0, L"Object Releasing - Failed", L"Error", MB_OK); return 0; } return 0; } bool InitializeWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed) { typedef struct _WNDCLASS { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS; WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = NULL; wc.cbWndExtra = NULL; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 2); wc.lpszMenuName = NULL; wc.lpszClassName = WndClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if (!RegisterClassEx(&wc)) { MessageBox(NULL, L"Error registering class", L"Error", MB_OK | MB_ICONERROR); return 1; } hwnd = CreateWindowEx( NULL, WndClassName, L"Window Title", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, NULL ); if (!hwnd) { MessageBox(NULL, L"Error creating window", L"Error", MB_OK | MB_ICONERROR); return 1; } ShowWindow(hwnd, ShowWnd); UpdateWindow(hwnd); return true; } bool InitializeDirect3dApp(HINSTANCE hInstance) { UINT createDeviceFlags = 0; D3D10_DRIVER_TYPE driverTypes[] = { D3D10_DRIVER_TYPE_HARDWARE, D3D10_DRIVER_TYPE_REFERENCE, }; UINT numDriverTypes = sizeof( driverTypes ) / sizeof( driverTypes[0] ); DXGI_SWAP_CHAIN_DESC scd; scd.BufferDesc.Width = Width; scd.BufferDesc.Height = Height; scd.BufferDesc.RefreshRate.Numerator = 60; scd.BufferDesc.RefreshRate.Denominator = 1; scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; scd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; scd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; //no multisampling scd.SampleDesc.Count = 1; scd.SampleDesc.Quality = 0; scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; scd.BufferCount = 1; scd.OutputWindow = hwnd; /////////////////////////new///////////////////////////////////////////////// scd.Windowed = false; /////////////////////////new///////////////////////////////////////////////// scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; scd.Flags = 0; D3D10CreateDeviceAndSwapChain(0, D3D10_DRIVER_TYPE_HARDWARE, 0, 0, D3D10_SDK_VERSION, &scd, &SwapChain, &d3dDevice); ID3D10Texture2D* backBuffer; SwapChain->GetBuffer(0, _uuidof(ID3D10Texture2D), reinterpret_cast<void**>(&backBuffer)); d3dDevice->CreateRenderTargetView(backBuffer, 0, &RenderTargetView); backBuffer->Release(); D3D10_TEXTURE2D_DESC depthStencilDesc; depthStencilDesc.Width = Width; depthStencilDesc.Height = Height; depthStencilDesc.MipLevels = 1; depthStencilDesc.ArraySize = 1; depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthStencilDesc.SampleDesc.Count = 1; depthStencilDesc.SampleDesc.Quality = 0; depthStencilDesc.Usage = D3D10_USAGE_DEFAULT; depthStencilDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL; depthStencilDesc.CPUAccessFlags = 0; depthStencilDesc.MiscFlags = 0; d3dDevice->CreateTexture2D(&depthStencilDesc, NULL, &DepthStencilBuffer); d3dDevice->CreateDepthStencilView(DepthStencilBuffer, NULL, &DepthStencilView); d3dDevice->OMSetRenderTargets(1, &RenderTargetView, DepthStencilView); // Setup the viewport D3D10_VIEWPORT vp; vp.Width = Width; vp.Height = Height; vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = 0; vp.TopLeftY = 0; d3dDevice->RSSetViewports( 1, &vp ); D3DXMatrixIdentity( &World ); Position = D3DXVECTOR3( 0.0f, 4.0f, -10.0f ); Target = D3DXVECTOR3( 0.0f, 0.0f, 0.0f ); Up = D3DXVECTOR3( 0.0f, 1.0f, 0.0f ); D3DXMatrixLookAtLH( &View, &Position, &Target, &Up ); return true; } bool InitDirectInput(HINSTANCE hInstance) { DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&DirectInput, NULL); DirectInput->CreateDevice(GUID_SysKeyboard, &DIKeyboard, NULL); DirectInput->CreateDevice(GUID_SysMouse, &DIMouse, NULL); DIKeyboard->SetDataFormat(&c_dfDIKeyboard); DIKeyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); DIMouse->SetDataFormat(&c_dfDIMouse); DIMouse->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_NOWINKEY | DISCL_FOREGROUND); return true; } void UpdateCamera() { D3DXMatrixRotationYawPitchRoll( &rotationMatrix, yaw, pitch, 0 ); D3DXVec3TransformCoord( &Target, &DefaultForward, &rotationMatrix ); D3DXVec3Normalize( &Target, &Target ); D3DXMATRIX RotateYTempMatrix; D3DXMatrixRotationY(&RotateYTempMatrix, yaw); D3DXVec3TransformNormal(&Right, &DefaultRight, &RotateYTempMatrix); D3DXVec3TransformNormal(&Up, &Up, &RotateYTempMatrix); D3DXVec3TransformNormal(&Forward, &DefaultForward, &RotateYTempMatrix); Position += moveLeftRight*Right; Position += moveBackForward*Forward; moveLeftRight = 0.0f; moveBackForward = 0.0f; Target = Position + Target; D3DXMatrixLookAtLH( &View, &Position, &Target, &Up ); } /////////////////////////new///////////////////////////////////////////////// void pick(float mouseX, float mouseY) { //Transform 2D pick position on screen space to 3D ray in View space D3DXVECTOR3 pickRayViewSpace; pickRayViewSpace.x = ((( 2.0f * mouseX) / ClientWidth ) - 1 ) / Projection(0,0); pickRayViewSpace.y = -((( 2.0f * mouseY) / ClientHeight) - 1 ) / Projection(1,1); pickRayViewSpace.z = 1.0f; D3DXVECTOR3 pickRayViewSpacePos(0.0f, 0.0f, 0.0f); D3DXVECTOR3 pickRayViewSpaceDir(pickRayViewSpace.x, pickRayViewSpace.y, pickRayViewSpace.z); // Transform 3D Ray from View space to 3D ray in World space D3DXMATRIX pickRayWorldSpace; D3DXMatrixInverse(&pickRayWorldSpace, 0, &View); D3DXVec3TransformCoord(&pickRayViewSpacePos, &pickRayViewSpacePos, &pickRayWorldSpace); D3DXVec3TransformNormal(&pickRayViewSpaceDir, &pickRayViewSpaceDir, &pickRayWorldSpace); // Transform 3D Ray from World space to each objects/models own local space int closestObject = -1; float closestObjectDist = FLT_MAX; for(int i = 0; i < numSpheres; i++) { if(spheres[i] == 1) { D3DXMATRIX pickRayLocalSpace; D3DXMatrixInverse(&pickRayLocalSpace,NULL,&SphereLocalSpace[i]); D3DXVECTOR3 pickRayLocalSpacePos,pickRayLocalSpaceDir; D3DXVec3TransformCoord(&pickRayLocalSpacePos,&pickRayViewSpacePos,&pickRayLocalSpace); D3DXVec3TransformNormal(&pickRayLocalSpaceDir,&pickRayViewSpaceDir,&pickRayLocalSpace); D3DXVec3Normalize(&pickRayLocalSpaceDir,&pickRayLocalSpaceDir); UINT numHits = 0; float hitDistance; meshes[1]->Intersect(&pickRayLocalSpacePos, &pickRayLocalSpaceDir, &numHits, 0, 0, 0, &hitDistance, 0); //Find nearest object hit, if any if(numHits > 0) { if(hitDistance < closestObjectDist) { closestObject = i; closestObjectDist = hitDistance; } } } } if(closestObject >= 0) { spheres[closestObject] = 0; } } /////////////////////////new///////////////////////////////////////////////// void DetectInput() { float speed = 0.005f; DIMOUSESTATE mouseCurrState; BYTE keyboardState[256]; DIKeyboard->Acquire(); DIMouse->Acquire(); DIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &mouseCurrState); DIKeyboard->GetDeviceState(sizeof(keyboardState),(LPVOID)&keyboardState); /////////////////////////new///////////////////////////////////////////////// if(keyboardState[DIK_ESCAPE] & 0x80) { SwapChain->SetFullscreenState(false, NULL); PostMessage(hwnd, WM_DESTROY, 0, 0); } /////////////////////////new///////////////////////////////////////////////// if(keyboardState[DIK_A] & 0x80) { moveLeftRight -= speed; } if(keyboardState[DIK_D] & 0x80) { moveLeftRight += speed; } if(keyboardState[DIK_W] & 0x80) { moveBackForward += speed; } if(keyboardState[DIK_S] & 0x80) { moveBackForward -= speed; } if((mouseCurrState.lX != mouseLastState.lX) || (mouseCurrState.lY != mouseLastState.lY)) { yaw += mouseLastState.lX * 0.001f; pitch += mouseCurrState.lY * 0.001f; mouseLastState = mouseCurrState; } /////////////////////////new///////////////////////////////////////////////// if(mouseCurrState.rgbButtons[0]) { if(isShoot == false) { POINT mousePos; GetCursorPos(&mousePos); ScreenToClient(hwnd, &mousePos); int mousex = mousePos.x; int mousey = mousePos.y; pick(mousex, mousey); isShoot = true; } } if(!mouseCurrState.rgbButtons[0]) { isShoot = false; } /////////////////////////new///////////////////////////////////////////////// UpdateCamera(); return; } bool LoadMesh(wstring filename) { HRESULT hr = 0; ID3DX10Mesh* tempMesh; UINT tempMeshSubsets; wifstream fileIn (filename.c_str()); wstring skipString; UINT meshVertices = 0; UINT meshTriangles = 0; if (fileIn) { fileIn >> skipString; // #Subsets fileIn >> tempMeshSubsets; fileIn >> skipString; // #Vertices fileIn >> meshVertices; fileIn >> skipString; // #Faces (Triangles) fileIn >> meshTriangles; meshSubsets.push_back(tempMeshSubsets); hr = D3DX10CreateMesh(d3dDevice, layout, 3, layout[0].SemanticName, meshVertices, meshTriangles, D3DX10_MESH_32_BIT, &tempMesh); if(FAILED(hr)) { MessageBox(0, L"Mesh Creation - Failed", L"Error", MB_OK); return false; } fileIn >> skipString; //#Subset_info for(UINT i = 0; i < tempMeshSubsets; ++i) { std::wstring diffuseMapFilename; fileIn >> diffuseMapFilename; ID3D10ShaderResourceView* DiffuseMapResourceView; D3DX10CreateShaderResourceViewFromFile(d3dDevice, diffuseMapFilename.c_str(), 0, 0, &DiffuseMapResourceView, 0 ); TextureResourceViews.push_back(DiffuseMapResourceView); meshTextures++; } Vertex* verts = new Vertex[meshVertices]; fileIn >> skipString; //#Vertex_info for(UINT i = 0; i < meshVertices; ++i) { fileIn >> skipString; //Vertex Position fileIn >> verts[i].pos.x; fileIn >> verts[i].pos.y; fileIn >> verts[i].pos.z; fileIn >> skipString; //Vertex Normal fileIn >> verts[i].normal.x; fileIn >> verts[i].normal.y; fileIn >> verts[i].normal.z; fileIn >> skipString; //Vertex Texture Coordinates fileIn >> verts[i].texCoord.x; fileIn >> verts[i].texCoord.y; } tempMesh->SetVertexData(0, verts); delete[] verts; DWORD* indices = new DWORD[meshTriangles*3]; UINT* attributeIndex = new UINT[meshTriangles]; fileIn >> skipString; //#Face_Index for(UINT i = 0; i < meshTriangles; ++i) { fileIn >> indices[i*3+0]; fileIn >> indices[i*3+1]; fileIn >> indices[i*3+2]; fileIn >> attributeIndex[i]; //Current Subset } tempMesh->SetIndexData(indices, meshTriangles*3); tempMesh->SetAttributeData(attributeIndex); delete[] indices; delete[] attributeIndex; tempMesh->GenerateAdjacencyAndPointReps(0.001f); tempMesh->Optimize(D3DX10_MESHOPT_ATTR_SORT|D3DX10_MESHOPT_VERTEX_CACHE,0,0); tempMesh->CommitToDevice(); meshCount++; meshes.push_back(tempMesh); } else { MessageBox(0, L"Load Mesh File - Failed", L"Error", MB_OK); return false; } return true; } void CreateSphere(int LatLines, int LongLines) { NumVertices = LatLines * LongLines; NumFaces = (LatLines-1)*(LongLines-1)*2; float sphereYaw = 0.0f; float spherePitch = 0.0f; std::vector<Vertex> vertices(NumVertices); D3DXVECTOR3 currVertPos = D3DXVECTOR3(0.0f, 1.0f, 0.0f); for(DWORD i = 0; i < LatLines; ++i) { sphereYaw = i * (3.14/LatLines); for(DWORD j = 0; j < LongLines; ++j) { spherePitch = j * (3.14/LongLines); D3DXMatrixRotationYawPitchRoll( &rotationMatrix, sphereYaw, spherePitch, 0 ); D3DXVec3TransformCoord( &currVertPos, &DefaultForward, &rotationMatrix ); D3DXVec3Normalize( &currVertPos, &currVertPos ); vertices[i*LongLines+j].pos = currVertPos; } } D3D10_BUFFER_DESC bd; bd.Usage = D3D10_USAGE_IMMUTABLE; bd.ByteWidth = sizeof( Vertex ) * NumVertices; bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; bd.MiscFlags = 0; D3D10_SUBRESOURCE_DATA InitData; InitData.pSysMem = &vertices[0]; d3dDevice->CreateBuffer( &bd, &InitData, &VertexBuffer ); std::vector<DWORD> indices(NumFaces * 3); int k = 0; for(DWORD i = 0; i < LatLines-1; ++i) { for(DWORD j = 0; j < LongLines-1; ++j) { indices[k] = i*LongLines+j; indices[k+1] = i*LongLines+j+1; indices[k+2] = (i+1)*LongLines+j; indices[k+3] = (i+1)*LongLines+j; indices[k+4] = i*LongLines+j+1; indices[k+5] = (i+1)*LongLines+j+1; k += 6; } } D3D10_BUFFER_DESC ibd; ibd.Usage = D3D10_USAGE_IMMUTABLE; ibd.ByteWidth = sizeof(DWORD) * NumFaces * 3; ibd.BindFlags = D3D10_BIND_INDEX_BUFFER; ibd.CPUAccessFlags = 0; ibd.MiscFlags = 0; D3D10_SUBRESOURCE_DATA iinitData; iinitData.pSysMem = &indices[0]; d3dDevice->CreateBuffer(&ibd, &iinitData, &IndexBuffer); UINT stride = sizeof( Vertex ); UINT offset = 0; d3dDevice->IASetVertexBuffers( 0, 1, &VertexBuffer, &stride, &offset ); d3dDevice->IASetIndexBuffer(IndexBuffer, DXGI_FORMAT_R32_UINT, 0); } bool InitScene() { HRESULT hr = 0; ID3D10Blob* compilationErrors = 0; LoadMesh(L"plane.dat"); //meshes[0] LoadMesh(L"sphere.dat"); //meshes[1] CreateSphere(10, 10); D3DX10_IMAGE_LOAD_INFO loadSMInfo; loadSMInfo.MiscFlags = D3D10_RESOURCE_MISC_TEXTURECUBE; ID3D10Texture2D* SMTexture = 0; hr = D3DX10CreateTextureFromFile(d3dDevice, L"skymap.dds", &loadSMInfo, 0, (ID3D10Resource**)&SMTexture, 0); if(FAILED(hr)) { MessageBox(0, L"Load cube texture - Failed", L"Error", MB_OK); return false; } D3D10_TEXTURE2D_DESC SMTextureDesc; SMTexture->GetDesc(&SMTextureDesc); D3D10_SHADER_RESOURCE_VIEW_DESC SMViewDesc; SMViewDesc.Format = SMTextureDesc.Format; SMViewDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURECUBE; SMViewDesc.TextureCube.MipLevels = SMTextureDesc.MipLevels; SMViewDesc.TextureCube.MostDetailedMip = 0; hr = d3dDevice->CreateShaderResourceView(SMTexture, &SMViewDesc, &smrv); if(FAILED(hr)) { MessageBox(0, L"Create Cube Texture RV - Failed", L"Error", MB_OK); return false; } SMTexture->Release(); light.dir = D3DXVECTOR3(0.25f, 0.5f, -1.0f); light.ambient = D3DXCOLOR(0.2f, 0.2f, 0.2f, 1.0f); light.diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); hr = D3DX10CreateEffectFromFile( L"vertex.fx", NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, d3dDevice, NULL, NULL, &FX, &compilationErrors, NULL ); if(FAILED(hr)) { MessageBoxA(0, (char*)compilationErrors->GetBufferPointer(), 0, 0); compilationErrors->Release(); return false; } Technique = FX->GetTechniqueByName( "Tech" ); SkyMapTechnique = FX->GetTechniqueByName("SkyMapTech"); fxWVPVar = FX->GetVariableByName("WVP")->AsMatrix(); fxDiffuseMapVar = FX->GetVariableByName("DiffuseMap")->AsShaderResource(); fxLightVar = FX->GetVariableByName("light"); fxSkyMapVar = FX->GetVariableByName("SkyMap")->AsShaderResource(); D3D10_PASS_DESC PassDesc; Technique->GetPassByIndex( 0 )->GetDesc( &PassDesc ); d3dDevice->CreateInputLayout( layout, 3, PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize, &VertexLayout ); d3dDevice->IASetInputLayout( VertexLayout ); d3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); /////////////////////////new///////////////////////////////////////////////// for( int i = 0; i < numSpheres; i++) { spheres[i] = 1; } /////////////////////////new///////////////////////////////////////////////// return true; } bool ReleaseObjects() { if( d3dDevice ) d3dDevice->ClearState(); if( VertexBuffer ) VertexBuffer->Release(); if( IndexBuffer ) IndexBuffer->Release(); if( VertexLayout ) VertexLayout->Release(); if( FX ) FX->Release(); if( RenderTargetView ) RenderTargetView->Release(); if( SwapChain ) SwapChain->Release(); if( d3dDevice ) d3dDevice->Release(); DIKeyboard->Unacquire(); DIMouse->Unacquire(); if( DirectInput ) DirectInput->Release(); for(int i = 0; i < meshCount; i++) if( meshes[i] ) meshes[i]->Release(); return true; } void DrawScene() { //Draw Scene Here D3DXCOLOR bgColor( 0.0f, 0.0f, 0.0f, 1.0f); d3dDevice->ClearRenderTargetView( RenderTargetView, bgColor ); d3dDevice->ClearDepthStencilView(DepthStencilView, D3D10_CLEAR_DEPTH|D3D10_CLEAR_STENCIL, 1.0f, 0); D3DXMatrixPerspectiveFovLH(&Projection, 0.4f*3.14f, ClientWidth/ClientHeight, 1.0f, 1000.0f); fxLightVar->SetRawValue(&light, 0, sizeof(Light)); D3D10_TECHNIQUE_DESC skymaptechDesc; SkyMapTechnique->GetDesc( &skymaptechDesc ); D3D10_TECHNIQUE_DESC techDesc; Technique->GetDesc( &techDesc ); D3DXMatrixScaling( &Scale, 1.0f, 1.0f, 1.0f ); D3DXMatrixTranslation( &Translation, 0.0f, -3.0f, 0.0f ); Transformations = Scale * Translation; WVP = Transformations * View * Projection; fxWVPVar->SetMatrix((float*)&WVP); //draw plane for( UINT p = 0; p < techDesc.Passes; ++p ) { for(UINT subsetID = 0; subsetID < meshSubsets[0]; ++subsetID) { fxDiffuseMapVar->SetResource(TextureResourceViews[subsetID]); Technique->GetPassByIndex( p )->Apply( 0 ); meshes[0]->DrawSubset(subsetID); } } /////////////////////////new///////////////////////////////////////////////// float sphereXPos = -30.0f; float sphereZPos = 30.0f; float sxadd = 0.0f; float szadd = 0.0f; for(int i = 0; i < numSpheres; i++) { sxadd++; if(sxadd == 10) { szadd -= 1.0f; sxadd = 0; } D3DXMatrixScaling( &Scale, 2.0f, 2.0f, 2.0f ); D3DXMatrixTranslation( &Translation, sphereXPos + sxadd*10.0f, 1.0f, sphereZPos + szadd*10.0f ); SphereLocalSpace[i] = Scale * Translation; WVP = SphereLocalSpace[i] * View * Projection; fxWVPVar->SetMatrix((float*)&WVP); //draw sphere if(spheres[i] == 1) { for( UINT p = 0; p < techDesc.Passes; ++p ) { for(UINT subsetID = 0; subsetID < meshSubsets[1]; ++subsetID) { fxDiffuseMapVar->SetResource(TextureResourceViews[subsetID + meshSubsets[0]]); Technique->GetPassByIndex( p )->Apply( 0 ); meshes[1]->DrawSubset(subsetID); } } } } /////////////////////////new///////////////////////////////////////////////// D3DXMatrixScaling( &Scale, 100.0f, 100.0f,100.0f ); D3DXMatrixTranslation( &Translation, Position.x, Position.y, Position.z ); Transformations = Scale * Translation; WVP = Transformations * View * Projection; fxWVPVar->SetMatrix((float*)&WVP); //draw skymap based off loaded mesh sphere fxSkyMapVar->SetResource(smrv); for( UINT p = 0; p < skymaptechDesc.Passes; ++p ) { for(UINT subsetID = 0; subsetID < meshSubsets[1]; ++subsetID) { fxDiffuseMapVar->SetResource(TextureResourceViews[subsetID + meshSubsets[0]]); SkyMapTechnique->GetPassByIndex( p )->Apply( 0 ); meshes[1]->DrawSubset(subsetID); } } SwapChain->Present( 0, 0 ); } int messageloop(){ MSG msg; ZeroMemory(&msg, sizeof(MSG)); while(true) { BOOL PeekMessageL( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg ); if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else{ // run game code DetectInput(); DrawScene(); } } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch( msg ) { case WM_DESTROY: PostQuitMessage(0); return 0; /////////////////////////new///////////////////////////////////////////////// case WM_SIZE: ClientWidth = LOWORD(lParam); ClientHeight = HIWORD(lParam); return 0; /////////////////////////new///////////////////////////////////////////////// } return DefWindowProc(hwnd, msg, wParam, lParam); } vertex.fx struct Light { float3 dir; float4 ambient; float4 diffuse; }; cbuffer cbPerFrame { Light light; }; cbuffer cbPerObject { float4x4 WVP; }; Texture2D DiffuseMap; TextureCube SkyMap; SamplerState TriLinearSample { Filter = MIN_MAG_MIP_LINEAR; }; struct VS_OUTPUT //output structure for vertex shader { float4 Pos : SV_POSITION; float2 texCoord : TEXCOORD; float3 normal : NORMAL; }; struct SKYMAP_VS_OUTPUT //output structure for skymap vertex shader { float4 Pos : SV_POSITION; float3 texCoord : TEXCOORD; }; // Vertex Shader VS_OUTPUT VS(float3 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL) { VS_OUTPUT output = (VS_OUTPUT)0; output.Pos = mul(float4(inPos, 1.0f), WVP); output.normal = mul(normal, WVP); output.texCoord = inTexCoord; return output; //send color and position to pixel shader } SKYMAP_VS_OUTPUT SKYMAP_VS(float3 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL) { SKYMAP_VS_OUTPUT output = (SKYMAP_VS_OUTPUT)0; output.Pos = mul(float4(inPos, 1.0f), WVP).xyww; output.texCoord = inPos; return output; //send color and position to pixel shader } // Pixel Shader float4 PS(VS_OUTPUT input) : SV_Target { input.normal = normalize(input.normal); float4 diffuse = DiffuseMap.Sample( TriLinearSample, input.texCoord ); float3 finalColor; finalColor = diffuse * light.ambient; finalColor += saturate(dot(light.dir, input.normal) * light.diffuse * diffuse); return float4(finalColor, diffuse.a); } float4 SKYMAP_PS(SKYMAP_VS_OUTPUT input) : SV_Target { return SkyMap.Sample(TriLinearSample, input.texCoord); } RasterizerState NoCulling { CullMode = None; }; DepthStencilState LessEqualDSS { DepthFunc = LESS_EQUAL; }; technique10 Tech { pass P0 { SetVertexShader( CompileShader( vs_4_0, VS() ) ); SetPixelShader( CompileShader( ps_4_0, PS() ) ); } } technique10 SkyMapTech { pass P0 { SetVertexShader( CompileShader( vs_4_0, SKYMAP_VS() ) ); SetPixelShader( CompileShader( ps_4_0, SKYMAP_PS() ) ); SetRasterizerState(NoCulling); SetDepthStencilState(LessEqualDSS, 0); } }