TUER: Truly Unusual Experience of Revolution, FPS using JOGL

I have finished the implementation of the algorithm to remove the redundant vertices :

@Override
        protected final boolean performTaskOnCurrentlyVisitedCell(){
            Full3DCell cell=getCurrentlyVisitedCell();
            ArrayList<List<float[]>> wallsVerticesListList=new ArrayList<List<float[]>>();
            wallsVerticesListList.add(cell.getBottomWalls());
            wallsVerticesListList.add(cell.getCeilWalls());
            wallsVerticesListList.add(cell.getFloorWalls());
            wallsVerticesListList.add(cell.getLeftWalls());
            wallsVerticesListList.add(cell.getRightWalls());
            wallsVerticesListList.add(cell.getTopWalls());
            //create the both tables
            LinkedHashMap<Integer,Integer> duplicateToUniqueIndexationTable=new LinkedHashMap<Integer,Integer>();
            LinkedHashMap<VertexData,Integer> vertexDataToUniqueIndexationTable=new LinkedHashMap<VertexData,Integer>();         
            //local variables used in the loop
            int portalVertexIndex,currentPortalVertexIndex,uniqueVertexIndex;
            int uniqueVerticesIndicesCount=0;
            int duplicateVerticesIndicesCount=0;
            Integer knownUniqueVertexIndex;
            Full3DCell neighborCell;
            Map.Entry<LinkedHashMap<Integer,Integer>,LinkedHashMap<VertexData,Integer>> neighborCellEntry;
            LinkedHashMap<VertexData,Integer> neighBorVertexDataToUniqueIndexationTable;
            //for each vertex
            for(List<float[]> wallsVerticesList:wallsVerticesListList)
                for(float[] wallVertex:wallsVerticesList)
                    {currentPortalVertexIndex=0;
                     //-1 is used to know that no vertex equal with this 
                     //vertex of wall has been found
                     portalVertexIndex=-1;
                     //look for this vertex in the vertices contained in the portals
                     for(float[] portalVertex:cell.getNeighboursPortalsList())
                         {//remind: T2_V3 (2 texture coordinates + 3 vertex coordinates)
                          if(portalVertex[2]==wallVertex[2]&&portalVertex[3]==wallVertex[3]&&portalVertex[4]==wallVertex[4])
                              {portalVertexIndex=currentPortalVertexIndex;
                               break;
                              }
                          currentPortalVertexIndex++;
                         }
                     //if the vertex is in a portal
                     if(portalVertexIndex!=-1)
                         {//get the cell that is linked to this portal
                          neighborCell=cell.getNeighboursCellsList().get(portalVertexIndex/4);
                          //use the table of cellular maps to get the tables of the neighbor cell if any
                          neighborCellEntry=cellularMapsTable.get(neighborCell);       
                          //if these tables exist
                          if(neighborCellEntry!=null)
                              {//get the second table of the neighbor cell
                               neighBorVertexDataToUniqueIndexationTable=neighborCellEntry.getValue();              
                               //get the unique index by using this second table
                               knownUniqueVertexIndex=neighBorVertexDataToUniqueIndexationTable.get(new VertexData(wallVertex));
                               //store the unique index
                               uniqueVertexIndex=knownUniqueVertexIndex.intValue();
                              }
                          //else (in this case, the neighbor cell has not yet been visited)
                          else
                              {//compute and store this new unique index
                               uniqueVertexIndex=uniqueVerticesIndicesCount;
                               //increment it in order to ensure the attributed value is really unique 
                               uniqueVerticesIndicesCount++;
                               //put it into the second table
                               vertexDataToUniqueIndexationTable.put(new VertexData(wallVertex),Integer.valueOf(uniqueVertexIndex));
                              }                            
                         }
                     //else
                     else
                         {//if the second table already contains this vertex
                          if((knownUniqueVertexIndex=vertexDataToUniqueIndexationTable.get(new VertexData(wallVertex)))!=null)
                              {//store the unique index
                               uniqueVertexIndex=knownUniqueVertexIndex.intValue();
                              }        
                          //else
                          else
                              {//compute and store this new unique index
                               uniqueVertexIndex=uniqueVerticesIndicesCount;
                               //increment it in order to ensure the attributed value is really unique 
                               uniqueVerticesIndicesCount++;
                               //put it into the second table
                               vertexDataToUniqueIndexationTable.put(new VertexData(wallVertex),Integer.valueOf(uniqueVertexIndex));
                              }               
                         }          
                     //fill the first table with the new duplicate index and the unique index
                     duplicateToUniqueIndexationTable.put(Integer.valueOf(duplicateVerticesIndicesCount),Integer.valueOf(uniqueVertexIndex));
                     //update the count of duplicate vertices
                     duplicateVerticesIndicesCount++;
                    }               
            //put the both tables in the table of cellular maps
            cellularMapsTable.put(cell,new SimpleEntry<LinkedHashMap<Integer,Integer>,LinkedHashMap<VertexData,Integer>>(duplicateToUniqueIndexationTable,vertexDataToUniqueIndexationTable));
            //go on visiting the network
            return(true);
        }

Watch the class tools.NetworkSet if you want to see the whole inner class. I need to implement something more simple for the texture coordinates as I said.

I wanted to build the game from svn source to take a clooser look at your code, but

[quote=""]
which version of jme and lwjgl do you use?

[edit] I have some problems with to sources, I added you to my MSN contacts. It will be easier to talk [/edit]

As I wrote in the download page on my personal website, the SVN version requires JMonkeyEngine 2.0 but not LWJGL (I donā€™t want to use LWJGL, I chose to repair the JOGL renderer in JMonkeyEngine instead), rather JOGL 1.1.1 ;D and maybe JOGL 2.0 when I begin writing the blueprint of the version for AndroĆÆd.

If you donā€™t want to use JMonkeyEngine, you can use the frozen deployed version that requires only JOGL 1.1.1, then download the ZIP file instead.

Obviously, you need ANT to compile. If it doesnā€™t work, manually use the target ā€œcompileā€ and the target ā€œrun-tilesgeneratorā€. If it takes too much time to produce the files, deactivate the system that removes redundancy, this is the last method called in the constructor of the class tools.TilesGenerator and relaunch thus target.

I added lots of bug fixes. The method that removes texture coordinates redundancy that relies mainly on Rivenā€™s suggestion (takes several minutes and maybe some hours, I need to test my latest fixes, those I submitted before going to work) is far slower than the method that relies both on his suggestion and the spatial coherence (takes a few seconds).

Everything works now and even the part of the code that uses only Rivenā€™s suggestion works fast:

http://tuer.tuxfamily.org/screenshots/blenderExport2.png

Nevertheless, JOGL and Blender doesnā€™t interpret the textures coordinates in the same way, the vertical coordinates are inverted, I think that it has something to do with a change in JOGL one year ago, Kenneth Russell explained it to me.

:frowning: It is a bit too much for JMonkeyEngine:

[quote]Exception in thread ā€œThread-1ā€ javax.media.opengl.GLException: java.lang.OutOfMemoryError: Java heap space
at javax.media.opengl.Threading.invokeOnOpenGLThread(Threading.java:271)
at javax.media.opengl.GLCanvas.maybeDoSingleThreadedWorkaround(GLCanvas.java:410)
at javax.media.opengl.GLCanvas.display(GLCanvas.java:244)
at com.sun.opengl.util.Animator.display(Animator.java:144)
at com.sun.opengl.util.Animator$MainLoop.run(Animator.java:181)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:462)
at java.util.LinkedHashMap.addEntry(LinkedHashMap.java:414)
at java.util.HashMap.put(HashMap.java:385)
at java.util.HashSet.add(HashSet.java:200)
at com.jmex.model.converters.ObjToJme$ArraySet.findSet(ObjToJme.java:574)
at com.jmex.model.converters.ObjToJme.addFaces(ObjToJme.java:407)
at com.jmex.model.converters.ObjToJme.processLine(ObjToJme.java:279)
at com.jmex.model.converters.ObjToJme.convert(ObjToJme.java:152)
at jme.LevelGameState.getInstance(LevelGameState.java:43)
at jme.JMEGameServiceProvider.getLevelGameState(JMEGameServiceProvider.java:71)
at jme.ExtendedMenuHandler$EnterAction.performAction(ExtendedMenuHandler.java:116)
at com.jme.input.ActionTrigger.performAction(ActionTrigger.java:264)
at com.jme.input.keyboard.KeyboardInputHandlerDevice$KeyTrigger.performAction(KeyboardInputHandlerDevice.java:99)
at com.jme.input.InputHandler.processTriggers(InputHandler.java:426)
at com.jme.input.InputHandler.update(InputHandler.java:411)
at jme.MenuState.update(MenuState.java:147)
at com.jmex.game.state.GameStateNode.update(GameStateNode.java:71)
at jme.JOGLMVCGame$GenericImplementor.simpleUpdate(JOGLMVCGame.java:104)
at com.jme.system.canvas.SimpleCanvasImpl.doUpdate(SimpleCanvasImpl.java:135)
at com.jmex.awt.jogl.JOGLAWTCanvas.display(JOGLAWTCanvas.java:180)
at com.sun.opengl.impl.GLDrawableHelper.display(GLDrawableHelper.java:78)
at javax.media.opengl.GLCanvas$DisplayAction.run(GLCanvas.java:435)
at com.sun.opengl.impl.GLDrawableHelper.invokeGL(GLDrawableHelper.java:194)
at javax.media.opengl.GLCanvas$DisplayOnEventDispatchThreadAction.run(GLCanvas.java:452)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:199)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:273)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:183)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:173)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:168)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:160)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:121)
[/quote]
It is strange as my own engine uses only 27 MB, I donā€™t understand, Iā€™m a bit disappointed :ā€™(

I think I will convert the file from WaveFront OBJ format to JME format outside of the game in the main preprocessing system that already requires 1.5 GB of RAM (512 MB should be enough for my map but not for a more complex one).

;D I succeeded in loading my level inside JMonkeyEngine 2.0. This engine doesnā€™t manage the memory very efficiently but it uses only 31 MB to handle my world for the moment, it is acceptable. The frame rate is very bad, only 4 FPS but it is my fault, I use only the basic features of JME, no batch, no lockingā€¦ and I canā€™t use my own algorithm of space partitioning :ā€™( I am falling asleep. I donā€™t sleep enough and I write mistakes, I forgot to call clone() in the program that generates the cells:

temporaryFullWallsList.add((PointPair)currentWallPiece.clone());

Then it modified cells that I expected not to be modified.

Iā€™m going to explain better how to configure your environment to use my source code tomorrow or Monday. The JDK (of course) 1.6, ANT, SVN, JME 2.0 and JOGL 1.1.1 are required.

Edit: using the lock allows me to get 12 FPS instead of 4 FPS ;D It is less than my previous engine (16 FPS when the scenegraph is off) but it is quite reassuring.

I did it today, I hope you will seriously have a look at my tests.

I updated the JNLP file, the JVM should start with 32 MB. The next build of the alpha version will use 20 MB instead of 27 MB and I have added lots of bug fixes, I solved some problems of precision in the computation of collisions by using more floats :frowning: and some linear interpolations. I almost fixed a very annoying bug discovered by EgonOlsen, this kind of fucking white void even fulling the small rooms sometimes, it did it by refactoring lots of important classes to remove a factor used to scale the level for Art Attack (the ancestor of TUER), this factor had forced me to keep huge vertex coordinates and then a frustum with a huge ā€œfar Zā€, this ā€œfar Zā€ was not correctly handled by other engines (JMonkeyEngine 2.0 displayed only the a part of the ceiling and a part of the floor) and some graphics cards (especially some series of ATI Radeon X1950 Pro 512 MB and some recent NVidia). The source code has been updated but not the binaries, I need to test the game a bit more before considering it works reliably. Good night, thank you for your remarks and your bug reports.

P.S: I have improved a bit a section of my website to explain how to build my game from the source code. Please Mac users and Netbeans users, have a look at it, I might have written some mistakes, I used Netbeans only twice in my life and a Mac too.

[quote=ā€œgouessej,post:427,topic:29428ā€]
If you are refering to the room/portal algorithm, then Iā€™m pretty sure you can use it with monkey engine. Each frame use your own datastructure and algorithm to determine wich rooms are visible. Create a SwitchNode that contains the rooms as children and has a Bit set wich detemine wich children to render. Use your algorithm to switch the correct bits on.

The quake3 benchmark uses this method. It uses a possible visible set instead of portals, but the principle is the same. The source can be found in this tread on the JME forum: http://www.jmonkeyengine.com/jmeforum/index.php?topic=2597.0

It contains SwitchNode implementaion you can use.

Thank you very much ;D. I asked them if I could use SwitchNodes to do this and nobody answered. Iā€™m going to investigate.

Tomā€™s advice has been very useful as now, I have a clear idea of the implementation of my own scenegraph inside JME 2.0. I have a forest of graphs, I link each graph to a node; as JME doesnā€™t support graph, I add each node from the same graph into the children of the same node in JME and I add another mechanism to contain the structure of the graphs. The nodes added into JME are all SwitchNode instances and they can be activated or deactivated before each rendering. Is it a good idea?

I have to admit I donā€™t understand what you wrote. What does the forest represent and what does the graph represent? how does this relate to rooms and portals?

Iā€™ll try to explain how I think it should be done. The world exist of a list of rooms. Two rooms can be connected threw a portal. Each room has a list of portals that connects it to neighboring rooms. This is a graph data structure. Each frame you have to determine what rooms are visible by recursively checking which portals are inside the view frustum. This gives you list of rooms that are visible.

In JME you create one SwitchNode with the rooms geometry in the child nodes. The child node contains the geometry for that room and no room/portal code! I assume that a piece of geometry is only in one room. It becomes slightly more complicated if geometry spans rooms.

Store the index of the room geometry node in your room class. Use the index to switch on the respective bit in the SwitchNode.

To recap: there is no room/portal code in the JME scenegraph. The JME scenegraph is a list of nodes in a SwitchNode. Each node represent the geometry for a room. When you have a list of visible rooms, you switch on the respective JME node by using the SwitchNode.

edit: Iā€™m not that familiar with JME. I assume you can write a SwitchNode that can have list of children where each child can be switched on or off independently. If there exist a SwitchNode in JME that only can switch on or of a single child, then you would have to have a SwtichNode for each room.

A forest of graphs is almost a list of graphs. Each node contains a cell (= a room), each link between both nodes is a portal (= a door or something that allows to go to another room) between both cells. Then, each cell contains a list of portals. When we want to render a level, for each graph, we look in which cell the player is located, we recursively look which cells are visible, we use portals to go from cells to cells, a cell is visible from another if the portal that connects them together is visible according to the view frustum culling.

[quote=ā€œtom,post:432,topic:29428ā€]
Ok I agree with you. In JME, you have a root node for each State instance. You suggest to add directly rooms as children of this root node whereas my idea is a very little bit more complicated in order to handle cases where some cells are in the same level but not connected in the same graph. This mechanism is already implemented in the version of TUER that works without JME, only the view frustum culling was not working accurately.

Watch this:

http://e6.img.v4.skyrock.net/e60/gouessej/pics/2271715395_2.png

Hi!

Currently Iā€™m trying to export the portals in WaveFront OBJ file format, then I will convert them in JBIN format and load them in JME 2.0. I use the primitive ā€œoā€ to name the cells and the portals. During the loading, I will watch the value of this primitive to ā€œreparentā€ the cells and the portals. I will use the ability of setting cull hint rather than the class SwitchNode. Integrating my implementation of the cells-and-portals algorithm in JMonkeyEngine 2.0 requires a lot of time :frowning:

Hi!

The first part of the integration of my implementation of the cells-and-portals algorithm seems to be ended but I need to test it a little bit. It means that the tree of JMonkeyEngine 2.0 contains the instances required to perform my variant of the algorithm but the second part is not yet implemented, it deals with the manipulation of this tree to cull hidden cells of course. Iā€™m going to draw another schema to explain to you how it works. The source code dealing with this is in the package ā€œjmeā€. If some people are interested, I might plan to port my implementation into Xith3D, Java3D or any other existing engines. The ā€œportalizerā€ is really engine-agnostic and will go on being improved to handle all kinds of levels, not only levels with one stair and with only orthogonal walls.

The schema below shows how I have finally structured my abstract data type, it has a bit changed:

http://tuer.tuxfamily.org/screenshots/jme_cells_and_portals_scenegraph.png

For the moment, Iā€™m able to display my arena by using this abstract data type but I need to implement the culling system.

Hi!

The breadth first search is working in JME 2.0. I would like to make a ā€œwebstartableā€ demo in order to show you what I have done but there are some very annoying bugs in the JOGL renderer of JME. Is it worth to show you another tech demo?

why not use the LWJGL Renderer for the webstart demo? should be an easy switch between JOGL/LWJGL now that you are using JME. :slight_smile:

On one hand, some people would be subjected to bugs of LWJGL and then would get only a black screen (at least me) or something like that (OpenGLException caused by an invalid enumā€¦) if I used LWJGL. On the other hand, if I go on using the JOGL renderer, everyone will have the same known bugs (issues 24 and 35) but they would at least see something.

I wrote a class to replace StandardGame with JOGL (to work around some bugs, not for the pleasure of reinventing the wheel) but youā€™re right, it would be quite easy to switch between JOGL and LWJGL. As I have only a very little time, for the moment, I go on using JOGL but I will keep the source code quite clean to ease this switch by avoiding the use of classes that directly depends on a particular renderer (it is open source, I prevent nobody from performing this switch). TUER has worked with JOGL since the beginning, I donā€™t want to give up. Thank you for your suggestion.

Hi!

The blue print of the tech demo of the next version of TUER is ready, give it a try:
http://download.tuxfamily.org/tuer/tuer.jnlp

It is extremely experimental and very limited but it allows to show you the progress of my project. For the moment, I only display the cell that contains the player and some bugs are still in the JOGL renderer of JME 2.0:

  • the flickering in the middle of the screen
  • no mouse move when no mouse button pressed

It still uses JOGL, it still works under Linux, Solaris, Mac OS X 10.5 and Windows and it still requires Java 1.6.