Thread: Height Mapping

  1. #1
    Registered User IdioticCreation's Avatar
    Join Date
    Nov 2006
    Location
    Lurking about
    Posts
    229

    Height Mapping

    I'm trying to load a bmp file and use it as a height map, but I'm having a little trouble. I've read a few tutorials on this in various places, but I can't seem to get it.

    I really would like to know what functions are best to read the bitmap data as int values, as they will be used in height mapping. I've seen fread() and the BiniaryReader class (though I have no idea where it's located) used. So if someone could give me a little insight or point me to a nice tutorial that would be awesome.

    Thanks,
    David

  2. #2
    Dr Dipshi++ mike_g's Avatar
    Join Date
    Oct 2006
    Location
    On me hyperplane
    Posts
    1,218
    This is probably not the best, but heres an image loading class I wrote in C++ the other day. It loads the image to RAM, so you dont have the overheads of dealing with surfaces. The downside to it is it only loads 24bit, uncompressed, non paletted BMPs. Theres also some unfinished stuff kicking about you could remove if you can be bothered to. It should work for what you need tho.
    Code:
    #include <cstring>
    #include <cctype>
    #include <fstream>
    
    typedef unsigned char  Uint8;
    typedef unsigned short Uint16;
    typedef unsigned int   Uint32; 
    
    enum ImageExtensions { NO_EXT, EXT_INVALID, BMP, JPG, PNG, TGA };
    enum ImageLoad { LOAD_IMAGE_SUCCESS, LOAD_IMAGE_INVALID, LOAD_IMAGE_UNSUPPORTED_BITDEPTH };
    
    class Image
    {
        public:
            Image(){w=0;h=0;pixels=NULL;}
            ~Image(){delete[] pixels;}
            bool Load(const char*);
            bool Save(const char*);
    		int GetWidth();
    		int GetHeight();
    		Uint32* GetImage();
        private:
            int GetExt(const char*);
            int LoadBMP(const char*);
            int LoadJPG(const char*);
            int LoadPNG(const char*);
            int LoadTGA(const char*);
            int SaveBMP(const char*);
    		
    	int BytesToInt(char*);
    	short BytesToShort(char*);
    	void IntToBytes(char*, Uint32);
    	void ShortToBytes(char*, Uint16);
            int w, h;
            Uint32 *pixels; 
    		
    };
    //------------------------------------------------------------------//
    //********************* PUBLIC FUNCTIONS ***************************//
    //------------------------------------------------------------------//
    
    bool Image::Load(const char* filename)
    {
        switch(GetExt(filename))
        {
            case NO_EXT:
                return false;
            case EXT_INVALID:
                return false;
            case BMP:
                LoadBMP(filename);
                break;
            case JPG:
        
                break;
            case PNG:
    
                break;
            case TGA:
    
                break;
        }
        return true;
    }
    
    bool Image::Save(const char* filename)
    {
    	SaveBMP(filename);
    	return 0;
    }
    int Image::GetWidth()
    {
    	return w;
    }
    int Image::GetHeight()
    {
    	return h;
    }
    Uint32* Image::GetImage()
    {
    	return pixels;
    }
    //------------------------------------------------------------------//
    //********************* PRIVATE FUNCTIONS **************************//
    //------------------------------------------------------------------//
    
    int Image::GetExt(const char* filename)
    {
        //This function gets a file extension and checks if it is valid
        char ext[10]="";
        char *e_ptr = ext;
        char *f_ptr = (char*)filename;
    
    	f_ptr = strrchr((char*)filename, '.');
        if(!f_ptr) return NO_EXT; 
        
        //Copy The extension converting to upper case
        while(*e_ptr++ = toupper(*f_ptr++)); 
    
        //Check against valid extensions    
        if(!strcmp(ext, ".BMP"))  return BMP;
        if(!strcmp(ext, ".JPG"))  return JPG;
        if(!strcmp(ext, ".JPEG")) return JPG;
        if(!strcmp(ext, ".PNG"))  return PNG;
        if(!strcmp(ext, ".TGA"))  return TGA;
        if(!strcmp(ext, ".TARGA"))return TGA; 
        return EXT_INVALID;
    }
    
    int Image::LoadBMP(const char* filename)
    {
    	ifstream in;
    	in.open(filename, ios::binary);
    	if(! in.is_open()) return LOAD_IMAGE_INVALID;
    	
    	char bytes[4];
    
    	in.seekg(18, ios::beg);
    	in.read(bytes, 4);
    	w=BytesToInt(bytes);
    	
    	in.read(bytes, 4);
    	h=BytesToInt(bytes);
    
    	in.seekg(2, ios::cur);
    	in.read(bytes, 2);
    	short bpp=BytesToShort(bytes);
    	if(bpp != 24) return LOAD_IMAGE_UNSUPPORTED_BITDEPTH;
    
    	
    	//Allocate space for image data
    	pixels = new Uint32[w*h];
    	Uint32 *px = pixels;
    
    	in.seekg(54, ios::beg);
    	for(int y=h; y>0; y--)
    	{
    		for(int x=0; x<w; x++)
    		{		
    			in.read(bytes, 3);
    			*px=((Uint8)bytes[2]<<16)+((Uint8)bytes[1]<<8)+(Uint8)bytes[0];
    			px++;
    		}
    	}
    	in.close();
    	return true;
    }
    
    int Image::BytesToInt(char* i)
    {
    	return ((Uint8)i[3]<<24)+((Uint8)i[2]<<16)+((Uint8)i[1]<<8)+(Uint8)i[0];
    }
    short Image::BytesToShort(char* i)
    {
    	return ((Uint8)i[1]<<8)+(Uint8)i[0];
    }
    int Image::SaveBMP(const char* filename)
    {
    	ofstream out;
    	out.open(filename, ios::binary);
    	if(! out.is_open()) return 1;
    	
    	char bytes[4]={'B','M','B', 'M'};
    
    	//-------HEADER--------------//
    	out.write(bytes, 2);				//Write bitmap ID
    
    	IntToBytes(bytes, w*h*3+54);
    	out.write(bytes, 4);				//Uint32 File Size In Bytes
    
    	IntToBytes(bytes, 0);
    	out.write(bytes, 4);				//Uint16 reserved 1 and 2
    
    	IntToBytes(bytes, 44);
    	out.write(bytes, 4);				//Int Offset To Image Data
    
    	//------- FILE INFO --------//
    
    	IntToBytes(bytes, 40);
    	out.write(bytes, 4);				//Uint32 Header Size In Bytes
    	
    	IntToBytes(bytes, (Uint32)w);
    	out.write(bytes, 4);				//Int Image Width
    	IntToBytes(bytes, (Uint32)h);
    	out.write(bytes, 4);				//Int Image Height
    
    	ShortToBytes(bytes, 1);
    	out.write(bytes, 2);				//Unsigned Short Planes = 1
    	ShortToBytes(bytes, 24);
    	out.write(bytes, 2);				//Unsigend Short Bit Depth = 24
    	IntToBytes(bytes, 0);
    	out.write(bytes, 4);				//Unsigned Int Compression = 0 (No compression)
    	IntToBytes(bytes, w*h*3);
    	out.write(bytes, 4);				//Unsigned Int Image Size In Bytes
    	IntToBytes(bytes, 2835);
    	out.write(bytes, 4);
    	out.write(bytes, 4);				//Int X_Res, Y_Rex (pixes per meter)
    	IntToBytes(bytes, 0);
    	out.write(bytes, 4);				//Uint32 number of paletted colours =0
    	out.write(bytes, 4);				//Uint32 Important colours
    
    	//------ IMAGE DATA --------//
    	Uint32 *px = pixels;
    	for(int y=h; y>0; y--)
    	{
    		for(int x=0; x<w; x++)
    		{
    			IntToBytes(bytes, *px);
    			out.write(bytes, 3); 
    			px++;
    		}
    	}
    	out.close();
    	return 0;
    }
    
    void Image::IntToBytes(char* bytes, Uint32 num)
    {
    	bytes[3] = num >> 24;
    	bytes[2] = (num >> 16) & 255;
    	bytes[1] = (num >> 8) & 255;
    	bytes[0] = num & 255;
    }
    void Image::ShortToBytes(char* bytes, Uint16 num)
    {
    	bytes[1] = num >> 8;
    	bytes[0] = num & 255;
    }
    [edit] Actually you could probably get rid of just about everything and just use the load bmp function [/edit]
    Last edited by mike_g; 10-07-2007 at 02:49 PM.

  3. #3
    Registered User IdioticCreation's Avatar
    Join Date
    Nov 2006
    Location
    Lurking about
    Posts
    229
    Awesome!

    It was the bytes to int and bytes to short stuff, that was giving my trouble.

    Thank you very much.

  4. #4
    Dr Dipshi++ mike_g's Avatar
    Join Date
    Oct 2006
    Location
    On me hyperplane
    Posts
    1,218
    Glad you got it working then

  5. #5
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Simple height map structure:

    Code:
    struct ColorHeight
    {
       BYTE red;
       BYTE green;
       BYTE blue;
       BYTE height;
    };
    Scale the height member by some factor during load time and store the scaled values in your actual height array. Using this structure you can store color and height in the same data structure.

    Most paint programs will allow you to open an image onto the alpha channel of a color image. So create your color map and create your height map. Open your height map onto the alpha channel of the color map. Also this type of data lends itself very well to RLE compression. You can save your image as a RAW file. Determine the type of header you need for the file and tell your paint program that size prior to saving the image. The paint program should reserve enough space at the start of the file so that you can write the header data from your code and then save the data from the paint program.


    BMP portion of the code

    Get rid of all those independent read/writes to/from disk. If you are reading/writing individual values why not just read/write the whole data block?

    BMP files store the color data in the reverse order that the hardware expects it. BMPs are normally saved bottom to top and are padded to the nearest 4 byte boundary.
    Last edited by VirtualAce; 10-10-2007 at 11:08 AM.

  6. #6
    Registered User IdioticCreation's Avatar
    Join Date
    Nov 2006
    Location
    Lurking about
    Posts
    229
    Thanks for the tips Bubba. This is my first stab at terrain rendering, and it's gone pretty smooth so far. But while I'm at it have have another question.

    I have it set up so that you can toggle between wire and filled. The only problem was that when it's filled if I just draw all the tri's white, then it just looks like a big white blob.
    I tried adding some gl dissipate lighting, but that didn't do it for me. What is the way to get a realistic depth look?

  7. #7
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    You can use diffuse vertex or per-pixel lighting.

  8. #8
    Crazy Fool Perspective's Avatar
    Join Date
    Jan 2003
    Location
    Canada
    Posts
    2,640
    >What is the way to get a realistic depth look?

    The real way is to do what bubba suggested. As a simple hack solution though, you can just colour the vertices based on their depth (low vertices dark, high ones white). This isn't proper lighting but will at least allow you to see the curviture of your terrain.

    Another alternative is to texture it with a shadow map.

  9. #9
    Registered User IdioticCreation's Avatar
    Join Date
    Nov 2006
    Location
    Lurking about
    Posts
    229
    Hey, nice, I'll try that. It was actually your LOD project that got me into this. It was just so cool I had to try it myself.

  10. #10
    Registered User IdioticCreation's Avatar
    Join Date
    Nov 2006
    Location
    Lurking about
    Posts
    229
    I tried it out, here are some screenies:
    Filled - http://i14.photobucket.com/albums/a3...ledterrain.png
    Wire - http://i14.photobucket.com/albums/a3...ireterrain.png

    Gets a whopping 20 fps when it's filled, and that's on pretty nice hardware. I guess 131072 is too many tri's at once. xD

  11. #11
    Crazy Fool Perspective's Avatar
    Join Date
    Jan 2003
    Location
    Canada
    Posts
    2,640
    Quote Originally Posted by IdioticCreation View Post
    Hey, nice, I'll try that. It was actually your LOD project that got me into this. It was just so cool I had to try it myself.
    I'm glad it could be of use, your screen shots are looking good. The terrain is really dense though, you could probably scale it out more and have a larger area.

  12. #12
    Amazingly beautiful user.
    Join Date
    Jul 2005
    Location
    If you knew I'd have to kill you
    Posts
    254
    Also, if you are drawing using glBegin(GL_TRIANGLES), with glVertex3f, this can hurt performance.
    Using Vertex Arrays will probably help things, as it will prevent you from making 131,072 function calls within a loop.
    A simpler (although less consistent) way to increase performance would be to use a display list. It will only take around 5 lines of code, and could give you a wopping boost (display lists are relatively dependent on the hardware, atleast with NVidia, I've found them to be a great help in this sort of situation).
    Programming Your Mom. http://www.dandongs.com/

  13. #13
    Registered User IdioticCreation's Avatar
    Join Date
    Nov 2006
    Location
    Lurking about
    Posts
    229
    So with a vertex array you just call BEGIN_TRIANGLES once and draw all your triangles at the same time, correct?

  14. #14
    Crazy Fool Perspective's Avatar
    Join Date
    Jan 2003
    Location
    Canada
    Posts
    2,640
    >this can hurt performance.

    this also depends on the hardware and drivers. I implemented my terrain engine with vertex arrays and found it was slower than rendering individual polys. This was with an older 64mb radeon on linux.

  15. #15
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Just step over to the darkside and use Direct3D. The D3DX library is worth it's weight in gold and Direct3D appears to have more standard support for primitives, vertex buffers, index buffers, and the like.

    I see so many posts about display lists and so forth. With Direct3D you get native support for this since it is part of the API.

    Want a hardware vertex buffer?

    HRESULT CreateVertexBuffer(
    UINT Length,
    DWORD Usage,
    DWORD FVF,
    D3DPOOL Pool,
    IDirect3DVertexBuffer9** ppVertexBuffer,
    HANDLE* pSharedHandle
    );
    Want a software or user vertex buffer?

    Create your vertex structure, tell Direct3D about it, create an array and populate it.
    Draw it with:
    HRESULT DrawPrimitiveUP(
    D3DPRIMITIVETYPE PrimitiveType,
    UINT PrimitiveCount,
    CONST void* pVertexStreamZeroData,
    UINT VertexStreamZeroStride
    );
    Need mesh support?
    Use ID3DXMESH.

    Need progressive mesh support?
    Use ID3DXPMESH.

    I could go on and on. Seems like OpenGL to me has very limited support for some of these advanced structures. Plus with Direct3D you get an HLSL compiler, the ID3DXEffect interface which makes using effect files and shaders a snap, and you get the re-assurance that as long as the drivers are up to date - your code will work.

    I know some hate Microsoft and I'm not a fanboy or anything but when it comes to graphics and hardware support and easing the burden of creating game code, Direct3D wins by a mile.

    My intent is not to turn this into a debate but it is to get the OP thinking.
    Last edited by VirtualAce; 10-15-2007 at 01:20 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. A bunch of Linker Errors...
    By Junior89 in forum Windows Programming
    Replies: 4
    Last Post: 01-06-2006, 02:59 PM
  2. struct question
    By caduardo21 in forum Windows Programming
    Replies: 5
    Last Post: 01-31-2005, 04:49 PM
  3. Binary Search Trees Part III
    By Prelude in forum A Brief History of Cprogramming.com
    Replies: 16
    Last Post: 10-02-2004, 03:00 PM
  4. Ok, Structs, I need help I am not familiar with them
    By incognito in forum C++ Programming
    Replies: 7
    Last Post: 06-29-2002, 09:45 PM
  5. Outputting String arrays in windows
    By Xterria in forum Game Programming
    Replies: 11
    Last Post: 11-13-2001, 07:35 PM