|
|
|
|
Importing height data from .bmp files |
My thanks to Richard Sharpe for porting this chapter to C++!
Since this is one of the tutorials in C# I have tried to rewrite for C++, this is a small chapter on importing height maps from a .bmp file instead of .raw files. You can easily edit/create your own .bmp files using MS Paint or other standard software. The reason why Riemer chose the .raw file to start with, is that every byte you read from the file is useful information. A .bmp file includes lots of other information, such as the filesize, the width and height of the image and so on. This makes importing data from .bmp a little more difficult than importing from .raw files, since now we have to cut away all the useless info. I will include a small summary on the structure of .bmp-files. If you're not interested in this, simply copy the code you find at the bottom of this page. In a .bmp-file, the actual pixel-data is preceded by a header. All values are between 0 and 255, but you don't find decimal values in a .bmp file, you find the ASCII representations of the values. As an example, when you open any .bmp with a simple text-editor like notepad, the first 2 letters always are BM. These correspond to the numbers 66 and 77 in the ASCII table. (Try opening a text editor and hold ALT while typing 66 or 77.) The rest of the file probably won't be readable, because in the ASCII table most numbers between 0 and 255 don't correspondent to a letter/number. When we use the ReadByte()method, it returns the ASCII value of the byte read. So, if we would read the first byte of any .bmp-file (always a B), it would return 66. Here is a description of the header : | Byte | Nr | Value | | 1-2 | 66 77 | Always BM | | 3-6 | X X X X | Filesize | | 7-10 | 0 0 0 0 | Always 0 | | 11-14 | X X X X | Offset to pixeldata | | 15-18 | 40 0 0 0 | Always 40 | | 19-22 | X X X X | Image width in pixels | | 23-26 | X X X X | Image height in pixels | | 27-28 | 1 0 | Always 1 | | 29-32 | 24 0 0 0 | Bits per pixel | | 33-36 | 0 0 0 0 | Compression, usually 0 | | 37-53 | 4x(0 0 0 0) | No longer used |
The most important entries include the image width, height and the bytenr where the actual pixel-data can be found. The actual pixeldata can be put immediately after the header, thus at byte 54. This is usually done, for example MS paint puts it there. The pixel data contains 24 bits per pixel, this corresponds to 3 bytes, which each can contain a value between 0 and 255. There is one byte for the Red, the Green and the Blue value for each pixel. So, if we want to use the pixeldata as heightdata, first we have to find the offset to the actual data, and then simply sum the 3 colorvalues to become the gray value of the pixel, which will be our height.
Lets start by changing our constants so they pick up their values from the bitmap files. (I am also declaring them using C++ standard (sorry Riemer))
Replace
#define WIDTH 64 #define HEIGHT 64
with
short unsigned const int offset = set_offset(); short unsigned const int WIDTH = set_width(); short unsigned const int HEIGHT = set_height();
As you can see I have a new constant to hold the offset value. So to set it we are going to use the following function
int set_offset (void) { std::ifstream f_DataFile; short unsigned int offset; short dummy; f_DataFile.open("heightmap.bmp", std::ios::binary); for (int i = 0; i < 10; i++) { dummy = f_DataFile.get(); } offset = f_DataFile.get(); offset += f_DataFile.get()*256; offset += f_DataFile.get()*256*256; offset += f_DataFile.get()*256*256*256; f_DataFile.close(); return offset; }
you will probably want to try to keep your functions and constants together so add this function to the top of the others and insert a prototype above the constant declaration.
int set_offset (void);
Now we need to create the functions to get the height and width of the terrain file.
int set_height (void) { std::ifstream f_DataFile; short unsigned int HEIGHT; short dummy; f_DataFile.open("heightmap.bmp", std::ios::binary); if (f_DataFile.is_open()) { for (int i = 0; i < 22; i++) { dummy = f_DataFile.get(); } HEIGHT = f_DataFile.get(); HEIGHT += f_DataFile.get()*256; HEIGHT += f_DataFile.get()*256*256; HEIGHT += f_DataFile.get()*256*256*256; f_DataFile.close(); } return HEIGHT; } int set_width (void) { std::ifstream f_DataFile; short unsigned int WIDTH; short dummy; f_DataFile.open("heightmap.bmp", std::ios::binary); if (f_DataFile.is_open()) { for (int i = 0; i < 18; i++) { dummy = f_DataFile.get(); } WIDTH = f_DataFile.get(); WIDTH += f_DataFile.get()*256; WIDTH += f_DataFile.get()*256*256; WIDTH += f_DataFile.get()*256*256*256; f_DataFile.close(); } return WIDTH; }
and of course the prototypes at the top of your code:
int set_height (void); int set_width (void);
First in the FillVertices function we need to change the cv_Vertices array to be a pointer to an array. This allows the array to have it's size set at runtime rather than at compilation.
OURCUSTOMVERTEX *cv_Vertices = new OURCUSTOMVERTEX[WIDTH*WIDTH];
Now we need to import the height values. To do this change the FillVertices Function from the line
std::ifstream f_DataFile;
to the line
f_DataFile.close();
To the fiollowing
std::ifstream f_DataFile; short unsigned int dummy; f_DataFile.open("heightmap.bmp", std::ios::binary); if (f_DataFile.is_open()) {
for (int i = 0;i<
(offset);i++) { dummy = f_DataFile.get(); }
for (int x=0;x<
WIDTH;x++) {
for (int y=0; y<
HEIGHT;y++) { int height = f_DataFile.get(); height += f_DataFile.get(); height += f_DataFile.get(); height /= 8; cv_Vertices[y*WIDTH + x].x = -x; cv_Vertices[y*WIDTH + x].y = y; cv_Vertices[y*WIDTH + x].z = height; cv_Vertices[y*WIDTH + x].color = 0xffffffff; } } }else{ MessageBox(han_Window,"HeighData file not found!","FillVertices()",MB_OK); }
f_DataFile.close();
Also just before the return statement you need to delete the pointer to free up the memory
delete [] cv_Vertices;
OK nearly there. Again we need to change the s_Indices variable into a pointer so it can be set at runtime so in FillIndices function declare it as
short *s_Indices = new short[(WIDTH-1)*(HEIGHT-1)*6];
Also you need to delete the pointer just before the return statement
delete [] s_Indices;
This is it! Now download a new example terrain here (http://users.pandora.be/riemer/files/heightmap.bmp) and place it in your ‘Debug’ directory. Because this is twice the size of our first map, we are going to position the camera a bit further from the center. Also notice that the far plane has been adjusted to 300, because otherwise every pixel further away from the camera than 100 would not be displayed:
D3DXVECTOR3 m_EyePos(80, 0, 120); D3DXVECTOR3 m_TargetPos(-20, 0, 0); D3DXVECTOR3 m_UpVector(0, 0, 1); D3DXMatrixPerspectiveFovLH(&m_Projection, D3DX_PI/4, 500/500, 1, 300);
NOTES: I have had a bit of a play with this. For some reason it seems to work fine with pretty much any size square bitmap (of course you will need to reposition the camera) but it crashes with rectangles. I am still learning so I may work out why soon but if you know why please let me know.
I will be trying to convert the next chapter into C++ shortly so watch this space (just need to write it up). Please see the completed code bellow for this tutorial.

Click here to go to the forum on this chapter!
Or click on one of the topics on this chapter to go there: Blank Screen Hi, I've found all the tutorials really useful so...
The complete code for this chapter:
#include<windows.h>
#include<d3d9.h>
#include<d3dx9.h>
#include<dinput.h>
#include<fstream>
int set_offset (void); int set_height (void); int set_width (void);
short unsigned const int offset = set_offset(); short unsigned const int WIDTH = set_width(); short unsigned const int HEIGHT = set_height();
struct OURCUSTOMVERTEX { float x,y,z; DWORD color; };
int int_AppRunning = 1;
float flt_Angle = 0;
int set_offset (void) { std::ifstream f_DataFile; short unsigned int offset; short dummy;
f_DataFile.open("heightmap.bmp", std::ios::binary); for (int i = 0; i < 10; i++) { dummy = f_DataFile.get(); }
offset = f_DataFile.get(); offset += f_DataFile.get()*256; offset += f_DataFile.get()*256*256; offset += f_DataFile.get()*256*256*256;
f_DataFile.close(); return offset; }
int set_height (void) { std::ifstream f_DataFile; short unsigned int HEIGHT; short dummy;
f_DataFile.open("heightmap.bmp", std::ios::binary);
if (f_DataFile.is_open()) { for (int i = 0; i < 22; i++) { dummy = f_DataFile.get(); }
HEIGHT = f_DataFile.get(); HEIGHT += f_DataFile.get()*256; HEIGHT += f_DataFile.get()*256*256; HEIGHT += f_DataFile.get()*256*256*256;
f_DataFile.close(); } return HEIGHT; }
int set_width (void) { std::ifstream f_DataFile; short unsigned int WIDTH; short dummy;
f_DataFile.open("heightmap.bmp", std::ios::binary);
if (f_DataFile.is_open()) {
for (int i = 0; i < 18; i++) { dummy = f_DataFile.get(); }
WIDTH = f_DataFile.get(); WIDTH += f_DataFile.get()*256; WIDTH += f_DataFile.get()*256*256; WIDTH += f_DataFile.get()*256*256*256;
f_DataFile.close(); } return WIDTH; }
LRESULT CALLBACK OurWindowProcedure(HWND han_Wind,UINT uint_Message,WPARAM parameter1,LPARAM parameter2) { return DefWindowProc(han_Wind,uint_Message,parameter1,parameter2); }
HWND NewWindow(LPCTSTR str_Title,int int_XPos, int int_YPos, int int_Width, int int_Height) { WNDCLASSEX wnd_Structure;
wnd_Structure.cbSize = sizeof(WNDCLASSEX); wnd_Structure.style = CS_HREDRAW | CS_VREDRAW; wnd_Structure.lpfnWndProc = OurWindowProcedure; wnd_Structure.cbClsExtra = 0; wnd_Structure.cbWndExtra = 0; wnd_Structure.hInstance = GetModuleHandle(NULL); wnd_Structure.hIcon = NULL; wnd_Structure.hCursor = NULL; wnd_Structure.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); wnd_Structure.lpszMenuName = NULL; wnd_Structure.lpszClassName = "WindowClassName"; wnd_Structure.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
RegisterClassEx(&wnd_Structure);
return CreateWindowEx(WS_EX_CONTROLPARENT, "WindowClassName", str_Title, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE, int_XPos, int_YPos, int_Width, int_Height, NULL, NULL, GetModuleHandle(NULL), NULL); }
LPDIRECT3DDEVICE9 InitializeDevice(HWND han_WindowToBindTo) { LPDIRECT3D9 p_dx_Object; LPDIRECT3DDEVICE9 p_dx_Device;
p_dx_Object = Direct3DCreate9(D3D_SDK_VERSION); if (p_dx_Object == NULL) { MessageBox(han_WindowToBindTo,"DirectX Runtime library not installed!","InitializeDevice()",MB_OK); }
D3DPRESENT_PARAMETERS dx_PresParams; ZeroMemory( &dx_PresParams, sizeof(dx_PresParams) ); dx_PresParams.Windowed = TRUE; dx_PresParams.SwapEffect = D3DSWAPEFFECT_DISCARD; dx_PresParams.BackBufferFormat = D3DFMT_UNKNOWN;
if (FAILED(p_dx_Object->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, han_WindowToBindTo, D3DCREATE_HARDWARE_VERTEXPROCESSING, &dx_PresParams, &p_dx_Device))) { if (FAILED(p_dx_Object->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, han_WindowToBindTo, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &dx_PresParams, &p_dx_Device))) { MessageBox(han_WindowToBindTo,"Failed to create even the reference device!","InitializeDevice()",MB_OK); } }
p_dx_Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); p_dx_Device->SetRenderState(D3DRS_LIGHTING,false); p_dx_Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
return p_dx_Device; }
void DrawScene(LPDIRECT3DDEVICE9 p_dx_Device, LPDIRECT3DVERTEXBUFFER9 p_dx_VertexBuffer, LPDIRECT3DINDEXBUFFER9 p_dx_IndexBuffer) { p_dx_Device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0); p_dx_Device->BeginScene();
p_dx_Device->SetStreamSource( 0, p_dx_VertexBuffer, 0, sizeof(OURCUSTOMVERTEX) ); p_dx_Device->SetFVF(D3DFVF_XYZ|D3DFVF_DIFFUSE); p_dx_Device->SetIndices(p_dx_IndexBuffer);
D3DXMATRIX m_Rotation; D3DXMatrixRotationZ(&m_Rotation, flt_Angle); D3DXMATRIX m_Translation; D3DXMatrixTranslation(&m_Translation,32,-32,0);
D3DXMATRIX m_World; D3DXMatrixMultiply(&m_World, &m_Translation, &m_Rotation); p_dx_Device->SetTransform(D3DTS_WORLD, &m_World);
p_dx_Device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,WIDTH*HEIGHT,0,(WIDTH-1)*(HEIGHT-1)*2);
p_dx_Device->EndScene(); p_dx_Device->Present(NULL, NULL, NULL, NULL); }
LPDIRECT3DVERTEXBUFFER9 FillVertices(HWND han_Window, LPDIRECT3DDEVICE9 p_dx_Device) {
LPDIRECT3DVERTEXBUFFER9 p_dx_VertexBuffer; OURCUSTOMVERTEX *cv_Vertices = new OURCUSTOMVERTEX[WIDTH*WIDTH];
std::ifstream f_DataFile; short unsigned int dummy; f_DataFile.open("heightmap.bmp", std::ios::binary); if (f_DataFile.is_open()) {
for (int i = 0;i<
(offset);i++) { dummy = f_DataFile.get(); }
for (int x=0;x<
WIDTH;x++) {
for (int y=0; y<
HEIGHT;y++) { int height = f_DataFile.get(); height += f_DataFile.get(); height += f_DataFile.get(); height /= 8; cv_Vertices[y*WIDTH + x].x = -x; cv_Vertices[y*WIDTH + x].y = y; cv_Vertices[y*WIDTH + x].z = height; cv_Vertices[y*WIDTH + x].color = 0xffffffff; } } }else{ MessageBox(han_Window,"HeighData file not found!","FillVertices()",MB_OK); }
f_DataFile.close();
if (FAILED(p_dx_Device->CreateVertexBuffer(WIDTH*HEIGHT*sizeof(OURCUSTOMVERTEX), 0, D3DFVF_XYZ|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &p_dx_VertexBuffer, NULL))) { MessageBox(han_Window,"Error while creating VertexBuffer","FillVertices()",MB_OK); }
VOID* p_Vertices; if (FAILED(p_dx_VertexBuffer->Lock(0, WIDTH*HEIGHT*sizeof(OURCUSTOMVERTEX), (void**)&p_Vertices, 0))) { MessageBox(han_Window,"Error trying to lock","FillVertices()",MB_OK); }else{ memcpy(p_Vertices, cv_Vertices, WIDTH*HEIGHT*sizeof(OURCUSTOMVERTEX)); p_dx_VertexBuffer->Unlock(); } delete [] cv_Vertices; return p_dx_VertexBuffer; }
LPDIRECT3DINDEXBUFFER9 FillIndices(HWND han_Window, LPDIRECT3DDEVICE9 p_dx_Device) { LPDIRECT3DINDEXBUFFER9 p_dx_IndexBuffer; short *s_Indices = new short[(WIDTH-1)*(HEIGHT-1)*6];
for (int x=0;x<
WIDTH-1;x++) {
for (int y=0; y<
HEIGHT-1;y++) { s_Indices[(x+y*(WIDTH-1))*6+2] = x+y*WIDTH; s_Indices[(x+y*(WIDTH-1))*6+1] = (x+1)+y*WIDTH; s_Indices[(x+y*(WIDTH-1))*6] = (x+1)+(y+1)*WIDTH;
s_Indices[(x+y*(WIDTH-1))*6+3] = (x+1)+(y+1)*WIDTH; s_Indices[(x+y*(WIDTH-1))*6+4] = x+y*WIDTH; s_Indices[(x+y*(WIDTH-1))*6+5] = x+(y+1)*WIDTH; } }
if (FAILED(p_dx_Device->CreateIndexBuffer((WIDTH-1)*(HEIGHT-1)*6*sizeof(short),D3DUSAGE_WRITEONLY,D3DFMT_INDEX16,D3DPOOL_MANAGED,&p_dx_IndexBuffer,NULL))) { MessageBox(han_Window,"Error while creating IndexBuffer","FillIndices()",MB_OK); }
VOID* p_Indices; if (FAILED(p_dx_IndexBuffer->Lock(0, (WIDTH-1)*(HEIGHT-1)*6*sizeof(short), (void**)&p_Indices, 0))) { MessageBox(han_Window,"Error trying to lock","FillIndices()",MB_OK); }else{ memcpy(p_Indices, s_Indices, (WIDTH-1)*(HEIGHT-1)*6*sizeof(short)); p_dx_IndexBuffer->Unlock(); } delete [] s_Indices; return p_dx_IndexBuffer; }
void SetUpCamera(LPDIRECT3DDEVICE9 p_dx_Device) { D3DXVECTOR3 m_EyePos(80, 0, 120); D3DXVECTOR3 m_TargetPos(-20, 0, 0); D3DXVECTOR3 m_UpVector(0, 0, 1); D3DXMATRIXA16 m_View; D3DXMatrixLookAtLH(&m_View, &m_EyePos, &m_TargetPos, &m_UpVector); p_dx_Device->SetTransform(D3DTS_VIEW, &m_View);
D3DXMATRIX m_Projection; D3DXMatrixPerspectiveFovLH(&m_Projection, D3DX_PI/4, 500/500, 1, 250); p_dx_Device->SetTransform(D3DTS_PROJECTION, &m_Projection); }
LPDIRECTINPUTDEVICE8 InitializeKeyboard(HWND han_Window) { LPDIRECTINPUT8 p_dx_KeybObject; LPDIRECTINPUTDEVICE8 p_dx_KeybDevice;
DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&p_dx_KeybObject, NULL); p_dx_KeybObject->CreateDevice(GUID_SysKeyboard, &p_dx_KeybDevice, NULL);
p_dx_KeybDevice->SetDataFormat(&c_dfDIKeyboard); p_dx_KeybDevice->SetCooperativeLevel(han_Window, DISCL_FOREGROUND|DISCL_NONEXCLUSIVE); p_dx_KeybDevice->Acquire();
return p_dx_KeybDevice; }
void ReadKeyboard(LPDIRECTINPUTDEVICE8 p_Keyb) { char chr_KeybState[256]; p_Keyb->GetDeviceState(sizeof(chr_KeybState),(LPVOID)&chr_KeybState);
if (chr_KeybState[DIK_ESCAPE]/128) { int_AppRunning = 0; }
if (chr_KeybState[DIK_DELETE]/128) { flt_Angle += (float)0.03; }
if (chr_KeybState[DIK_NEXT]/128) { flt_Angle -= (float)0.03; } }
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPreviousInstance,LPSTR lpcmdline,int nCmdShow) { MSG msg_Message;
HWND han_Window = NewWindow("DirectX C++ Tutorial",100,100,500,500); LPDIRECT3DDEVICE9 p_Device = InitializeDevice(han_Window); LPDIRECT3DVERTEXBUFFER9 p_dx_VB = FillVertices(han_Window, p_Device); LPDIRECT3DINDEXBUFFER9 p_dx_IB = FillIndices(han_Window, p_Device); SetUpCamera(p_Device);
LPDIRECTINPUTDEVICE8 p_KeybDevice = InitializeKeyboard(han_Window);
while(int_AppRunning) { if(PeekMessage(&msg_Message,han_Window,0,0,PM_REMOVE)) { DispatchMessage(&msg_Message); }
ReadKeyboard(p_KeybDevice);
DrawScene(p_Device, p_dx_VB, p_dx_IB); }
p_KeybDevice->Release();
p_dx_VB->Release(); p_dx_IB->Release(); p_Device->Release(); DestroyWindow(han_Window);
return 0; }
- Website design & XNA + DirectX code : Riemer Grootjans - ©2003 - 2008 Riemer Grootjans
|
|
|
|
|