- void GLMegaTerrain::LoadHIMData(const char *path, int sizeX, int sizeY, int Scale)
- {
- HIM_DATA = new float[65*65*sizeX*(sizeY)];
- //array for all heights of terrain
- char *temp_path = new char[255];
- strcpy(temp_path, path);
- strncat(temp_path, "30_30.him", 9);
- //make whole path to him file
- int ofs = 0;
- int l = 0;
- while(path[l] != NULL)
- {
- if(path[l] == '/')
- ofs = l;
- l++;
- }
- PATH_OFFSET = 1; //this value needs to know from what file begins map 30_30 or 31_31
- int temp; //Ofcose there are maps that begins from 32 ... so it needs to upgrade
- int offset = 49;
- FILE *t;
- bool close_flag = true;
- temp_path[ofs+2] = (char)(offset);
- temp_path[ofs+5] = (char)(offset-1);
- if((t=fopen(temp_path,"rb"))==NULL)
- {
- PATH_OFFSET = 0;
- //If 30_30.him existing PATH_OFFSET = 0 else PATH_OFFSET = 1
- close_flag = false;
- }
- if(close_flag)
- fclose(t);
- int index = 0;
- for(int i=0;i<sizeX;i++)
- for(int j=0;j<sizeY;j++)
- {
- temp_path[ofs+2] = (char)(i+offset);
- temp_path[ofs+5] = (char)(j+offset-PATH_OFFSET);
- FILE *f;
- if((f=fopen(temp_path,"rb"))==NULL)
- {
- cout<<"\nCannot open file: "<<temp_path<<endl;
- continue;
- }
- int width, height, unk;
- float basic_float;
- fread(&width,sizeof(width),1,f);
- fread(&height,sizeof(height),1,f);
- fread(&unk,sizeof(unk),1,f);
- fread(&basic_float,sizeof(basic_float),1,f);
- int HIM_OFFSET = (index)*65*65;
- for(int i=0;i<width*height;i++)
- {
- fread(&HIM_DATA[i+HIM_OFFSET],sizeof(HIM_DATA[i+HIM_OFFSET]),1,f);
- HIM_DATA[i + HIM_OFFSET]/=(200/ Scale);
- }
- index++;
- // quadtrees here
- fclose(f);
- }
- }
Fist off all.
In order to do something like this you need to know some coding language with OOP bases and off course you need to know something about OpenGL (here are some good tutorials NEHE) and GLSL.
Post your comments if you don't understand something and Ill fix an article. If you are ready lets begin
So how is this working? To load Rose map (only terrain) you need to know how to open *.HIM, *.TIL and *.ZON files. More information about them can be found here: http://rose.br19.com/
*.HIM
Those are just an array of height values of each terrain point and quadtree data (I don't work with it, but it shouldn't be so difficult). To understand
look at this pictures for an example of the heights array:
I am using VBO to render my terrain, this means that all points data are sent in one big array sent in one shot into the video card. It will ensure good performance. If you have any questions about VBO, look here: http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=45
There is nothing too hard about this as you can see. So to load whole map we need to load all ".HIM" data in one array.
Something like this:
Maybe there are some errors in code, but you got now the main idea. Just simple working with strings. After this all ".HIM" data will be saved in an array like this:
- {30_30,30_31,30_32,31_30,31_31,31_32...}
Then.
If you try NEHE ex.45 with VBO you can understand how it's working so let's go next.
The next step is very important.
We need to split whole map into block of 4x4 points it's the way it's connected to each other, why? This is need in texturing part of map loading. Look at this screenshot to understand what I am talking about:
Dark lines are borders of each 4x4 piece. So you need to place them in the right position and add the needed heights. Like this:
- void GLMegaTerrain::Load(char *path, int sX, int sY, int DivSize)
- {
- strcpy(MapPath, path);
- DIV_SIZE = DivSize;
- LoadHIMData(path, sX, sY, 3);//last param is terrain block scale
- SIZEX = sX*(64/DivSize);
- SIZEY = sY*(64/DivSize);
- ...
- data = new GLTerrain[SIZEX*SIZEY]; // data is array of terrains 4x4 that make whole map
- int offset = 49; //offset to translate int into char
- int index = 0;
- for(int i=0;i<SIZEX;i++) //
- {
- for(int j=0;j<SIZEY;j++)
- {
- char *TIL_PATH = new char[255];
- strcpy(TIL_PATH, path);
- strncat(TIL_PATH, "31_31.til", 9);
- char *Shadow_path = new char[255];
- strcpy(Shadow_path, path);
- strncat(Shadow_path, "31_30/31_30_PLANELIGHTINGMAP.dds", 32);
- //Incrase names in path
- TIL_PATH[1+p_index] = (char)(int(i/(64/DivSize))+offset);
- TIL_PATH[4+p_index] = (char)(int(j/(64/DivSize))+offset-PATH_OFFSET);
- Shadow_path[1+p_index] = (char)(int(i/(64/DivSize))+offset);
- Shadow_path[7+p_index] = (char)(int(i/(64/DivSize))+offset);
- Shadow_path[4+p_index] = (char)(int(j/(64/DivSize))+offset-PATH_OFFSET);
- Shadow_path[10+p_index] = (char)(int(j/(64/DivSize))+offset-PATH_OFFSET);
- LoadTextures(TIL_PATH, i, j, index); //
- data[index].SHADOW_TEXTURE = TManager.LoadDDSTexture(Shadow_path);
- int gr = DivSize;
- data[index].Create(j*(gr*3),i*(gr*3),gr,gr,3);
- //j*(gr*3),i*(gr*3) is x and y position of piace, gr is size
- data[index].SetHeights(RetHeights(i, j), i, j, DivSize);
- data[index].BuildVBOs();
- index++;
- }
- }
- }
What is data[index].SetHeights(RetHeights(i, j), i, j, DivSize); doing? It actually sets heights of 4x4 pieces.
-RetHeights(int i, int j) returns a 64x64 array of heights that is in the ".HIM", in the part for this 4x4 piace.
Very simple function:
- float* GLMegaTerrain::RetHeights(int i, int j)
- {
- int s = 65*65;
- float *z = new float[s];
- int l =( int(i/(64/DIV_SIZE))* s *(SIZEY/(64/DIV_SIZE)) ) + ( ( int(j/(64/DIV_SIZE)) * s ));
- for(int i=0;i<s;i++)
- z[i] = HIM_DATA[i+l];
- return z;
- delete z;
- }
SetHeights:
- void GLTerrain::SetHeights(float *z, const int offsetX, const int offsetY, int size)
- {
- int count = 0;
- int tx = offsetX;
- if(tx > (64/size)-1 ) tx = tx % (64/size);
- int ty = offsetY;
- if(ty > (64/size)-1 ) ty = ty % (64/size);
- for(int i=0;i<sizeX+1;i++)
- for(int j=1;j<sizeY+1;j++)
- AddPoint(i,j, z[(i+ty*size)*65+(j+tx*size)-1]);
- //set height of [i, j] point in current 4x4 piece
- }
AddPoint(i,j, z[(i+ty*size)*65+(j+tx*size)-1]) adds a height to i,j point of terrain,
[(i+ty*size)*65+(j+tx*size)-1] is just an offset to find needed heights of 4x4 piece.
So we have now the whole terrain with applied heights:
3DDATA/MAPS/JUNON/JG01/
Texturing
To texture terrain I use GLSL shader, it isn't very hard to understand. I also use this code to work with shaders:
*.ZON info
In this file you need to read TEXTURELIST (just array of strings that are the paths of all textures used in this map) and TILELIST info.
*.TIL info
This is an array of 16x16 by 4x4 pieces texture data where tile_id matches number in TILELIST. Then base1+offset1 is number of bottom texture (number maches TEXTURELIST) and base2+offset2 top texture. Orientation is rotation of the texture.
- if(Orientation == 1)
- data[index].TexCoordOrient = Vector2D(1,1);
- else if(Orientation == 2)
- data[index].TexCoordOrient = Vector2D(-1,1);
- else if(Orientation == 3)
- data[index].TexCoordOrient = Vector2D(1,-1);
- else if(Orientation == 4)
- data[index].TexCoordOrient = Vector2D(-1,-1);
It turns like this:
Orient. : 1 2 3 4
So why we split whole terrain in 4x4 blocks? It is for easy texturing, I think that Rose developers did the same. I use my shader on this block and texture it with 3 textures in one time.
Now you have 2 textures of current 4x4 piece and it is time to texture it. But don't forget rotation.
Here are some main steps to run shader:
1. Init shaders
- void GLMegaTerrain::InitShaders()
- {
- if(program.loadShaders("Shaders/Terrain/t1.vsh", "Shaders/Terrain/t1.fsh"))
- cout<<"---Shaders loaded!---"<<endl;
- else cout<<"---ERROR in loading shaders---"<<endl;
- program.bind ();
- program.setTexture( "Texture0", 0);
- program.setTexture( "Texture1", 1);
- program.setTexture( "Shadow", 2);
- program.unbind();
- }
You must call InitShaders() after you create GLMegaTerrain object (or whatever you named you terrain class).
2. Then at the render state bind/unbind program and set new textures to get performance. And also set vector of rotation and vector of shadow texture coords:
- GLMegaTerrain::Render()
- {
- program.bind();
- for(int i=0;i<SIZEX*SIZEY;i++)
- {
- program.setUniformVector("TexC", data[i].TexCoordOrient);
- program.setUniformVector("ShaTC", data[i].ShadowOffset);
- data[i].Render();
- }
- program.unbind();
- }
And shader itself. Fragment program:
- uniform sampler2D Texture0;
- uniform sampler2D Texture1;
- uniform sampler2D Shadow;
- uniform vec2 TexC;
- uniform vec2 ShaTC;
- varying vec2 texCoord;
- varying vec3 l;
- varying vec3 v;
- varying vec3 n;
- void main(void)
- {
- vec2 v = vec2(texCoord.x*TexC.x, texCoord.y*TexC.y);
- vec2 shv = vec2(texCoord);
- shv /= 16.0;
- shv += ShaTC;
- vec4 a = texture2D( Texture0, v ) ;
- vec4 b = texture2D( Texture1, v ) ;
- vec4 shadow = texture2D( Shadow, shv ) ;
- const vec4 diffColor = vec4 ( 0.6, 0.6, 0.6, 0.5 );
- vec3 n2 = normalize ( n );
- vec3 l2 = normalize ( l );
- vec4 diff = diffColor * max ( dot ( n2, l2 ), 0.0 );
- gl_FragColor = (dot ( n2, l2 ) *3.0 )* mix(a, b, b.a) *shadow;;
- }
Vertex program:
- varying vec2 texCoord;
- varying vec3 l;
- varying vec3 n;
- varying vec3 v;
- uniform vec4 lightPos;
- uniform vec4 eyePos;
- void main(void)
- {
- texCoord = gl_MultiTexCoord0.xy;
- vec3 p = vec3 ( gl_ModelViewMatrix * gl_Vertex ); // transformed point to world space
- l = normalize ( vec3 ( lightPos ) - p ); // vector to light source
- v = normalize ( vec3 ( eyePos ) - p ); // vector to the eye
- n = normalize ( gl_NormalMatrix * gl_Normal ); // transformed n
- gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
- }
Optimization:
To speed up your fps you can use Quad-tree optimization as used by Rose online client. There are a lot of information about this metod in web just use google
Seems like this is all. Yeah this is huge article and it's hard to understand all about loading rose map from it.
You need to know many things to do that, but I hope it will help someone If you dont understand somehing ask and I'll try to help you.
Thaks to Imame for translation help.
ADDED:
So here are sources, I wrote them in Visual Studio 2003 so if you are using another version you can get some small errors.
How to use
- Compile
- Copy Rose Etior.exe in extracted ROSE data
- Copy shaders from my project in the same catalog
- run
mouse+right button - camera move;
shift+mouse - up, down;