Composite Mesh

I implemented something I have called CompositeMesh. This is TriMesh on steroids - also contains vertex data and indices, but instead of having all indices grouped into triangles, ranges of them can mean different things.



If you want to create java3d-like TriangleStripArray, just make number of ranges with triangle strips. Quads ? No problem, just use quad range. Best part is that you can mix the strip types in indices, while sharing vertex data. This is for example useful in sphere - use two triangle fans for caps and z-resolution triangle strips for rest of sphere. I have reworked Sphere implementation to test this thing and my class is 3 times as fast for heavily divided spheres.



I got the idea for this class while I was working on stripifier for xith3d. Stripification sometimes leaves orphaned triangles behind, which does not fit to any strip. In xith3d/java3d world, you either need to have degenerated strips for connecting (ugly hack), restart single-element strips for every orphan triangle (performance hit) or create separate geometry just for them (performance hit and scenegraph clutter). With class like Composite Mesh, it is trivial to put everything into one mesh - vertex data will be shared between different range kinds, thus giving cleaner data to work with and better performance on GPU.



I’ll comment/cleanup code and try to post it tomorrow.



These were good news. Bad news are that collision handling will be non trivial. All variations of strips/fans/triangles/quads certainly won’t make it easy for implementor. But in general, triangle strips are a must for any serious opengl, they are often 2-3 times faster than comparable disconnected triangles.



With this class in place, I should be able to port my stripifier/normal generator from xith3d.

Not sure I completely understand. When you say you can put everything into one mesh, do you mean after it has passed the cull tests and is ready for rendering? Otherwise, how would you render a scene properly if everything is a single mesh?

Very interesting and with some good potential. I’m glad to see some new code coming in from ya after my comment (didn’t scare you away… hehe). Am I right that this would require a new draw method in the Renderer interface? What about utility classes like your stripifier? Do you have any others and are you planning to port them?

Ok, I think I actually misunderstood what you meant by "it is trivial to put everything into one mesh". So if you could explain that a bit more.

"renanse" wrote:
Very interesting and with some good potential. I'm glad to see some new code coming in from ya after my comment (didn't scare you away... hehe). Am I right that this would require a new draw method in the Renderer interface? What about utility classes like your stripifier? Do you have any others and are you planning to port them?

Yes, it does need new method in renderer interface. On the other hand Trimesh is just a specialized version of CompositeMesh - so it would be possible to remove Trimesh and make it just a subclass of Composite mesh with simplified constructor - but this is too far fetched change to do by me. I also think that optimized version of Trimesh rendering could be better, as it will be probably most common version of mesh.

Stripifier code also have an indexer - so you can feed it raw data (just vertexes grouped in triangles) and it will find same vertices and indexify them - then create strips (with configurable length of strip etc). It is based on nvidia stripifier - but AFAIK, nividia code is free to use, so it's port should be also free. If you want to check it out before I port it, it is in xith3d code base at
com.xith3d.utility.geometry.*/com.xith3d.utility.geometry.nvtristrip.*

As for the other classes, I have some code for bounding shapes - so I will be able to fit some missing intersection methods (but I'm waiting for change with removing Trimesh - it will probably change these classes a lot). I have a particle emitter system, but jME already has one - but maybe I will need to port it anyway, because it is already optimized for specific use - rendering NWN models. And this is the biggest part I can contribute - loader for NWN models, together with animation and emitter support for them.

If you have java3d installed, you can check it out at
http://nwn-j3d.sourceforge.net/applet/viewer.html
This is version for java3d - but porting to jME should not be very hard.
"mojomonk" wrote:
Ok, I think I actually misunderstood what you meant by "it is trivial to put everything into one mesh". So if you could explain that a bit more.

By 'everything' I mean of course 'everything from one shape'. If you have model which uses nuber triangle strips for geometry, you sometimes end up with number of triangles which would fit best in triangle fan or just be separate. They still use the same vertex data for given shape - just method of interpreting indexes is different. So instead of creating few shapes which duplicate vertex data, appearance/state just to have different geometry type, you can put it all in one CompositeMesh. As I have told, best example is Sphere - where you use two triangle fans for caps and number of triangles strips for rest of the body.

As a benefit, you don't longer need to implement number of different Geometry kinds like java3d - IndexedTriangleFanArray, IndexedTriangleStripArray, IndexedQuadArray, IndexedQuadStripArray - it is all available in one class, with simple API.
"abies" wrote:
I have a particle emitter system, but jME already has one - but maybe I will need to port it anyway, because it is already optimized for specific use - rendering NWN models. And this is the biggest part I can contribute - loader for NWN models, together with animation and emitter support for them.

If you have java3d installed, you can check it out at
http://nwn-j3d.sourceforge.net/applet/viewer.html
This is version for java3d - but porting to jME should not be very hard.

If you've read in the User Showcase section, you're probably already aware that user Graum and I are also doing stuff based on NWN formats and jME. Graum has been the one to do much of the NWN model loading based partially on some existing Delphi and Java sources. I'd planned to expand jME's particle system to cover some of the additional functionality of NWN's particle system in the next month or two. Maybe you'd be interested in sharing some experiences in these areas via email? (see my profile)

Here is the main class


package com.jme.scene;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.logging.Level;

import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.system.JmeException;
import com.jme.util.LoggingSystem;

/**
 * <code>Composite</code> defines a geometry mesh. This mesh defines a three
 * dimensional object via a collection of points, colors, normals and textures.
 * The points are referenced via a indices array. This array instructs the
 * renderer the order in which to draw the points, with exact meaning of
 * indices being defined by IndexRange collection. Index ranges are interpreted
 * one after another, consuming their 'count' indices each time. Every range
 * use same vertex data, so it is perfectly possible to reference already used
 * indices from different kind of range.
 *
 * <b> Warning - currently this class has broken collision support and can
 * return random values or crash if it is used in this way </b>
 * @author Artur Biesiadowski
 */
public class CompositeMesh extends TriMesh implements Serializable {

   protected IndexRange[] ranges;
   
   /**
    * Constructor instantiates a new <code>CompositeMesh</code> object.
    *
    * @param name
    *            the name of the scene element. This is required for
    *            identification and comparision purposes.
    */
   public CompositeMesh(String name) {
      super(name);
   }
   
   /**
    * Constructor instantiates a new <code>CompositeMesh</code> object. Provided
    * are the attributes that make up the mesh all attributes may be null,
    * except for vertices,indices and ranges
    *
    * @param name
    *            the name of the scene element. This is required for
    *            identification and comparision purposes.
    * @param vertices
    *            the vertices of the geometry.
    * @param normal
    *            the normals of the geometry.
    * @param color
    *            the colors of the geometry.
    * @param texture
    *            the texture coordinates of the mesh.
    * @param indices
    *            the indices of the vertex array.
    * @param ranges
    *            the list of index ranges to be used in rendering
    */
   public CompositeMesh(String name, Vector3f[] vertices, Vector3f[] normal,
         ColorRGBA[] color, Vector2f[] texture, int[] indices, IndexRange[] ranges) {
      super(name);
      this.reconstruct(vertices,normal,color,texture,indices,ranges);
      LoggingSystem.getLogger().log(Level.INFO, "CompositeMesh created.");
   }
   
   /**
    * Recreates the geometric information of this CompositeMesh from scratch. The
    * index,vertex and ranges array must not be null, but the others may be.
    *
    * @param vertices
    *            the vertices of the geometry.
    * @param normal
    *            the normals of the geometry.
    * @param color
    *            the colors of the geometry.
    * @param texture
    *            the texture coordinates of the mesh.
    * @param indices
    *            the indices of the vertex array.
    * @param ranges
    *            the list of index ranges to be used in rendering
    */
   public void reconstruct(Vector3f[] vertices, Vector3f[] normal,
         ColorRGBA[] color, Vector2f[] texture, int[] indices,IndexRange[] ranges) {
      super.reconstruct(vertices, normal, color, texture,indices);

      if ( ranges == null ) {
         LoggingSystem.getLogger().log(Level.WARNING,"Index ranges may not be null.");
         throw new JmeException("Index ranges may not be null.");
      }
      this.ranges = ranges;
   }

   /**
    *
    * @return currently set index ranges
    */
   public IndexRange[] getIndexRanges() {
      return ranges;
   }
   
   /**
    * Sets new index ranges - be sure to match it with updates to indices array if needed
    * @param ranges
    */
   public void setIndexRanges(IndexRange[] ranges) {
      this.ranges = ranges;
   }

   /**
    * <code>draw</code> calls super to set the render state then passes
    * itself to the renderer.
    *
    * @param r
    *            the renderer to display
    */
   public void draw(Renderer r) {
      //TODO: This part duplicates logic in TriMesh - maybe it can be optimized somehow ?
      if (!r.isProcessingQueue()) {
         if (r.checkAndAdd(this))
            return;
      }
      super.draw(r);
      r.draw(this);
   }
   
   /**
    * <code>drawBounds</code> calls super to set the render state then passes
    * itself to the renderer.
    *
    * @param r
    *            the renderer to display
    */
   public void drawBounds(Renderer r) {
      r.drawBounds(this);
   }
   
   /**
    * @return equivalent number of triangles - each quad counts as two triangles
    */
   public int getTriangleQuantity() {
      int quantity = 0;
      for ( int i =0; i < ranges.length; i++ ) {
         quantity += ranges[i].getTriangleQuantityEquivalent();
       }
      return quantity;
   }
   
   
   /**
    *
    * <code>setIndexBuffers</code> creates the <code>IntBuffer</code> that
    * contains the indices array.
    * 
    */
   public void updateIndexBuffer() {
      if (indices == null) {
         return;
      }
      IntBuffer indexBuffer = getIndexAsBuffer();
      if (indexBuffer == null || indexBuffer.capacity() < indices.length) {
         indexBuffer = ByteBuffer
               .allocateDirect(indices.length*4)
               .order(ByteOrder.nativeOrder())
               .asIntBuffer();
      }

      indexBuffer.clear();
      indexBuffer.put(indices, 0,indices.length);
      indexBuffer.flip();
      setIndexBuffer(indexBuffer);
   }
   
   /**
    * Create index range representing free, unconnected triangles.
    * @param count number of indexes to be put in this range
    * @return new IndexRange for unconnected triangles
    */
   public static IndexRange createTriangleRange(int count) {
      return new IndexRange(IndexRange.TRIANGLES,count);
   }

   /**
    * Create index range representing triangle strip
    * @param count number of indexes to be put in this range
    * @return new IndexRange for triangle strip
    */
   public static IndexRange createTriangleStrip(int count) {
      return new IndexRange(IndexRange.TRIANGLE_STRIP,count);
   }

   /**
    * Create index range representing triangle fan
    * @param count number of indexes to be put in this range
    * @return new IndexRange for triangle fan
    */
   public static IndexRange createTriangleFan(int count) {
      return new IndexRange(IndexRange.TRIANGLE_FAN,count);
   }

   /**
    * Create index range representing free, unconnected quads.
    * @param count number of indexes to be put in this range
    * @return new IndexRange for unconnected quads
    */
   public static IndexRange createQuadRange(int count) {
      return new IndexRange(IndexRange.QUADS,count);
   }

   /**
    * Create index range representing quad strip
    * @param count number of indexes to be put in this range
    * @return new IndexRange for quad strip
    */
   public static IndexRange createQuadStrip(int count) {
      return new IndexRange(IndexRange.QUAD_STRIP,count);
   }

   private static final long serialVersionUID = 1;
   
   
   /**
    * This class represents range of indexes to be interpreted in a way depending on 'kind'
    * attribute. To create instances of this class, please check CompositeMesh static methods.
    */
   public static class IndexRange implements java.io.Serializable {
      
      public static final int TRIANGLES = 1;
      public static final int TRIANGLE_STRIP = 2;
      public static final int TRIANGLE_FAN = 3;
      public static final int QUADS = 4;
      public static final int QUAD_STRIP = 5;
      
      private int kind;
      private int count;
      
      IndexRange(int aKind, int aCount) {
         kind = aKind;
         count = aCount;
         
      }
      
      public int getCount() {
         return count;
      }
      
      public int getKind() {
         return kind;
      }
      
      /**
       * @return equivalent in triangles of elements drawn (1 quad counts as two triangles)
       */
      public long getTriangleQuantityEquivalent() {
         switch (kind) {
            case TRIANGLES:
               return count/3;
            case TRIANGLE_STRIP:
               return count-2;
            case TRIANGLE_FAN:
               return count-2;
            case QUADS:
               return (count/4)*2;
            case QUAD_STRIP:
               return ((count-2)/2)*2;
            default:
               throw new JmeException("Unknown kind of index range");
         }
      }
      
      public String toString() {
         return "IndexRange kind=" + KIND_NAMES[getKind()] + " count="+getCount();
      }
      
      private String[] KIND_NAMES = {null,"TRIANGLES","TRIANGLE_STRIP","TRIANGLE_FAN","QUADS","QUAD_STRIP"};
      
      private static final long serialVersionUID = 1;



   }

}

And here are diffs for other parts of system



Index: DummyDisplaySystem.java
===================================================================
RCS file: /cvs/jme/src/com/jme/scene/model/XMLparser/Converters/DummyDisplaySystem.java,v
retrieving revision 1.15
diff -u -r1.15 DummyDisplaySystem.java
--- DummyDisplaySystem.java   13 Sep 2004 21:13:12 -0000   1.15
+++ DummyDisplaySystem.java   14 Sep 2004 20:10:03 -0000
@@ -173,6 +173,7 @@
             public void draw(Curve c) {}
             public void draw(Text t) {}
             public void draw(TriMesh t) {}
+            public void draw(CompositeMesh t) {}
             public void draw(WidgetRenderer wr) {}
             public RenderQueue getQueue() {return null;}
             public boolean isProcessingQueue() {return false;}




Index: LWJGLRenderer.java
===================================================================
RCS file: /cvs/jme/src/com/jme/renderer/lwjgl/LWJGLRenderer.java,v
retrieving revision 1.46
diff -u -r1.46 LWJGLRenderer.java
--- LWJGLRenderer.java   14 Sep 2004 05:05:00 -0000   1.46
+++ LWJGLRenderer.java   14 Sep 2004 20:09:31 -0000
@@ -55,14 +55,14 @@
 
 package com.jme.renderer.lwjgl;
 
+import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
 import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.util.logging.Level;
-import javax.imageio.ImageIO;
 
-import java.awt.image.BufferedImage;
+import javax.imageio.ImageIO;
 
 import org.lwjgl.BufferUtils;
 import org.lwjgl.opengl.Display;
@@ -73,6 +73,7 @@
 import org.lwjgl.opengl.GL15;
 import org.lwjgl.opengl.GLContext;
 import org.lwjgl.opengl.glu.GLU;
+
 import com.jme.bounding.BoundingVolume;
 import com.jme.curve.Curve;
 import com.jme.math.Quaternion;
@@ -82,6 +83,7 @@
 import com.jme.renderer.ColorRGBA;
 import com.jme.renderer.RenderQueue;
 import com.jme.renderer.Renderer;
+import com.jme.scene.CompositeMesh;
 import com.jme.scene.Geometry;
 import com.jme.scene.Line;
 import com.jme.scene.Point;
@@ -900,16 +902,9 @@
         GL11.glPopMatrix();
     }
 
-    /**
-     * <code>draw</code> renders a <code>TriMesh</code> object including
-     * it's normals, colors, textures and vertices.
-     *
-     * @see com.jme.renderer.Renderer#draw(com.jme.scene.TriMesh)
-     * @param t
-     *            the mesh to render.
-     */
-    public void draw(TriMesh t) {
-        // set world matrix
+    // shared implementation of state management for both draw(Trimesh) and draw(CompositeMesh)
+    private void predrawMesh(Geometry t) {
+       // set world matrix
         Quaternion rotation = t.getWorldRotation();
         Vector3f translation = t.getWorldTranslation();
         Vector3f scale = t.getWorldScale();
@@ -920,11 +915,13 @@
         GL11.glTranslatef(translation.x, translation.y, translation.z);
         GL11.glRotatef(rot, vRot.x, vRot.y, vRot.z);
         GL11.glScalef(scale.x, scale.y, scale.z);
-        if (!(scale.x == 1 && scale.y == 1 && scale.z == 1))
-                GL11.glEnable(GL11.GL_NORMALIZE); // since we are using
-        // glScalef, we should enable
-        // this to keep normals
-        // working.
+        if (!(scale.x == 1 && scale.y == 1 && scale.z == 1)) {
+                GL11.glEnable(GL11.GL_NORMALIZE);
+                // since we are using
+                // glScalef, we should enable
+                // this to keep normals
+                // working.
+        }
 
         prepVBO(t);
 
@@ -995,6 +992,24 @@
             }
         }
 
+    }
+   
+    private void postdrawMesh(Geometry t) {
+       GL11.glMatrixMode(GL11.GL_MODELVIEW);
+        GL11.glPopMatrix();
+    }
+   
+   
+    /**
+     * <code>draw</code> renders a <code>TriMesh</code> object including
+     * it's normals, colors, textures and vertices.
+     *
+     * @see com.jme.renderer.Renderer#draw(com.jme.scene.TriMesh)
+     * @param t
+     *            the mesh to render.
+     */
+    public void draw(TriMesh t) {
+        predrawMesh(t);
         IntBuffer indices = t.getIndexAsBuffer();
         if (statisticsOn) {
             numberOfTris += (t.getTriangleQuantity() >= 0 ? t
@@ -1006,9 +1021,59 @@
         GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, t.getVertQuantity(),
                 indices);
 
-        GL11.glMatrixMode(GL11.GL_MODELVIEW);
-        GL11.glPopMatrix();
+        postdrawMesh(t);
+    }
+   
+    /**
+     * <code>draw</code> renders a <code>CompositeMesh</code> object including
+     * it's normals, colors, textures and vertices.
+     *
+     * @see com.jme.renderer.Renderer#draw(com.jme.scene.CompositeMesh)
+     * @param t
+     *            the mesh to render.
+     */
+    public void draw(CompositeMesh t) {
+       predrawMesh(t);
+
+        IntBuffer indices = t.getIndexAsBuffer().duplicate();
+        CompositeMesh.IndexRange[] ranges = t.getIndexRanges();
+        if (statisticsOn) {
+            numberOfVerts += (t.getVertQuantity() >= 0 ? t.getVertQuantity() : t.getVertices().length);
+            numberOfTris += t.getTriangleQuantity();
+        }
+
+       
+        indices.position(0);
+        for ( int i =0; i < ranges.length; i++){
+           int mode;
+           switch (ranges[i].getKind()) {
+              case CompositeMesh.IndexRange.TRIANGLES:
+                 mode = GL11.GL_TRIANGLES;
+                 break;
+              case CompositeMesh.IndexRange.TRIANGLE_STRIP:
+                 mode = GL11.GL_TRIANGLE_STRIP;
+                 break;
+              case CompositeMesh.IndexRange.TRIANGLE_FAN:
+                 mode = GL11.GL_TRIANGLE_FAN;
+                 break;
+              case CompositeMesh.IndexRange.QUADS:
+                 mode = GL11.GL_QUADS;
+                 break;
+              case CompositeMesh.IndexRange.QUAD_STRIP:
+                 mode = GL11.GL_QUAD_STRIP;
+                 break;
+              default:
+                 throw new JmeException("Unknown index range type "+ ranges[i].getKind());
+           }
+           indices.limit(indices.position()+ranges[i].getCount());
+           GL12.glDrawRangeElements(mode, 0, t.getVertQuantity(),indices);
+           indices.position(indices.limit());
+        }
+
+        postdrawMesh(t);
     }
+   
+   
 
     IntBuffer buf = BufferUtils.createIntBuffer(16);
 




Index: Renderer.java
===================================================================
RCS file: /cvs/jme/src/com/jme/renderer/Renderer.java,v
retrieving revision 1.46
diff -u -r1.46 Renderer.java
--- Renderer.java   13 Sep 2004 21:13:11 -0000   1.46
+++ Renderer.java   14 Sep 2004 20:09:12 -0000
@@ -33,6 +33,7 @@
 
 import com.jme.bounding.BoundingVolume;
 import com.jme.curve.Curve;
+import com.jme.scene.CompositeMesh;
 import com.jme.scene.Geometry;
 import com.jme.scene.Line;
 import com.jme.scene.Point;
@@ -420,6 +421,12 @@
      * @param t the mesh to be rendered.
      */
     public void draw(TriMesh t);
+   
+    /**
+     * <code>draw</code> renders a composite mesh to the back buffer.
+     * @param t the mesh to be rendered.
+     */
+    public void draw(CompositeMesh c);
 
     /**
      * <code>draw</code> renders a Widget that is associated




Index: Sphere.java
===================================================================
RCS file: /cvs/jme/src/com/jme/scene/shape/Sphere.java,v
retrieving revision 1.6
diff -u -r1.6 Sphere.java
--- Sphere.java   21 Aug 2004 06:18:34 -0000   1.6
+++ Sphere.java   14 Sep 2004 20:10:23 -0000
@@ -35,14 +35,14 @@
 import com.jme.math.Vector2f;
 import com.jme.math.Vector3f;
 import com.jme.renderer.ColorRGBA;
-import com.jme.scene.TriMesh;
+import com.jme.scene.CompositeMesh;
 
 /**
  * <code>Sphere</code> is um ... a sphere :)
  * @author Joshua Slack
  * @version $Id: Sphere.java,v 1.6 2004/08/21 06:18:34 cep21 Exp $
  */
-public class Sphere extends TriMesh {
+public class Sphere extends CompositeMesh {
     private int zSamples;
     private int radialSamples;
 
@@ -241,66 +241,48 @@
     private void setIndexData() {
 
         // allocate connectivity
-        int indexQuantity = 2 * (zSamples - 2) * radialSamples;
-        indices = new int[3 * indexQuantity];
+       int zStrips = zSamples-3;
+       
+        indices = new int[zStrips*2*(radialSamples+1) + 2*(radialSamples+2)];
 
         // generate connectivity
+       
+        // zStrips * triangle strip around the sphere
         int index = 0;
-        for (int iZ = 0, iZStart = 0; iZ < (zSamples - 3); iZ++) {
+        for (int iZ = 0, iZStart = 0; iZ < zStrips; iZ++) {
             int i0 = iZStart;
-            int i1 = i0 + 1;
             iZStart += (radialSamples + 1);
             int i2 = iZStart;
-            int i3 = i2 + 1;
-            for (int i = 0; i < radialSamples; i++, index += 6) {
-                if (true) {
-                    indices[index + 0] = i0++;
-                    indices[index + 1] = i1;
-                    indices[index + 2] = i2;
-                    indices[index + 3] = i1++;
-                    indices[index + 4] = i3++;
-                    indices[index + 5] = i2++;
-                } else // inside view
-                    {
-                    indices[index + 0] = i0++;
-                    indices[index + 1] = i2;
-                    indices[index + 2] = i1;
-                    indices[index + 3] = i1++;
-                    indices[index + 4] = i2++;
-                    indices[index + 5] = i3++;
-                }
+            for (int i = 0; i <= radialSamples; i++) {
+                indices[index++] = i0+i;
+                indices[index++] = i2+i;
             }
         }
 
-        // south pole triangles
-        for (int i = 0; i < radialSamples; i++, index += 3) {
-            if (true) {
-                indices[index + 0] = i;
-                indices[index + 1] = vertex.length - 2;
-                indices[index + 2] = i + 1;
-            } else // inside view
-                {
-                indices[index + 0] = i;
-                indices[index + 1] = i + 1;
-                indices[index + 2] = vertex.length - 2;
-            }
+
+        // south pole triangles (triangle fan)
+        indices[index++] = vertex.length - 2;
+        for (int i = 0; i <= radialSamples; i++) {
+            indices[index++] = i;
         }
 
-        // north pole triangles
+
+        // north pole triangles (triangle fan)
         int iOffset = (zSamples - 3) * (radialSamples + 1);
-        for (int i = 0; i < radialSamples; i++, index += 3) {
-            if (true) {
-                indices[index + 0] = i + iOffset;
-                indices[index + 1] = i + 1 + iOffset;
-                indices[index + 2] = vertex.length - 1;
-            } else // inside view
-                {
-                indices[index + 0] = i + iOffset;
-                indices[index + 1] = vertex.length - 1;
-                indices[index + 2] = i + 1 + iOffset;
-            }
+        indices[index++] = vertex.length - 1;
+        for (int i = 0; i <= radialSamples; i++) {
+            indices[index++] = i + iOffset;
         }
         setIndices(indices);
+       
+        IndexRange[] ranges = new IndexRange[zStrips+2];
+        for ( int i =0; i < ranges.length-2; i++) {
+           ranges[i] = CompositeMesh.createTriangleStrip(2*(radialSamples+1));
+        }
+        ranges[ranges.length-2] = CompositeMesh.createTriangleFan(radialSamples+2);
+        ranges[ranges.length-1] = CompositeMesh.createTriangleFan(radialSamples+2);
+        setIndexRanges(ranges);
+       
     }
 
     private void setColorData() {

when you have a chance could you e-mail me the new class and the patch files?

Will this slow down jme?

"Badmi" wrote:
Will this slow down jme?

Slow down ? All current functionalityis unchanged, you just get another kind of geometry to put into scenegraph. As far as speed is concerned, CompositeMesh should be up to 3 times faster for triangle/vertex program-limited scenes, with average of about 1.5/2 times probably. But you don't have to use it, so all current programs will run at the same speed - only cost is few extra kb for extra class in jme jar.

There is a bug in CompositeMesh draw method - it causes geometry to be drawn twice. Here is a correct version



public void draw(Renderer r) {
   if (!r.isProcessingQueue()) {
      if (r.checkAndAdd(this))
         return;
   }
   applyStates();
   r.draw(this);
}

Mojo, have you tried this out yet? I’d be interested in playing with it.



Also, abies, you talked about a stripifier? Would be interesting to take some existing trimeshes and see how it ran after “stripification”…

Yes, I have and it runs very nice, the reason I’m a little hesitant at the moment is I want to see how difficult it will be to integrate into the collision system (bounding and OBBTrees).

Nod. Sure… although I suppose it can even be restricted to the many cases where collision support is not needed (if it came to that.)

I’m just ending up port of stripifier and here is some performance data. I have used stanford bunny as example, 16301 and 69451 faces versions. Fps were




             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



Trimesh is original jme trimesh. Trimesh opt for cache is same trimesh, but with indexes reordered for GPU cache with stripifier utility method (still separate triangles). Mixed stitched is 2-range composite mesh (one range for big strip array stitched with degenerate triangles and second for free triangles). Mixed non-striched is n-range composite mesh (one range for free triangles and number of strip ranges). Cont strip is one big stiched strip, with single-triangles being inside strip at cost of degenerates. Chunked strip is as above, but using separate ranges instead of stiches (with possibility of 1-triangle long strips).

As you can see, performance varies. Trimesh optimized for cache looks very good for smaller case, which probably covers most real-life models. On the other hand, continous strip looks like best solution overall - performs well for both models.

This is of course very GPU and model dependent. Process of generating strips/optimizing triangles takes ages for bigger model - in range of minutes, so it is certainly something do to offline or at first startup of game, saving it later to geometry file.

I hope to post all code tomorrow, but I'm also preparing for next round of Google Code Jam, so wish me luck :)

I could add an option to model converters to do that, so when they are loaded the stripping would already be done. Looks very promising.

As with the stripifier, I’m working on getting this in, and focusing on how it works with tri level collision/picking.

Mojo, maybe I can look into collision handling for CompositeMesh ? From what I see, it is just a problem of adding construct(CompositeMesh parent) which would translate various ranges from composite to plain triangle tree. Picking is probably no brainer with that in place. What could be needed is to redefine both getTriangle versions for CompositeMesh, but this is mostly trivial (even if a bit time consuming).



Are there any showstoppers I don’t see here ?