Q3 maps

I know it is not very impressive yet, but have a look at my first ever loaded q3 map in jme:







It lacks textures, patches, I don’t care for the BSP tree information and I ignore area information, but somehow I still liked it :slight_smile:

wooooooooha! :smiley: great work :slight_smile:

Good work! :smiley:



keep it up

Hey it looks great !

Keep up the good work :smiley:



Chman

Very nice! How much more work will it be to texture it? That would make a big difference.

Is it converted to .jme first or did you load directly?

Texturing this special one will be difficult since it uses textures built into q3 itself - which I don’t own :slight_smile: It’s a gtk radiant (map editor) example. But I know how to do the textures generally - this is the next on my list.



It is read in directly, Badmi, I wanted to see whether I understand the map format correctly. Here is my activity list:


  1. Implement the bezier patches from the map data (they seem incompatible to jME patches)
  2. Include textures
  3. Build a more sensible tree, using the node and leaf information from the map
  4. Use the lightmaps
  5. Find the portal information in the map and use my portal system - this is the major test for it!
  6. Find a way to convert this into .jme format



    Open issues: I need some sort of discussion with someone more experienced than I am about the use of bsp trees. Currently I do not see where we would need them other than finding out, which region the camera currently is in.



    It think I will be writing a design document and post it here, once the mist has cleared a bit.



    But we have a not-so-small problem. It’s a bit lengthy to explain but here it goes.



    I found a class in an article at javaworld which helps you do a “sizeof” for java classes. You know, there is no “sizeof” in java, but sometimes it might come handy. Anyway, here it is:


import com.jme.scene.TriMesh;
import com.jme.system.DisplaySystem;

public class Sizeof {
    public static void main(String[] args) throws Exception {
        // Warm up all classes/methods we will use
        runGC();
        usedMemory();

        // Array to keep strong references to allocated objects
        final int count = 100000;
        Object[] objects = new Object[count];

        long heap1 = 0;
        DisplaySystem display = DisplaySystem.getDisplaySystem("LWJGL");
        display.createWindow(800, 600, 32, 60, false);
        // Allocate count+1 objects, discard the first one
        for (int i = -1; i < count; ++i) {
            Object object = null;

            // ### Instantiate your data here and assign it to object
            object = new TriMesh();
            // ###

            if (i >= 0)
                objects[i] = object;
            else {
                object = null; // Discard the warm up object
                runGC();
                heap1 = usedMemory(); // Take a before heap snapshot
            }
        }

        runGC();
        long heap2 = usedMemory(); // Take an after heap snapshot:

        final int size = Math.round(((float)(heap2 - heap1)) / count);
        System.out.println("'before' heap: " + heap1 + ", 'after' heap: " + heap2);
        System.out.println("heap delta: " + (heap2 - heap1) + ", {" + objects[0].getClass()
                + "} size = " + size + " bytes");

        for (int i = 0; i < count; ++i)
            objects[i] = null;
        objects = null;
        display.close();
        System.exit(0);
    }

    private static void runGC() throws Exception {
        // It helps to call Runtime.gc()
        // using several method calls:
        for (int r = 0; r < 4; ++r)
            _runGC();
    }

    private static void _runGC() throws Exception {
        long usedMem1 = usedMemory(), usedMem2 = Long.MAX_VALUE;
        for (int i = 0; (usedMem1 < usedMem2) && (i < 500); ++i) {
            s_runtime.runFinalization();
            s_runtime.gc();
            Thread.yield();

            usedMem2 = usedMem1;
            usedMem1 = usedMemory();
        }
    }

    private static long usedMemory() {
        return s_runtime.totalMemory() - s_runtime.freeMemory();
    }

    private static final Runtime s_runtime = Runtime.getRuntime();

}


If you run this, you see that a (empty) TriMesh allocates 319 bytes. No big deal, you say. Well, exchange the 'new TriMesh()' by 'new TriMesh("a")'...
First of all, you need to increase the heap size or your VM will crash. Then you'll find, that after an awful lot of time the program finishes and tells you, that in reality an empty TriMesh allocates 711 bytes - without a single geometry information!

Here's the problem: A typical mid-size q3 map can have an interesting number of vertices and faces. I found a map "quatrix", which is a q3 version of the lobby scene from the matrix movie, which has 111 textures, 46279 vertices, 9278 faces, 43340 triangles and 34 lightmaps.
Due to the structure of the map data, I have to create a TriMesh for each face, which then can contain a number of coplanar triangles which share the same texture.

I have to set the heap size for the vm to > 512MB in order to create the trimeshes for this map, at the end the VM will have allocated 1.2GB of memory! It takes < 3 seconds to parse the file, but > 2 minutes to create all the TriMeshes and the garbage collector is constantly working. Note that I share the vertex, normal and index information already, only references are handled, no unneccessary object creations!

Is there _any_ way to make TriMeshes (in fact I think it is Geometry) more lightweight?

batman.ac, I won’t be able to answer your question, but I find your post very interesting. The point about memory allocation is a good one. I think we should test all Jme files that store informations to see how they manage memory… It could be a way to make great optimisations (on memory allocation) for the core engine.

The more important classes would be geometry ones, as you said.



Chman

Can you explain why you need so many TriMeshs?



If the problem is adding or disabling triangles then the only think that I can suggest is that you create a modified version of composite mesh that allows you to disable ranges. I done right there would be a very small decree in speed when used the way it is used now. Mojo would you mined this addition? If you want I can do it myself and put it in code review.

I currently create a TriMesh for every face in the map. Each face may contain more than one triangle though. Think of a quad. It would be two triangles in one TriMesh. So for “quatrix” I create 9278 TriMeshes.

The reason why I currently have to do this is that each face can have it’s own texture (and lightmap). So if you have 100 faces in a .BSP file, you can have 100 different textures assigned to them. I don’t know how to assign an arbitrary number of textures to a single TriMesh yet and access the correct textures for the vertices. AFAIK, a TextureState can only hold multiple textures in terms of multitexturing.

it’s true that only one texturestate really affects a given trimesh at a given time. You could use an image that contains multiple subimages though and adjust the texture coordinates to use the exact subimages you require in the right places.

How do you plan on compiling the maps? The tools for quake have restrictions so you can not use them in a commercial game.

On the whole memory issue, let’s break down a Trimesh class into it’s actual components and see where the memory is used. The main difference I see between no arg and the name arg methods of construction is a handful of array and object creations. How big is a typical Vector3f or Quaternion? Or how big (memory wise) is an array that has a length of say 8 but all indexes are null?

Remember there are also buffers.

true, but no nio buffers are directly created in either constructor mentioned or by default. An array of floatbuffers IS created, but not filled with actual buffer objects.

We should consider the remote possibility that java has a pore buffers implementation in witch empty buffers take up a large amount of memory.

what empty buffers? Or do you mean arrays []?

My mistake. There are no empty buffers, But what I said can also apply to arrays [].

Open issues: I need some sort of discussion with someone more experienced than I am about the use of bsp trees. Currently I do not see where we would need them other than finding out, which region the camera currently is in.


Yeap. That's exactly why you want them. While building a good BSP tree can be slow, once built it can be much faster than BoundingVolumes for culling. For example, everything "behind" the camera can be culled at once.

Now that classes are finally over, I've been going over Elberly's BSP code. To do portals like he does, I'll need to make BPS nodes and have portals extend those. I'm pretty sure I can get working BPS nodes and BPS trees by the end of the month. If you've gotten anywhere with them, let me know in PM. I havn't actually coding anything as of this moment but was hoping to start soon.

Cep, I have ported most of the eberly code, but there are some issues with this. Some methods (eg. in Camera) are note yet implemented. Shall I post in the forum, what I have got so far?