/*
* 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.
*/
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";
private static final String NATIVE_BVH = "nativeBvh";
protected int numVertices, numTriangles, vertexStride, triangleIndexStride;
protected ByteBuffer triangleIndexBase, vertexBase;
protected long meshId = 0;
protected long nativeBVHBuffer = 0;
private boolean memoryOptimized;
public MeshCollisionShape() {
}
/**
* Creates a collision shape from the given Mesh.
* Default behavior, more optimized for memory usage.
*
* @param mesh
*/
public MeshCollisionShape(Mesh 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(null);
}
private void createCollisionMesh(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();
int verticesLength = mesh.getVertexCount() * 3;
for (int i = 0; i < verticesLength; i++) {
float tempFloat = vertices.get();
vertexBase.putFloat(tempFloat);
}
int indicesLength = mesh.getTriangleCount() * 3;
for (int i = 0; i < indicesLength; i++) {
triangleIndexBase.putInt(indices.get(i));
}
vertices.rewind();
vertices.clear();
this.createShape(null);
}
@Override
public void write(final JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule capsule = ex.getCapsule(this);
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);
}
}
@Override
public void read(final JmeImporter im) throws IOException {
super.read(im);
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, null));
this.vertexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.VERTEX_BASE, null));
byte[] nativeBvh = capsule.readByteArray(MeshCollisionShape.NATIVE_BVH, null);
memoryOptimized=nativeBvh != null;
createShape(nativeBvh);
}
private void createShape(byte bvh[]) {
boolean buildBvh=bvh==null||bvh.length==0;
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, buildBvh, this.meshId);
Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(this.objectId));
if(!buildBvh) nativeBVHBuffer = setBVH(bvh, this.objectId);
this.setScale(this.scale);
this.setMargin(this.margin);
}
/**
* 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
public 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, this.nativeBVHBuffer);
}
}
private native void finalizeNative(long objectId, long nativeBVHBuffer);
}