|
@@ -31,6 +31,13 @@
|
|
*/
|
|
*/
|
|
package com.jme3.bullet.collision.shapes;
|
|
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.bullet.util.NativeMeshUtil;
|
|
import com.jme3.export.InputCapsule;
|
|
import com.jme3.export.InputCapsule;
|
|
import com.jme3.export.JmeExporter;
|
|
import com.jme3.export.JmeExporter;
|
|
@@ -40,41 +47,91 @@ import com.jme3.scene.Mesh;
|
|
import com.jme3.scene.VertexBuffer.Type;
|
|
import com.jme3.scene.VertexBuffer.Type;
|
|
import com.jme3.scene.mesh.IndexBuffer;
|
|
import com.jme3.scene.mesh.IndexBuffer;
|
|
import com.jme3.util.BufferUtils;
|
|
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
|
|
* Basic mesh collision shape
|
|
|
|
+ *
|
|
* @author normenhansen
|
|
* @author normenhansen
|
|
*/
|
|
*/
|
|
public class MeshCollisionShape extends CollisionShape {
|
|
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";
|
|
|
|
+ private static final String NATIVE_BVH = "nativeBvh";
|
|
protected int numVertices, numTriangles, vertexStride, triangleIndexStride;
|
|
protected int numVertices, numTriangles, vertexStride, triangleIndexStride;
|
|
protected ByteBuffer triangleIndexBase, vertexBase;
|
|
protected ByteBuffer triangleIndexBase, vertexBase;
|
|
protected long meshId = 0;
|
|
protected long meshId = 0;
|
|
|
|
+ protected long nativeBVHBuffer = 0;
|
|
|
|
+ private boolean memoryOptimized;
|
|
|
|
|
|
public MeshCollisionShape() {
|
|
public MeshCollisionShape() {
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * creates a collision shape from the given TriMesh
|
|
|
|
- * @param mesh the TriMesh to use
|
|
|
|
|
|
+ * Creates a collision shape from the given Mesh.
|
|
|
|
+ * Default behavior, more optimized for memory usage.
|
|
|
|
+ *
|
|
|
|
+ * @param mesh
|
|
*/
|
|
*/
|
|
public MeshCollisionShape(Mesh mesh) {
|
|
public MeshCollisionShape(Mesh mesh) {
|
|
- createCollisionMesh(mesh);
|
|
|
|
|
|
+ this(mesh, true);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Creates a collision shape from the given Mesh.
|
|
|
|
+ * <code>memoryOptimized</code> determines if optimized instead of
|
|
|
|
+ * quantized BVH will be used.
|
|
|
|
+ * Internally, <code>memoryOptimized</code> BVH is slower to calculate (~4x)
|
|
|
|
+ * but also smaller (~0.5x).
|
|
|
|
+ * It is preferable to use the memory optimized version and then serialize
|
|
|
|
+ * the resulting MeshCollisionshape as this will also save the
|
|
|
|
+ * generated BVH.
|
|
|
|
+ * An exception can be procedurally / generated collision shapes, where
|
|
|
|
+ * the generation time is more of a concern
|
|
|
|
+ *
|
|
|
|
+ * @param mesh the Mesh to use
|
|
|
|
+ * @param memoryOptimized True to generate a memory optimized BVH,
|
|
|
|
+ * false to generate quantized BVH.
|
|
|
|
+ */
|
|
|
|
+ public MeshCollisionShape(final Mesh mesh, final boolean memoryOptimized) {
|
|
|
|
+ this.memoryOptimized = memoryOptimized;
|
|
|
|
+ this.createCollisionMesh(mesh);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Advanced constructor, usually you don’t want to use this, but the Mesh
|
|
|
|
+ * based one. Passing false values can lead to a crash, use at own risk
|
|
|
|
+ *
|
|
|
|
+ * This constructor bypasses all copy logic normally used, this allows for
|
|
|
|
+ * faster bullet shape generation when using procedurally generated Meshes.
|
|
|
|
+ *
|
|
|
|
+ *
|
|
|
|
+ * @param indices the raw index buffer
|
|
|
|
+ * @param vertices the raw vertex buffer
|
|
|
|
+ * @param memoryOptimized use quantisize BVH, uses less memory, but slower
|
|
|
|
+ */
|
|
|
|
+ public MeshCollisionShape(ByteBuffer indices, ByteBuffer vertices, 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(true);
|
|
|
|
+ }
|
|
|
|
+
|
|
private void createCollisionMesh(Mesh 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.
|
|
|
|
|
|
+ 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();
|
|
IndexBuffer indices = mesh.getIndicesAsList();
|
|
FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
|
|
FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
|
|
@@ -93,69 +150,86 @@ public class MeshCollisionShape extends CollisionShape {
|
|
vertices.rewind();
|
|
vertices.rewind();
|
|
vertices.clear();
|
|
vertices.clear();
|
|
|
|
|
|
- createShape();
|
|
|
|
|
|
+ this.createShape(true);
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * 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 {
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void write(final JmeExporter ex) throws IOException {
|
|
super.write(ex);
|
|
super.write(ex);
|
|
OutputCapsule capsule = ex.getCapsule(this);
|
|
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);
|
|
|
|
-
|
|
|
|
- capsule.write(triangleIndexBase.array(), "triangleIndexBase", new byte[0]);
|
|
|
|
- capsule.write(vertexBase.array(), "vertexBase", new byte[0]);
|
|
|
|
|
|
+ capsule.write(numVertices, MeshCollisionShape.NUM_VERTICES, 0);
|
|
|
|
+ capsule.write(numTriangles, MeshCollisionShape.NUM_TRIANGLES, 0);
|
|
|
|
+ capsule.write(vertexStride, MeshCollisionShape.VERTEX_STRIDE, 0);
|
|
|
|
+ capsule.write(triangleIndexStride, MeshCollisionShape.TRIANGLE_INDEX_STRIDE, 0);
|
|
|
|
+
|
|
|
|
+ triangleIndexBase.position(0);
|
|
|
|
+ byte[] triangleIndexBasearray = new byte[triangleIndexBase.limit()];
|
|
|
|
+ triangleIndexBase.get(triangleIndexBasearray);
|
|
|
|
+ capsule.write(triangleIndexBasearray, MeshCollisionShape.TRIANGLE_INDEX_BASE, null);
|
|
|
|
+
|
|
|
|
+ vertexBase.position(0);
|
|
|
|
+ byte[] vertexBaseArray = new byte[vertexBase.limit()];
|
|
|
|
+ vertexBase.get(vertexBaseArray);
|
|
|
|
+ capsule.write(vertexBaseArray, MeshCollisionShape.VERTEX_BASE, null);
|
|
|
|
+
|
|
|
|
+ if (memoryOptimized) {
|
|
|
|
+ byte[] data = saveBVH(objectId);
|
|
|
|
+ capsule.write(data, MeshCollisionShape.NATIVE_BVH, null);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- public void read(JmeImporter im) throws IOException {
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void read(final JmeImporter im) throws IOException {
|
|
super.read(im);
|
|
super.read(im);
|
|
InputCapsule capsule = im.getCapsule(this);
|
|
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);
|
|
|
|
-
|
|
|
|
- triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0]));
|
|
|
|
- vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0])).order(ByteOrder.nativeOrder());
|
|
|
|
- createShape();
|
|
|
|
|
|
+ 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, null));
|
|
|
|
+ this.vertexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.VERTEX_BASE, null));
|
|
|
|
+
|
|
|
|
+ byte[] nativeBvh = capsule.readByteArray(MeshCollisionShape.NATIVE_BVH, null);
|
|
|
|
+ if (nativeBvh == null) {
|
|
|
|
+ // Either using non memory optimized BVH or old J3O file
|
|
|
|
+ memoryOptimized = false;
|
|
|
|
+ createShape(true);
|
|
|
|
+ } else {
|
|
|
|
+ // Using memory optimized BVH, load from J3O, then assign it.
|
|
|
|
+ memoryOptimized = true;
|
|
|
|
+ createShape(false);
|
|
|
|
+ nativeBVHBuffer = setBVH(nativeBvh, this.objectId);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- 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);
|
|
|
|
|
|
+ private void createShape(boolean buildBvt) {
|
|
|
|
+ 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 = createShape(memoryOptimized, buildBvt, 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(long meshId);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * returns the pointer to the native buffer used by the in place
|
|
|
|
+ * de-serialized shape, must be freed when not used anymore!
|
|
|
|
+ */
|
|
|
|
+ private native long setBVH(byte[] buffer, long objectid);
|
|
|
|
+
|
|
|
|
+ private native byte[] saveBVH(long objectId);
|
|
|
|
+
|
|
|
|
+ private native long createShape(boolean memoryOptimized, boolean buildBvt, long meshId);
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- protected void finalize() throws Throwable {
|
|
|
|
|
|
+ public void finalize() throws Throwable {
|
|
super.finalize();
|
|
super.finalize();
|
|
- Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Mesh {0}", Long.toHexString(meshId));
|
|
|
|
- finalizeNative(meshId);
|
|
|
|
|
|
+ Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Mesh {0}", Long.toHexString(this.meshId));
|
|
|
|
+ if (this.meshId > 0) {
|
|
|
|
+ this.finalizeNative(this.meshId, this.nativeBVHBuffer);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- private native void finalizeNative(long objectId);
|
|
|
|
-}
|
|
|
|
|
|
+ private native void finalizeNative(long objectId, long nativeBVHBuffer);
|
|
|
|
+}
|