Stripifier

I have put stripifier classes at

http://nwn-j3d.sourceforge.net/misc/jme-stripifier.zip



Here is an example how to use it - please also check javadocs for GeometryCreator class.



I’ll later implement method for creating GeometryInfo from already existing TriMesh.



If you want ply files, please look at

The Stanford 3D Scanning Repository

Be warned - res2 is very fast, for full resolution, stripification can take few minutes. This PlyLoader will work only for bunny - rest of ply files have a bit different format, I’ll make more compatible loader for inclusion into jME later.



package test;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.StreamTokenizer;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.CompositeMesh;
import com.jme.scene.Geometry;
import com.jme.scene.TriMesh;
import com.jme.util.LoggingSystem;
import com.jme.util.geom.GeometryCreator;
import com.jme.util.geom.JmeGeometryInfo;

public class PlyLoader extends SimpleGame
{
   
   private Quaternion rotQuat = new Quaternion();
     private float angle = 0;
     private Vector3f axis = new Vector3f(1, 1, 0);
     private Geometry s;

     /**
      * Entry point for the test,
      * @param args
      */
     public static void main(String[] args) {
       LoggingSystem.getLogger().setLevel(java.util.logging.Level.OFF);
       PlyLoader app = new PlyLoader();
       app.setDialogBehaviour(SimpleGame.NEVER_SHOW_PROPS_DIALOG);
       app.start();
     }

     protected void simpleUpdate() {
       if (tpf < 1) {
         angle = angle + (tpf * 1);
         if (angle > 360) {
           angle = 0;
         }
       }
       rotQuat.fromAngleAxis(angle, axis);
       s.setLocalRotation(rotQuat);
     }

     /**
      * builds the trimesh.
      * @see com.jme.app.SimpleGame#initGame()
      */
     protected void simpleInitGame() {
       display.setTitle("jME");
       try {
          s = loadObj("e:\models\bun_zipper_res2.ply");
       } catch (Exception e ) {
          throw new Error(e);
       }
      
      
       s.setLocalTranslation(new Vector3f(0,0,-40));
       s.setModelBound(new BoundingBox());
       s.updateModelBound();
       rootNode.attachChild(s);
     }
   

       
   
    public static Geometry loadObj(String filename) throws IOException{
        BufferedReader br = new BufferedReader(new FileReader(filename));
       
        StreamTokenizer st = new StreamTokenizer(br);
        st.resetSyntax();
        st.eolIsSignificant(false);
        st.wordChars(0,255);
        st.whitespaceChars(' ', ' ');
        st.whitespaceChars('n','n');
        st.whitespaceChars('r','r');
        st.whitespaceChars('t','t');
        String str;
        int vertexCount = 0;
        int facesCount = 0;
       
       
        GeometryCreator create = new GeometryCreator();

       
        while ( true ) {
            int token = st.nextToken();
            if ( token == StreamTokenizer.TT_EOF )
                break;
            if ( token != StreamTokenizer.TT_WORD)
                continue;
           
            if ( st.sval.equalsIgnoreCase("element")) {
                st.nextToken();
                if ( st.sval.equalsIgnoreCase("vertex") ) {
                    st.nextToken();
                    vertexCount = Integer.parseInt(st.sval);
                } else if (st.sval.equalsIgnoreCase("face")) {
                   st.nextToken();
                    facesCount = Integer.parseInt(st.sval);
                }
            } else if (st.sval.equalsIgnoreCase("end_header") ){
                break;
            }   
        }
       
        float scale = 200;
        for ( int i =0; i < vertexCount; i++) {
            st.nextToken();
            float x = scale*Float.parseFloat(st.sval);
            st.nextToken();
            float y = scale*Float.parseFloat(st.sval);
            st.nextToken();
            float z = scale*Float.parseFloat(st.sval);
            st.nextToken();
            st.nextToken();
            create.addCoordinate(x,y,z);
           
        }
       
        for ( int i =0; i < facesCount; i++ ) {
            st.nextToken();
            st.nextToken();
            int f1 = Integer.parseInt(st.sval);
            st.nextToken();
            int f2 = Integer.parseInt(st.sval);
            st.nextToken();
            int f3 = Integer.parseInt(st.sval);
            create.addCoordIndex(f1);
            create.addCoordIndex(f2);
            create.addCoordIndex(f3);
            create.setFaceSmoothingGroup(1);
            create.nextFace();
        }
       
       
        JmeGeometryInfo geom = new JmeGeometryInfo();
        create.fillGeometryInfo(geom);
        //geom.recalculateFlatNormals();

        geom.recalculateSmoothGroupNormals();
        geom.weldVertices();
       
        TriMesh g;
       
        //geom.optimizeTrianglesForCache();
        g = geom.createTrimesh("TestMesh");
        //g = geom.createMixedArray("a1",4,true);
        //g = geom.createContinousStripMesh("a1");
        //g = geom.createChunkedStripArray("a1");
        System.out.println("Indices = "+g.getIndices().length + " vertices= "+g.getVertices().length);
        if ( g instanceof CompositeMesh ) {
           System.out.println("Ranges = "+((CompositeMesh)g).getIndexRanges().length);
        }

       
        return g;
 
    }

}


If it’s not too much to ask, would you mind posting fps and a pic for the lazy (or those with piles of work… ? :frowning: )


             Smaller       Bigger

Trimesh          160           35

Trimesh opt      340           35
for cache

Mixed            300           62
stitched

Mixed            220           72
non-stitched

Cont strip       340           62

Chunked strip    225           74



For discussion of results please see Composite Mesh thread
http://www.mojomonkeycoding.com/jmeforum/viewtopic.php?t=864&start=15

As for the screenshot, they all look the same :)

Pardon my ignorance… Just a couple questions.



You say it’ll take a few minutes… Is this at game runtime, or is there a reasonable and efficient way to store the stripified model and do this ahead of time?



Also… how does this affect CLOD? From what little I know about both CLOD and stripification, both can really help rendering performance… any chance of them working together, or will the stripifier just take too long to do in real time?





–K

"Shmooh" wrote:
You say it'll take a few minutes.. Is this at game runtime, or is there a reasonable and efficient way to store the stripified model and do this ahead of time?
You should do it before starting game or at model loading time - certainly not during rendering.


Also.. how does this affect CLOD? From what little I know about both CLOD and stripification, both can really help rendering performance.. any chance of them working together, or will the stripifier just take too long to do in real time?


No chance of using it with CLOD. Stripification is done on CPU and costs several orders of magnitude more time than rendering given model.

If you use strips, you don't generally use CLOD. With stripified geometry you can render about twice as many triangles as with separate triangles. You can use discrete LOD and change it far enough so changes won't be visible. Thanks to that, you won't have all CPU overhead of CLOD.

You can use stripified meshes with vertex morphing/animation - just you cannot play with changing number/order of indices.

If you put this in, I can add a “stripify” option to the model loader so that this is done when the model’s XML is created.

"Cep21" wrote:
If you put this in, I can add a "stripify" option to the model loader so that this is done when the model's XML is created.

Problem is with choosing correct version of output. It is quite model and GPU dependent. IMHO, it should be configurable. Regardless of that, we should provide some reasonable default. Best solution probably will be to write some benchmark application, which will load few models and measure fps for all kinds of stripification. Then we would run it on few different GPUs to see what is best across the boards.

This could be a start of jME benchmark application BTW ;)
"abies" wrote:
This could be a start of jME benchmark application BTW ;)
What, and break from tradition?? 8-O

Heh, above guest was me… wow, first time that’s happened.

Without VBO - I thought it is turned on by default for cards supporting it.



Here are results with VBO


Trimesh           165    50
Trimesh cache     340   121
Mixed stiched     310   115
Mixed non-stiched 310   115
Cont strip        340   125
Chunked strip     345   125

Cool, so with all 4 VBO fields enabled? Looks like it evens out the playing field amongst the various methods.

So far looks pretty good abies. I promise I’ll take a deeper look and start investigating getting it into the base line as soon as I get the collision updates out of the way.

Any update on this? Would be interesting see how this effects FPS on a few apps I have.

He has a link at the top of the page where you can use them in your local code. It’s not in CVS yet, I’ll look into integrating it when I have taken care of a couple other things.

Ok, finally, I am working this into the base code.



Documenting where needed and just trying to understand it myself.

After much distraction, I’m working on this again. Got it all in and license header etc. Working on comments at the moment.



I’m not thrilled about the test though, as it doesn’t accurately represent how one would use the stripifier with existing jME objects. I’ll look into what to do regarding that after I finish with some of the comments (putting abies as the author of course :slight_smile: ).

For the test, I think it would be best to implement some kind of loader using GeometryCreator. If you want, I can give a shot at .ply loader (The Stanford 3D Scanning Repository) - I have it already working for few models, probably not much work left to make if fully generic.

I agree, that would be a good test. However, I would also like a test that shows how to take some TriMesh objects that you might already have and preprocess them into a CompositeMesh. Until we are able to convert the loader into generating a CompositeMesh, people might find they need/want to do that.

added, sans tests.