Quake 3 Map Importer (.BSP)

I've searched on the forums and it seems like someone wrote a quake 3 map importer; however, I can't find any information about how to get it. 



Does anyone know anything about this?



Thanks

The only one I know of is the one by renanse - can be found in the following thread.

There have been other ones mentioned, but the links are long dead.



http://www.jmonkeyengine.com/jmeforum/index.php?topic=2597.0

Yea, I actually downloaded the code; however, it doesn't work with JME 2.0.  I guess I could try making it work with 2.0, but if someone already has then it would save me the effort.

Ok.  So I went ahead and converted the jme_q3 for jme 2.0.  There still might be some things that need to be tweaked.



I've included the modified source with build.xml for those of you that use ant.  It can be found here.



monky-games



I've also included a level from the game "Tremulous".  It isn't very well lit because they have some scripts that use shaders to do their lighting.  This is also the case for their "sky".

1 Like

Nice!  :slight_smile:

Can you post your BSP importer under the Code Snippets wiki page? Some people have been asking about BSP importing and I think many others would find it useful.

Done :slight_smile:

http://www.jmonkeyengine.com/wiki/doku.php?id=bsp_file_importer

I downloaded and compiled the code without a problem. I built it using a custom maven script instead of the ant script provided because I didn't have the necessary jar files as it expected them. I used lwjgl rc2 and the latest jme2 svn code. The map was loaded but it doesn't look right, is this how you see it? (login to see attached file).



I wasn't able to load other maps, I tried a Bid For Power map pack but couldn't get the textures loaded. I changed the code just a bit to easily try different maps but couldn't load textures providing all the possibilities I considered reasonable.



Link to map pack: http://www.quakeunity.com/file=747



See the other attached image for the files layout.



I tried:



java -jar -Djava.library.path="./lib" quake3-loader-1.0.jar data/mappak/textures/kit_namek/ data/mappak/maps/kitnamek.bsp



"data/mappak/textures/kit_namek/" or "data/mappak/textures/" or "data/mappak/" or "data/" won't work to get the textures.


   static String mapPath = "data/qdata/atcs.bsp";
   static String textPath = "data/qdata/";
   public static void main(String[] args) {
      if(args.length >= 2){
         textPath = args[0];
         mapPath = args[1];
      }
       ...
       ...
       protected void simpleInitGame() {
                ...
      // load quake 3 level
      TextureLoader.getInstance().registerPath(textPath);
      Quake3Loader loader = new Quake3Loader();
      try {
         loader.load(mapPath);
      } catch (IOException e) {
         e.printStackTrace();
      }
      ...


The problem appears to be in the TGA image loader - it doesn't support TGA files with RLE encoding.

I'm looking into that, but for now if you convert the tga texture files to jpg it will work.

That is the answer to both problems right? This map's textures look not so good and the other maps don't load textures at all because of that right? I will try it out then and see how it goes.

Well I have my own modified version of a bsp loader (based on the original). Many quake maps still require some textures from the original pak files (in my version, I tried your maps and think I had to add the original quake textures to the search path for textures.



For now tho - here is a patch to modify TGALoader that should fix some of the missing texture problems - not hugely tested, - just a few compressed 32 / 24 bit TGA images (dont have anything that will save a 16 bit one, so cant test that)





Index: src/com/jme/image/util/TGALoader.java
===================================================================
--- src/com/jme/image/util/TGALoader.java   (revision 4081)
+++ src/com/jme/image/util/TGALoader.java   (working copy)
@@ -127,8 +127,8 @@
         // open a stream to the file
         BufferedInputStream bis = new BufferedInputStream(fis, 8192);
         DataInputStream dis = new DataInputStream(bis);
+        boolean createAlpha=false;
        
-       
         //


Start Reading the TGA header
//
         // length of the image id (1 byte)
         int idLength = dis.readUnsignedByte();
@@ -214,13 +214,13 @@
         if ((pixelDepth == 32) || (exp32)) {
             rawData = new byte[width * height * 4];
             dl = 4;
+            createAlpha=true;
         } else {
             rawData = new byte[width * height * 3];
             dl = 3;
         }
         int rawDataIndex = 0;
 
-       
         if (imageType == TYPE_TRUECOLOR) {
             byte red = 0;
             byte green = 0;
@@ -283,9 +283,145 @@
                     }
                 }
             else throw new JmeException("Unsupported TGA true color depth: "+pixelDepth);
+
+
+        } else if( imageType == TYPE_TRUECOLOR_RLE ){
+            byte red = 0;
+            byte green = 0;
+            byte blue = 0;
+            byte alpha = 0;
            
-           
-        } else if (imageType == TYPE_COLORMAPPED) {
+            // Faster than doing a 16-or-24-or-32 check on each individual pixel,
+            // just make a seperate loop for each.
+            if (pixelDepth == 32) {
+               for (int i = 0; i <= (height - 1); ++i) {
+                    if (!flip)
+                        rawDataIndex = (height - 1 - i) * width * dl;
+
+                    for (int j = 0; j < width; ++j) {
+                       // Get the number of pixels the next chunk covers (either packed or unpacked)
+                       int count = dis.readByte();
+                       if( (count & 0x80) != 0 ){
+                          // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
+                          count &= 0x07f;
+                         j+=count;
+                          blue = dis.readByte();
+                         green = dis.readByte();
+                         red = dis.readByte();
+                         alpha = dis.readByte();
+                         while ( count-- >= 0) {
+                            rawData[rawDataIndex++] = red;
+                            rawData[rawDataIndex++] = green;
+                              rawData[rawDataIndex++] = blue;
+                              rawData[rawDataIndex++] = alpha;
+                          }
+                        } else {
+                           // Its not RLE packed, but the next <count> pixels are raw.
+                         j+=count;
+                         while ( count-- >= 0) {
+                             blue = dis.readByte();
+                            green = dis.readByte();
+                            red = dis.readByte();
+                            alpha = dis.readByte();
+                            rawData[rawDataIndex++] = red;
+                            rawData[rawDataIndex++] = green;
+                            rawData[rawDataIndex++] = blue;
+                            rawData[rawDataIndex++] = alpha;
+                           }
+                       }
+                    }
+                }
+            }
+            else if (pixelDepth == 24) {
+               for (int i = 0; i <= (height - 1); i++) {
+                  if (!flip)
+                     rawDataIndex = (height - 1 - i) * width * dl;
+                  for (int j = 0; j < width; ++j) {
+                       // Get the number of pixels the next chunk covers (either packed or unpacked)
+                       int count = dis.readByte();
+                       if( (count & 0x80) != 0 ){
+                          // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
+                          count &= 0x07f;
+                         j+=count;
+                          blue = dis.readByte();
+                         green = dis.readByte();
+                         red = dis.readByte();
+                         while ( count-- >= 0) {
+                           rawData[rawDataIndex++] = red;
+                           rawData[rawDataIndex++] = green;
+                           rawData[rawDataIndex++] = blue;
+                           if (createAlpha) {
+                              rawData[rawDataIndex++] = (byte) 255;
+                           }
+                          }
+                        } else {
+                           // Its not RLE packed, but the next <count> pixels are raw.
+                         j+=count;
+                         while ( count-- >= 0) {
+                             blue = dis.readByte();
+                            green = dis.readByte();
+                            red = dis.readByte();
+                            rawData[rawDataIndex++] = red;
+                            rawData[rawDataIndex++] = green;
+                            rawData[rawDataIndex++] = blue;
+                           if (createAlpha) {
+                              rawData[rawDataIndex++] = (byte) 255;
+                           }
+                           }
+                       }
+                  }
+               }
+            }
+            else if (pixelDepth == 16) {
+                byte[] data = new byte[2];
+                float scalar = 255f/31f;
+                for (int i = 0; i <= (height - 1); i++) {
+                    if (!flip)
+                        rawDataIndex = (height - 1 - i) * width * dl;
+                    for (int j = 0; j < width; j++) {
+                       // Get the number of pixels the next chunk covers (either packed or unpacked)
+                       int count = dis.readByte();
+                       if( (count & 0x80) != 0 ){
+                          // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
+                          count &= 0x07f;
+                         j+=count;
+                           data[1] = dis.readByte();
+                           data[0] = dis.readByte();
+                           blue = (byte)(int)(getBitsAsByte(data, 1, 5) * scalar);
+                           green = (byte)(int)(getBitsAsByte(data, 6, 5) * scalar);
+                           red = (byte)(int)(getBitsAsByte(data, 11, 5) * scalar);
+                         while ( count-- >= 0) {
+                           rawData[rawDataIndex++] = red;
+                           rawData[rawDataIndex++] = green;
+                           rawData[rawDataIndex++] = blue;
+                           if (createAlpha) {
+                              rawData[rawDataIndex++] = (byte) 255;
+                           }
+                          }
+                       } else {
+                          // Its not RLE packed, but the next <count> pixels are raw.
+                          j+=count;
+                          while ( count-- >= 0) {
+                               data[1] = dis.readByte();
+                               data[0] = dis.readByte();
+                               blue = (byte)(int)(getBitsAsByte(data, 1, 5) * scalar);
+                               green = (byte)(int)(getBitsAsByte(data, 6, 5) * scalar);
+                               red = (byte)(int)(getBitsAsByte(data, 11, 5) * scalar);
+                             rawData[rawDataIndex++] = red;
+                             rawData[rawDataIndex++] = green;
+                             rawData[rawDataIndex++] = blue;
+                             if (createAlpha) {
+                                rawData[rawDataIndex++] = (byte) 255;
+                             }
+                          }
+                       }
+                    }
+                }
+            }
+            else throw new JmeException("Unsupported TGA true color depth: "+pixelDepth);
+
+        }
+        else if (imageType == TYPE_COLORMAPPED) {
             int bytesPerIndex = pixelDepth / 8;
            
             if (bytesPerIndex == 1) {

Replacing tga with jpg didn't work, I tried removing tga files and replace them with jpg equivalents but didn't work, then I renamed all the jpg that had a tga equivalent to look like a tga file while being a jpg, that caused some weird exception also! I will try your fix now.



EDIT: Applied diff patch tested, didn't work, made sure new code was actually being used, tried again, didn't work, deleted qdata and replaced with original qdata provided in tarball just in case, didn't work. Are you saying this map needs those textures "from the original pak files"? Could you provide them or know where to get those? Do you see this map as I see it?



Thanks for your fast replies!

ok, maybe its something I have changed in my own BSP code. I'll try building the one in the wiki tomorrow night (back to work, so not much free time again :frowning: )



The kitroom.bsp map uses the following textures -

These are from Quake3 itself (pak0.pk3 I think, but not sure - I already lots into the same folder…)

00000000 - 00000001 - textures/base_trim/spidertrim

000004a0 - 00000001 - textures/common/caulk

000044b0 - 00010000 - textures/common/clip

00000000 - 00000001 - textures/gothic_trim/wood2

00000000 - 20000001 - textures/base_wall/glass01



These are in the map pak

00000000 - 00000001 - textures/dakamis/dash_small_tiles

00000000 - 00000001 - textures/dakamis/dash_wall2

00000000 - 00000001 - textures/dakamis/dash_gold

00000000 - 00000001 - textures/kit_st/kit-wall_rockyrocky

00000000 - 00000001 - textures/dakamis/dash_window2

00000000 - 00000001 - textures/kit_room/kit_roomwall2

00000000 - 00000001 - textures/dakamis/jar1

00000434 - 00000001 - textures/kit_room/jf-io_sky

00000000 - 00000001 - textures/kit_room/doorkit1

00000000 - 00000001 - textures/dakamis/kit_tsroof1

00000000 - 00000001 - textures/dakamis/stone_n1b

00000000 - 00000001 - textures/dakamis/dash_wall2a

00000000 - 00000001 - textures/dakamis/dash_gold2

00000000 - 00000001 - textures/dakamis/chair_back



Dunno about this one:-

00000000 - 00000001 - noshader



In my code, I have a couple of entries for texture lookup paths (all hard coded - its only messing around code).

   // load quake 3 level
TextureLoader.getInstance().registerPath("D:/games/quake3/unpacked/");
TextureLoader.getInstance().registerPath("D:/development/myprojects/projects_java/JMonkeyTests/QuakeViewer/MAPPACK/");
         
Quake3Loader loader = new Quake3Loader();
loader.load("D:\development\myprojects\projects_java\JMonkeyTests\QuakeViewer\MAPPACK\maps\kitroom.bsp");



A screenshot of the kitroom.bsp map using my code...I tried it without using Q3's textures (only the ones in the your pak file), and it didnt look a lot different. Some textures were missing, but the building was all there.

A proper .bsp loader could be very helpful for my TPS thx for this i'll try the code when i get home

I built the version from the wiki today, and have found the probable cause for the missing textures when using other maps.



In Quake3Converter.convertTextures() comment out the line t = t.substring(t.lastIndexOf("/") + 1);

This should fix the problem when using maps other than the sample one provided (unless you copy all the textures into the folder where the bsp file is).



The screenshot you pretty much what I get from the wiki version. The big white areas are probably animated textures and handled by shaders rather than just being textured, but there seems to be some bugs that I have fixed in my version that improves the look of things rather greatly - will see if I can try to work out what they were.


There were too many changes for a simple code snippet, so I reworked some of them back into the original code and zipped the relevant source. So here is a copy of my "quake3" source folder which has a number of changes that should improve the looks of things - fixed some problems with textures and lightmaps - but still no shader code or lights etc (I might get round to finishing that some day)



Just extract it over the existing source (i.e.  replace the existing trb.jme.quake3 folder)  and errr I think it should just work the same . . .



<< JOC >>





damn. . . no zip files allowed and I dont currently have anywhere to upload files to, so I'll try attaching it as doc and you should rename the attachment to .zip to unpack it

Hell yeah! Now we are talking! Loaded the map and just a few textures are missing mostly textures of lamps I guess and the skybox but it looks like a playable area now!



By the way… how would I move a character around that place? How can I tell the ground height? How to collide with walls? I have no idea of what information is supplied with those maps, so there might be information supplied about where a character can and can't move perhaps?

Google for quake3 collision and you'll get a bunch of hits on how to do collision detection against a q3 map. It involves parsing the file, and tracing a line, sphere or box against the leafy bsp tree.

Well, you are pretty much on your own there, but as tom says there is a lot of info out there from a BSP perspective. . .



As the map is now contained in a JME node, you can use simple ray casting for some stuff ( I did a picker to select polys and dump information about them to help with some of the problems etc… ), but I didn't really know where to go with it next - did post a rather confusing question about it a long time ago, but got no answers (it probably didnt make sense to anyone - heh)



I haven't got much further than fixing some of the problems and trying to implement the missing parts.



It does seem a little wrong having your whole map as one node in the scene graph, and I didn't really get that, but I think the original code was written to show off rendering speed rather than functionality?

JOC, I'm probably going to work on a FPS now with some people, those people not programmers thought, modelers and animators mainly. I want to use bsp format as it would allow them to use existing tools to create the maps and we could for the time being use any map out there.



I will surely get working on improving the bsp loader then. Would you like to have the code hosted on sourceforge or google code or whatever you like (I only ask for SVN) and make it easier to collaborate? oh and choose a licence!



EDIT:

JOC said:

It does seem a little wrong having your whole map as one node in the scene graph, and I didn't really get that, but I think the original code was written to show off rendering speed rather than functionality?


I'm playing around with the code now, I'm trying to separate jME and the loader as much as possible, I would like to have a good BSP library that could be used by any engine given just a few classes actually implementing the scene graph allocation of elements.

I noticed by reading the code and reading "http://graphics.cs.brown.edu/games/quake/quake3.html" that the code does scene graph manipulation! That's absurd! It does Face Culling, and some kind of Frustum Culling or something that figures where the viewer is and worries to much about rendering. That should definitely be done by jME after providing a well structured node tree with all the stuff. I'm going to rewrite most of it as that is not good at all and makes it much harder to work with it.
JOC said:

As the map is now contained in a JME node, you can use simple ray casting for some stuff ( I did a picker to select polys and dump information about them to help with some of the problems etc... ), but I didn't really know where to go with it next - did post a rather confusing question about it a long time ago, but got no answers (it probably didnt make sense to anyone - heh)

I'm slightly confused on this comment under bsp i have to find an alternative means to figure collisions other than that already in jME