MeshCollisionShape serialize

Since creating my main Collisionshape for the level currently reched like 1minute on the server, I started looking for ways to optimize, and saw in the native bullet wiki, that saving the generated acceleration structure is possible.

I want to try to get a java interface for this in the wrapper, as I guess may users will benefit from this. (and especially the ones targetting mobile devices)

So I wanted to ask if anyone already tried to do this or has any knowlegde with this?

@normen

Just store the collision shape in a j3o? Works with both versions.

But it rebuilds the native one then, and that needs very long.

[java]
@Override
public void read(final JmeImporter im) throws IOException {
super.read(im);
final InputCapsule capsule = im.getCapsule(this);
this.numVertices = capsule.readInt(“numVertices”, 0);
this.numTriangles = capsule.readInt(“numTriangles”, 0);
this.vertexStride = capsule.readInt(“vertexStride”, 0);
this.triangleIndexStride = capsule.readInt(“triangleIndexStride”, 0);

	this.triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0]));
	this.vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0])).order(ByteOrder.nativeOrder());
	this.createShape(); //this one here needs 1 minute
}

[/java]

Ah I see, the derived data. Well if that is possible why not? But it should be implemented in both versions then.

Well my basic approach/target would be to override that read/save in the native one to save the calculated shape if available /and load it into the j3o. The jbullet one misses the low level stuff to do that without a complete port of the c based code.

So it would change nothing about the interface, it would only increase the j3o size a bit but improve loading speed.

@normen Currently writing a testcase for the speedup/size differences measurement before starting to implement the binding, I figured that there is a critival bug in the current code preventing it from working at all. as the bytebuffers are only allow array() if its a wrapped one, but bullet uses direct ones.

The problem is I dont know if this breaks it for jbullet (it should not if it only uses the normal interface, if it internally expects to use array() however. Can you maybee test this?

ps. Sorry for the changed formating, plz only look at the write and save methods

Index: MeshCollisionShape.java
===================================================================
--- MeshCollisionShape.java	(revision 10788)
+++ MeshCollisionShape.java	(working copy)
@@ -31,6 +31,13 @@
  */
 package com.jme3.bullet.collision.shapes;
 
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
 import com.jme3.bullet.util.NativeMeshUtil;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
@@ -40,122 +47,129 @@
 import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.mesh.IndexBuffer;
 import com.jme3.util.BufferUtils;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.util.logging.Level;
-import java.util.logging.Logger;
 
 /**
  * Basic mesh collision shape
+ * 
  * @author normenhansen
  */
 public class MeshCollisionShape extends CollisionShape {
 
-    protected int numVertices, numTriangles, vertexStride, triangleIndexStride;
-    protected ByteBuffer triangleIndexBase, vertexBase;
-    protected long meshId = 0;
+	protected int	numVertices, numTriangles, vertexStride, triangleIndexStride;
+	protected ByteBuffer	triangleIndexBase, vertexBase;
+	protected long			meshId	= 0;
 
-    public MeshCollisionShape() {
-    }
+	public MeshCollisionShape() {
+	}
 
-    /**
-     * creates a collision shape from the given TriMesh
-     * @param mesh the TriMesh to use
-     */
-    public MeshCollisionShape(Mesh mesh) {
-        createCollisionMesh(mesh);
-    }
+	/**
+	 * creates a collision shape from the given TriMesh
+	 * 
+	 * @param mesh
+	 *            the TriMesh to use
+	 */
+	public MeshCollisionShape(final Mesh mesh) {
+		this.createCollisionMesh(mesh);
+	}
 
-    private void createCollisionMesh(Mesh mesh) {
-        triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4);
-        vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4);
-        numVertices = mesh.getVertexCount();
-        vertexStride = 12; //3 verts * 4 bytes per.
-        numTriangles = mesh.getTriangleCount();
-        triangleIndexStride = 12; //3 index entries * 4 bytes each.
+	private void createCollisionMesh(final Mesh mesh) {
+		this.triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4);
+		this.vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4);
+		this.numVertices = mesh.getVertexCount();
+		this.vertexStride = 12; // 3 verts * 4 bytes per.
+		this.numTriangles = mesh.getTriangleCount();
+		this.triangleIndexStride = 12; // 3 index entries * 4 bytes each.
 
-        IndexBuffer indices = mesh.getIndicesAsList();
-        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
-        vertices.rewind();
+		final IndexBuffer indices = mesh.getIndicesAsList();
+		final FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
+		vertices.rewind();
 
-        int verticesLength = mesh.getVertexCount() * 3;
-        for (int i = 0; i < verticesLength; i++) {
-            float tempFloat = vertices.get();
-            vertexBase.putFloat(tempFloat);
-        }
+		final int verticesLength = mesh.getVertexCount() * 3;
+		for (int i = 0; i < verticesLength; i++) {
+			final float tempFloat = vertices.get();
+			this.vertexBase.putFloat(tempFloat);
+		}
 
-        int indicesLength = mesh.getTriangleCount() * 3;
-        for (int i = 0; i < indicesLength; i++) {
-            triangleIndexBase.putInt(indices.get(i));
-        }
-        vertices.rewind();
-        vertices.clear();
+		final int indicesLength = mesh.getTriangleCount() * 3;
+		for (int i = 0; i < indicesLength; i++) {
+			this.triangleIndexBase.putInt(indices.get(i));
+		}
+		vertices.rewind();
+		vertices.clear();
 
-        createShape();
-    }
+		this.createShape();
+	}
 
-    /**
-     * creates a jme mesh from the collision shape, only needed for debugging
-     */
-//    public Mesh createJmeMesh(){
-//        return Converter.convert(bulletMesh);
-//    }
-    public void write(JmeExporter ex) throws IOException {
-        super.write(ex);
-        OutputCapsule capsule = ex.getCapsule(this);
-        capsule.write(numVertices, "numVertices", 0);
-        capsule.write(numTriangles, "numTriangles", 0);
-        capsule.write(vertexStride, "vertexStride", 0);
-        capsule.write(triangleIndexStride, "triangleIndexStride", 0);
+	/**
+	 * creates a jme mesh from the collision shape, only needed for debugging
+	 */
+	// public Mesh createJmeMesh(){
+	// return Converter.convert(bulletMesh);
+	// }
+	@Override
+	public void write(final JmeExporter ex) throws IOException {
+		super.write(ex);
+		final OutputCapsule capsule = ex.getCapsule(this);
+		capsule.write(this.numVertices, "numVertices", 0);
+		capsule.write(this.numTriangles, "numTriangles", 0);
+		capsule.write(this.vertexStride, "vertexStride", 0);
+		capsule.write(this.triangleIndexStride, "triangleIndexStride", 0);
 
-        capsule.write(triangleIndexBase.array(), "triangleIndexBase", new byte[0]);
-        capsule.write(vertexBase.array(), "vertexBase", new byte[0]);
-    }
+		this.triangleIndexBase.position(0);
+		final byte[] triangleIndexBasearray = new byte[this.triangleIndexBase.capacity()];
+		this.triangleIndexBase.get(triangleIndexBasearray);
+		capsule.write(triangleIndexBasearray, "triangleIndexBase", new byte[0]);
 
-    public void read(JmeImporter im) throws IOException {
-        super.read(im);
-        InputCapsule capsule = im.getCapsule(this);
-        numVertices = capsule.readInt("numVertices", 0);
-        numTriangles = capsule.readInt("numTriangles", 0);
-        vertexStride = capsule.readInt("vertexStride", 0);
-        triangleIndexStride = capsule.readInt("triangleIndexStride", 0);
+		this.vertexBase.position(0);
+		final byte[] vertexBaseArray = new byte[this.vertexBase.capacity()];
+		this.vertexBase.get(vertexBaseArray);
 
-        triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0]));
-        vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0])).order(ByteOrder.nativeOrder());
-        createShape();
-    }
+		capsule.write(vertexBaseArray, "vertexBase", new byte[0]);
+	}
 
-    protected void createShape() {
-//        bulletMesh = new IndexedMesh();
-//        bulletMesh.numVertices = numVertices;
-//        bulletMesh.numTriangles = numTriangles;
-//        bulletMesh.vertexStride = vertexStride;
-//        bulletMesh.triangleIndexStride = triangleIndexStride;
-//        bulletMesh.triangleIndexBase = triangleIndexBase;
-//        bulletMesh.vertexBase = vertexBase;
-//        bulletMesh.triangleIndexBase = triangleIndexBase;
-//        TriangleIndexVertexArray tiv = new TriangleIndexVertexArray(numTriangles, triangleIndexBase, triangleIndexStride, numVertices, vertexBase, vertexStride);
-//        objectId = new BvhTriangleMeshShape(tiv, true);
-//        objectId.setLocalScaling(Converter.convert(getScale()));
-//        objectId.setMargin(margin);
-        meshId = NativeMeshUtil.createTriangleIndexVertexArray(triangleIndexBase, vertexBase, numTriangles, numVertices, vertexStride, triangleIndexStride);
-        Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Mesh {0}", Long.toHexString(meshId));
-        objectId = createShape(meshId);
-        Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId));
-        setScale(scale);
-        setMargin(margin);
-    }
+	@Override
+	public void read(final JmeImporter im) throws IOException {
+		super.read(im);
+		final InputCapsule capsule = im.getCapsule(this);
+		this.numVertices = capsule.readInt("numVertices", 0);
+		this.numTriangles = capsule.readInt("numTriangles", 0);
+		this.vertexStride = capsule.readInt("vertexStride", 0);
+		this.triangleIndexStride = capsule.readInt("triangleIndexStride", 0);
 
-    private native long createShape(long meshId);
+		this.triangleIndexBase = BufferUtils.createByteBuffer(capsule.readByteArray("triangleIndexBase", new byte[0]));
+		this.vertexBase = BufferUtils.createByteBuffer(capsule.readByteArray("vertexBase", new byte[0])).order(ByteOrder.nativeOrder());
+		this.createShape();
+	}
 
-    @Override
-    protected void finalize() throws Throwable {
-        super.finalize();
-        Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Mesh {0}", Long.toHexString(meshId));
-        finalizeNative(meshId);
-    }
+	protected void createShape() {
+		// bulletMesh = new IndexedMesh();
+		// bulletMesh.numVertices = numVertices;
+		// bulletMesh.numTriangles = numTriangles;
+		// bulletMesh.vertexStride = vertexStride;
+		// bulletMesh.triangleIndexStride = triangleIndexStride;
+		// bulletMesh.triangleIndexBase = triangleIndexBase;
+		// bulletMesh.vertexBase = vertexBase;
+		// bulletMesh.triangleIndexBase = triangleIndexBase;
+		// TriangleIndexVertexArray tiv = new TriangleIndexVertexArray(numTriangles, triangleIndexBase, triangleIndexStride, numVertices, vertexBase, vertexStride);
+		// objectId = new BvhTriangleMeshShape(tiv, true);
+		// objectId.setLocalScaling(Converter.convert(getScale()));
+		// objectId.setMargin(margin);
+		this.meshId = NativeMeshUtil.createTriangleIndexVertexArray(this.triangleIndexBase, this.vertexBase, this.numTriangles, this.numVertices, this.vertexStride, this.triangleIndexStride);
+		Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Mesh {0}", Long.toHexString(this.meshId));
+		this.objectId = this.createShape(this.meshId);
+		Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(this.objectId));
+		this.setScale(this.scale);
+		this.setMargin(this.margin);
+	}
 
-    private native void finalizeNative(long objectId);
+	private native long createShape(long meshId);
+
+	@Override
+	protected void finalize() throws Throwable {
+		super.finalize();
+		Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Mesh {0}", Long.toHexString(this.meshId));
+		this.finalizeNative(this.meshId);
+	}
+
+	private native void finalizeNative(long objectId);
 }

I don’t undstand what you mean?

the write does ByteBuffer.array/( but this only works for Array BackedBytebuffers, in this case as it is a DirectByteBuffer used by the antive bullet it gives a

UnsupportedOperationException - If this buffer is not backed by an accessible array
The loading is even worse, as it tries to pass a HeapBuffer to the antive side, leading to a grinding crash with native stacktrace.

I still don’t understand? You are writing the code right now I thought, so why do you do that? :slight_smile: Just copy over the data into the format thats needed both on the native and java side?

THE EXISTING!!! CODE cannot work nothing I has done! the code you said that is currently there to serialize stuff.
-> Just store the collision shape in a j3o? Works with both versions.
Does NOT work with bullet indeed and could never work that way its currently coded.

I always start by doing a small testcase for stuff like this to verify the assumed to be working senviroment does indeed work, it failed due to above reasons.

@Empire Phoenix said: THE EXISTING!!!!!!!!!!!!!!!!!!! CODE cannot work nothing I has done! the code you said that is currently there to serialize stuff. -> Just store the collision shape in a j3o? Works with both versions. Does NOT work with bullet indeed and could never work that way its currently coded.

I always start by doing a small testcase for stuff like this to verify the assumed to be working senviroment does indeed work, it failed due to above reasons.

First of all, when somebody says he doesn’t understand then raising your voice won’t alleviate that. No need to scream here. :-x

Still what I said is the solution, just use the right buffer types and copy it over. Remember you are using alpha software.

The diff file doesn’t seem to work, apparently whatever changes (if any) that you made in there could not be applied.
Give it another try?

Also, I don’t see how heap vs. direct buffers have any relevance in this case. The discussion is whether the internal bullet collision shape representation could be serialized and how, since the jME3 savable system supports saving both heap and direct buffers, you can just load it into a direct bytebuffer and then save it with the rest of the data.

@Momoko_Fan said: The diff file doesn't seem to work, apparently whatever changes (if any) that you made in there could not be applied. Give it another try?

Also, I don’t see how heap vs. direct buffers have any relevance in this case. The discussion is whether the internal bullet collision shape representation could be serialized and how, since the jME3 savable system supports saving both heap and direct buffers, you can just load it into a direct bytebuffer and then save it with the rest of the data.

I think his point was that it is not doing that now thus the current implementation is broken.

@pspeed said: I think his point was that it is not doing that now thus the current implementation is broken.
Yes, we all know that. I was referring to the heap v. direct buffers concerns.

We all know the current one is broken? Thats news for me, I only expected it to not serialize the acceleration structures properly. But working with rebuilding the native part.

After all main logic is there and nearly correct, the only thing that has to be changed is that instead of bytebuffer.array() the bytebuffers needs to be copied into a byte[] to be saved. (And opposite direction copied into a native one). This is where it matters if its a direct buffer or a heap one, array is an invalid call on direct ones, leading to an excpetion, while on loading passing the heapbuffer to the native code lets it read large amounts of trash, due to the non fixed memory adress. This leads inevitably to a native crash in a few seconds.

Alternativly since I see this in some classes it might be good idea to add a writeByteBuffer(int start,int end) and readByteBuffer(int start,int end,boolean shouldBeDirect) methods to the capsule system. As many classes do this coping or rely on having wrapped buffers. After all thats general logic and nothing special, and there are probably a lot more saveable classes using bytebuffers.

Apart from that, the native stuff finally gets some monumentum, I’m sucessfully able to serialize the acceleration stuff. And current working on loading it, (due to bullet having complelty seperate stuff for saving and loading. For whatever reason loading is in bullet extras, whiel saving is in the core?)

2 Likes

Ok so here is the current result:

Read below for the full explanation and things I tried.

Replacement dropins
[java]
/*

  • Copyright © 2009-2012 jMonkeyEngine
  • All rights reserved.
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    */
    package com.jme3.bullet.collision.shapes;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme3.bullet.util.NativeMeshUtil;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.BufferUtils;

/**

  • Basic mesh collision shape

  • @author normenhansen
    */
    public class MeshCollisionShape extends CollisionShape {

    private static final String VERTEX_BASE = “vertexBase”;
    private static final String TRIANGLE_INDEX_BASE = “triangleIndexBase”;
    private static final String TRIANGLE_INDEX_STRIDE = “triangleIndexStride”;
    private static final String VERTEX_STRIDE = “vertexStride”;
    private static final String NUM_TRIANGLES = “numTriangles”;
    private static final String NUM_VERTICES = “numVertices”;
    protected int numVertices, numTriangles, vertexStride, triangleIndexStride;
    protected ByteBuffer triangleIndexBase, vertexBase;
    protected long meshId = 0;
    private boolean memoryOptimized;

    public MeshCollisionShape() {
    }

    /**

    • creates a collision shape from the given TriMesh memory optimized is much more memory efficient, however it is also several times slower.
    • @param mesh
    •        the TriMesh to use
      

    */
    public MeshCollisionShape(final Mesh mesh, final boolean memoryOptimized) {
    this.createCollisionMesh(mesh);
    this.memoryOptimized = memoryOptimized;
    }

    /**

    • creates a collision shape from the given TriMesh default behaviour, more optimized for memory useage
    • @param mesh
      */
      public MeshCollisionShape(final Mesh mesh) {
      this(mesh, true);
      }

    private void createCollisionMesh(final Mesh mesh) {
    this.triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4);
    this.vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4);
    this.numVertices = mesh.getVertexCount();
    this.vertexStride = 12; // 3 verts * 4 bytes per.
    this.numTriangles = mesh.getTriangleCount();
    this.triangleIndexStride = 12; // 3 index entries * 4 bytes each.

     final IndexBuffer indices = mesh.getIndicesAsList();
     final FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
     vertices.rewind();
    
     final int verticesLength = mesh.getVertexCount() * 3;
     for (int i = 0; i < verticesLength; i++) {
     	final float tempFloat = vertices.get();
     	this.vertexBase.putFloat(tempFloat);
     }
    
     final int indicesLength = mesh.getTriangleCount() * 3;
     for (int i = 0; i < indicesLength; i++) {
     	this.triangleIndexBase.putInt(indices.get(i));
     }
     vertices.rewind();
     vertices.clear();
    
     this.createShape();
    

    }

    public MeshCollisionShape(final ByteBuffer indices, final ByteBuffer vertices, final boolean memoryOptimized) {
    this.triangleIndexBase = indices;
    this.vertexBase = vertices;
    this.numVertices = vertices.limit() / 4 / 3;
    this.numTriangles = this.triangleIndexBase.limit() / 4 / 3;
    this.vertexStride = 12;
    this.triangleIndexStride = 12;
    this.memoryOptimized = memoryOptimized;
    this.createShape();
    }

    /**

    • creates a jme mesh from the collision shape, only needed for debugging
      */
      // public Mesh createJmeMesh(){
      // return Converter.convert(bulletMesh);
      // }
      @Override
      public void write(final JmeExporter ex) throws IOException {
      super.write(ex);
      final OutputCapsule capsule = ex.getCapsule(this);
      capsule.write(this.numVertices, MeshCollisionShape.NUM_VERTICES, 0);
      capsule.write(this.numTriangles, MeshCollisionShape.NUM_TRIANGLES, 0);
      capsule.write(this.vertexStride, MeshCollisionShape.VERTEX_STRIDE, 0);
      capsule.write(this.triangleIndexStride, MeshCollisionShape.TRIANGLE_INDEX_STRIDE, 0);

      this.triangleIndexBase.position(0);
      final byte[] triangleIndexBasearray = new byte[this.triangleIndexBase.capacity()];
      this.triangleIndexBase.get(triangleIndexBasearray);
      capsule.write(triangleIndexBasearray, MeshCollisionShape.TRIANGLE_INDEX_BASE, new byte[0]);

      this.vertexBase.position(0);
      final byte[] vertexBaseArray = new byte[this.vertexBase.capacity()];
      this.vertexBase.get(vertexBaseArray);

      capsule.write(vertexBaseArray, MeshCollisionShape.VERTEX_BASE, new byte[0]);

    }

    @Override
    public void read(final JmeImporter im) throws IOException {
    super.read(im);
    final InputCapsule capsule = im.getCapsule(this);
    this.numVertices = capsule.readInt(MeshCollisionShape.NUM_VERTICES, 0);
    this.numTriangles = capsule.readInt(MeshCollisionShape.NUM_TRIANGLES, 0);
    this.vertexStride = capsule.readInt(MeshCollisionShape.VERTEX_STRIDE, 0);
    this.triangleIndexStride = capsule.readInt(MeshCollisionShape.TRIANGLE_INDEX_STRIDE, 0);

     this.triangleIndexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.TRIANGLE_INDEX_BASE, new byte[0]));
     this.vertexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.VERTEX_BASE, new byte[0])).order(ByteOrder.nativeOrder());
    

    }

    private void createShape() {
    this.meshId = NativeMeshUtil.createTriangleIndexVertexArray(this.triangleIndexBase, this.vertexBase, this.numTriangles, this.numVertices, this.vertexStride, this.triangleIndexStride);
    Logger.getLogger(this.getClass().getName()).log(Level.FINE, “Created Mesh {0}”, Long.toHexString(this.meshId));
    this.objectId = this.createShape(this.memoryOptimized, this.meshId);
    Logger.getLogger(this.getClass().getName()).log(Level.FINE, “Created Shape {0}”, Long.toHexString(this.objectId));
    this.setScale(this.scale);
    this.setMargin(this.margin);
    }

    private native long createShape(boolean memoryOptimized, long meshId);

    @Override
    protected void finalize() throws Throwable {
    super.finalize();
    Logger.getLogger(this.getClass().getName()).log(Level.FINE, “Finalizing Mesh {0}”, Long.toHexString(this.meshId));
    if (this.meshId > 0) {
    this.finalizeNative(this.meshId);
    }
    }

    private native void finalizeNative(long objectId);
    }

[/java]

/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * Author: Normen Hansen
 */
#include "com_jme3_bullet_collision_shapes_MeshCollisionShape.h"
#include "jmeBulletUtil.h"
#include "BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h"
#include "btBulletDynamicsCommon.h"
#include "BulletCollision/Gimpact/btGImpactShape.h"


#ifdef __cplusplus
extern "C" {
#endif

    /*
     * Class:     com_jme3_bullet_collision_shapes_MeshCollisionShape
     * Method:    createShape
     * Signature: (J)J
     */
    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_createShape
    (JNIEnv* env, jobject object,jboolean isMemoryEfficient, jlong arrayId) {
        jmeClasses::initJavaClasses(env);
        btTriangleIndexVertexArray* array = reinterpret_cast<btTriangleIndexVertexArray*>(arrayId);
        btBvhTriangleMeshShape* shape = new btBvhTriangleMeshShape(array, isMemoryEfficient, true);
        return reinterpret_cast<jlong>(shape);
    }

    /*
     * Class:     com_jme3_bullet_collision_shapes_MeshCollisionShape
     * Method:    finalizeNative
     * Signature: (J)V
     */
    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_finalizeNative
    (JNIEnv* env, jobject object, jlong arrayId){
        btTriangleIndexVertexArray* array = reinterpret_cast<btTriangleIndexVertexArray*>(arrayId);
        delete(array);
    }


#ifdef __cplusplus
}
#endif

Ok my first attempt was to use the btSerializer to write out the whole shape and store it in the save, then restore it.

While this works, there is a speed problem.
Saving for a MeshShape is below 100ms usually so that is fine,
however loading with the btFile stuff needs around as long as recreating the shape from scratch. (Even when loaded from a memory buffer, I must say I’m greatly dissapointed by this, as there seems to be some really bad scaling algorithms in place).

Then saving only the bvh acceleration structure the same way led to a small improvement, for my test file wich needs 17 seconds to be regenerated it was around 11seconds to load the pregenerated one. This is still inacceptable.
I tried to create a own serialisation and deserialisation, but that a bit over the top for my c knowledge :confused:

Then I searched for other options to speed stuff up na bit and saw that the quantisized flag is always used from out binding. Settings this to false lead to around 13 seconds reduction but at a somewhat higher memory cost (around 1.5).

So I added methods to create a MeshShape with and without the quantisation, makeing sure that all current existing methods use the same way.
Then a little other improvement is to allow a raw version of the constructor, wich greatly helps for doing procedually generated meshes, as it gets rid of a extra copy of the data in memory.

Last but not least the serialisation is fixed, in the senso that trying to serialize a meshcollisionshape no longer leads to a exception, due to wrong directbytebuffeer handeling.

So discussion is open, what do you think? can we commit this? If not why?

So based on my understanding, setting the useQuantizedAabbCompression to false improves the serialization speed from 17 seconds to 13? Or 17 - 13 = 4? Or 11 - 13 = -2? I didn’t understand from your post.

Let me try to reparse:

The deserialisation they use is crap and slow as shit (due to the chunked binary format they use, as the parser is extremly slow) . No way to use it without rewriting that part.
What improves the speed for larger Meshshapes is allowing to not use quantisized bvh. This improves generation speed, not serialisation. However it will help, as the main reason for serialisation attempts were to reduce loading times.

Regenerating with Quantisized 17
Regenerating without Quantisized 2
De/Serialisation whole Shape: ~17
De/Serialisation only bhv 13

So if it is one large shape but there is still much free memory it’s a good idea to decrease loading times by disabling the quantisation.
This increaseses the memory amount of the shape by roughly 1.5-2

This person was able to improve their speed by from 4 seconds to 0.15 seconds by using serialization, but they are doing it slightly differently than you would expect.

1 Like

Ah this is intresting, they seemd to have succeded at doing my second approch via a different api. Thanks for sharing will look into it.
(Good that they are opensource this will greatly help as reference)

1 Like