Howto : Drastically improve Q3 Map Quality

A forum to store posts deemed exceptionally wise and useful
Post Reply
TekniX
Posts: 24
Joined: Tue May 01, 2007 12:39 pm

Howto : Drastically improve Q3 Map Quality

Post by TekniX »

Hi,

While working on my last project i used the gtk Radiant map editor to design my levels, what i missed was that cool feature, wich you can for example find in FEAR that there is a lot of textures with bumpmaps on them, improving the visual quality a lot. If you think back to halflife ( or in this case q3 ) we did not see those features in that games. So while working on my maps, i found a way to add this feature into your Q3 maps with Irrlicht. I will explain the basic idea here in a few words, and if there is any interest from the community i can write a detailed explanation of how to realise this.

Well, its quite a while that i didnt work with irrlicht and my ex project, so excuse any undetailed explanations, if someone wants to know more, ill do some proper research and post example code, but i guess you should be able to handle this yourselves if you dont already do it :P .
(Warning ! I never said this is easy, or that it might be the best way to do so, its just the way i used and if theres an easier way i didnt find it ;) )

1. While mapping write down the exact texture name of the textures you want to have a heightmap on later e.g "rock1.jpg"

2. You will have to get yourself a couple of parsing functions in order to parse strings out of a file.

3. You have to create a "levelfile" this levelfile will contain the following information : a) Nr of textures that will receive a heightmap e.g "7" .
b ) The name of the texture you want to have the heightmap for e.g "rock1.jpg" and c ) the name of the heightmap ... (or not if you give it a standard extension like rock1.jpgHT )


Ok now comes the funny part, if you look at the *ITexture documentation you will see that irrlicht supports up to 4 (?) texture layers for one object.
This comes in handy, the idea why you have been creating this levelfile and you have been writing down so many texture names is that when you load your bsp file, you will do the following :

- Add the pk to the system .
- parse your levelfile for the bspfilename ( in my case ) and the names of the textures with heightmaps and store that in a linked list
- Load the BSP Scene node
- Run a loop that goes through all the material indexes of the bsp scene node and compares the active index with the names of the textures that have heightmaps, if theyre the same, youre on an index wich needs a heightmap, everyhting you do then, is get the heightmapname of the linked list and load it into the second texture layer ...

I just happened to find my loop back... here it goes but be warned, it was in development at that time, and although the part interesting for the textures works, i cannot recoment to copy and paste it in any form, anyways youd need the HL list and the parselevelfile to , For your information, this function below, was called after parsing the levelfile weve been talking about, thus the bspfilename, and the list are passed on from there ( just in case u wonder ;) )

Code: Select all

int LoadBSP(char *name,HeightList *hl)
    {
		char names[15];
        strncpy(names,name,15);
        
        g_bspmesh = g_smgr->getMesh(name);
        if ( g_bspmesh == NULL ) 
            {
                printf("\n CRITICAL ERROR ! Could not load MapPack BSP File ! Exitting ! ");
                g_done = true;
                return -1;
            }

	 
	   g_bspnode = g_smgr->addOctTreeSceneNode(g_bspmesh->getMesh(0));
	 
	   if ( g_bspnode == NULL ) 
            {
                printf("\n Error Loading Bsp Scene Node ! ");
                g_done = true;
                return -1;
            }
	
		int matcount=0;
		int i=0;
		char hstring[50];
		char tstring[50];

		matcount = g_bspnode->getMaterialCount();
		g_bspnode->setMaterialType(video::EMT_SOLID);
		while ( i < matcount-1 ) 
			{
				
				texname = g_bspnode->getMaterial(i).Texture1->getName();
				sprintf(tstring,"%s",texname.c_str());
				//NOw we check if this special texture has got a heightmap ...
				if ( hl->check_for_name(tstring) == 1 )
					{
						//yes it does, so we load the pertaining height file
						sprintf(hstring,"%sheight",texname);
						g_bspnode->getMaterial(i).Texture2 = g_driver->getTexture(hstring);
						g_driver->makeNormalMapTexture(g_bspnode->getMaterial(i).Texture2,1.0f);
						g_bspnode->getMaterial(i).MaterialTypeParam = 0.028f; 
						g_bspnode->getMaterial(i).Lighting=false;	
						g_bspnode->getMaterial(i).MaterialType = EMT_PARALLAX_MAP_SOLID;							
}
				g_bspnode->getMaterial(i).Lighting=false;	
				i++;
			}

		g_bspnode->setMaterialFlag(EMF_LIGHTING,false);
		
		g_smgr->setShadowColor(video::SColor(150,0,0,0)); 
	
		
	//Initialise the triangleselector
        g_triangselect = g_smgr->createOctTreeTriangleSelector(g_bspmesh->getMesh(0),g_bspnode,128);
        if ( g_triangselect == 0 ) 
            {
                printf("\n Error Creating TriangleSelector ! ");
                return -1;
            }
        g_bspnode->setTriangleSelector(g_triangselect);
        g_bspnode->setID(-555);
		g_metatriangle->addTriangleSelector(g_triangselect);
        g_triangselect->drop();
	
		g_bspnode->setID(-555);
		//CreateNewtonBSPTree(g_bspmesh);
		return 0;
    }

Code: Select all

if ( hl->check_for_name(tstring) == 1 )

the HEIGHTMAPLIST ( HL ) is passed to the loadbsp function and has the namecheck function, so here we compare the name of the BSP texture stored in the actual index (Layer 1 ( Texture1) ) with the names we parsed out of the levelfile...

Code: Select all

//yes it does, so we load the pertaining height file
						sprintf(hstring,"%sheight",texname);
						g_bspnode->getMaterial(i).Texture2 = g_driver->getTexture(hstring);
						g_driver->makeNormalMapTexture(g_bspnode->getMaterial(i).Texture2,1.0f);
						g_bspnode->getMaterial(i).MaterialTypeParam = 0.028f; 
						g_bspnode->getMaterial(i).Lighting=false;	
						g_bspnode->getMaterial(i).MaterialType = EMT_PARALLAX_MAP_SOLID;							
}
				g_bspnode->getMaterial(i).Lighting=false;	
				i++;
			}

As you can see, i named my heightmaps like this rock1.jpgheight, so i skip having to include another filename in the levefile ( in contradiction to what i stated before ;) ) , well i load that texture here

Code: Select all

 g_bspnode->getMaterial(i).Texture2 = g_driver->getTexture(hstring);
and then its just setting up parameters.. making the normal map etc... In this case, i didnt use EMT_BUMP ( or what its called ) but rather parallax mapping, wich can also improve your levels a lot ...

Ok, if you could follow my thoughts until here.. you definitely get an A grade in following chaotic pieces of code with bad descriptions ;), i think that this method has quite a lot of possibilities, just imagine what you can do if you get yourself a fully blown levelfile "format" with all the infos of your textures, then it will not just be "plain" "boring" textures, this gives you the possibility to add up to four texture layers and control each one of them... of course, if you want to do this, i have to warn you, that the fileformat can get quite complicated... a few tipps :

- Allways use the same nr of datafields e.g "name" "bumpfactor" "xxx"
and if you happen not to need one u just can fill it with an "IF" for IgnoreField if you then find an IF in what you parsed you know that field is not needed .
- the first thing in your level file should be a [NR] eg [9] then you can write yourself a ParseInteger func wich gets that number, and run your parsefile function the NR of times ... ( Or just put an [EOF] when ur finished, and compare your strings to that strcmp(str,"EOF").

.... Well i hope this was usefull for someone here that still uses q3 radiant, if done in the right way, ull get a real boost out of the old technology :)
Of course, the code and everything i stated here, can be improved by another 100% ... the problem, and sadly the thing that finished my last project was the fact, that i could not keep the overview over all the different file formats i added and the order it had to be parsed in... i had several linked lists featuring ( objects, lights, pickups, weapons ) and i had written a 3d world editor, in wich you could set up everything the way u wanted it configure your objects to a certain factor and the let him print out the different files .. lightfile, objectfile, pickupfile .. and i didnt even start with actors... So as you can see, my problem was organisating the big amounts of information needed in order to get a real good map together .. :) Below a little screenshot of the editor in action ;)
Image[/img]
it was entirely done in irrlicht and actually quite a help.. but .. projects start to grow fast, and theres allways a point where i loose overview, and stop it :-( the ump was also my modelling :) its quite nice though ( even had it animated in irrlicht for shooting and playing shooting sound with irrklang..
Greetings
TekniX

and to the irrlicht team : a BIG thx ! This engine is great, you have thought about almost everything ( e.g the multiple tex layers ) :shock: your amazing !
Bob Finnie
Posts: 49
Joined: Tue Jan 23, 2007 12:36 pm
Location: Bedford, UK

Post by Bob Finnie »

Great post and a good cheap/quick way to improve maps!
I just can't believe no-one else has responded!

Have you progressed much with your editor? - It sure would be a great asset to the community if it is still being used/maintained.

Well Done.

Regards,

Bob
Post Reply