aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/scene/Mesh.java
diff options
context:
space:
mode:
authorScott Barta <sbarta@google.com>2012-03-01 12:35:35 -0800
committerScott Barta <sbarta@google.com>2012-03-01 12:40:08 -0800
commit59b2e6871c65f58fdad78cd7229c292f6a177578 (patch)
tree2d4e7bfc05b93f40b34675d77e403dd1c25efafd /engine/src/core/com/jme3/scene/Mesh.java
parentf9b30489e75ac1eabc365064959804e99534f7ab (diff)
downloadjmonkeyengine-59b2e6871c65f58fdad78cd7229c292f6a177578.tar.gz
Adds the jMonkeyEngine library to the build.
Adds the jMonkeyEngine open source 3D game engine to the build. This is built as a static library and is only used by the Finsky client. Change-Id: I06a3f054df7b8a67757267d884854f70c5a16ca0
Diffstat (limited to 'engine/src/core/com/jme3/scene/Mesh.java')
-rw-r--r--engine/src/core/com/jme3/scene/Mesh.java1315
1 files changed, 1315 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/scene/Mesh.java b/engine/src/core/com/jme3/scene/Mesh.java
new file mode 100644
index 0000000..6c587f2
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Mesh.java
@@ -0,0 +1,1315 @@
+/*
+ * 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.scene;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.bih.BIHTree;
+import com.jme3.export.*;
+import com.jme3.material.RenderState;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.mesh.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.SafeArrayList;
+import java.io.IOException;
+import java.nio.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <code>Mesh</code> is used to store rendering data.
+ * <p>
+ * All visible elements in a scene are represented by meshes.
+ * Meshes may contain three types of geometric primitives:
+ * <ul>
+ * <li>Points - Every vertex represents a single point in space,
+ * the size of each point is specified via {@link Mesh#setPointSize(float) }.
+ * Points can also be used for {@link RenderState#setPointSprite(boolean) point
+ * sprite} mode.</li>
+ * <li>Lines - 2 vertices represent a line segment, with the width specified
+ * via {@link Mesh#setLineWidth(float) }.</li>
+ * <li>Triangles - 3 vertices represent a solid triangle primitive. </li>
+ * </ul>
+ *
+ * @author Kirill Vainer
+ */
+public class Mesh implements Savable, Cloneable {
+
+ /**
+ * The mode of the Mesh specifies both the type of primitive represented
+ * by the mesh and how the data should be interpreted.
+ */
+ public enum Mode {
+ /**
+ * A primitive is a single point in space. The size of the points
+ * can be specified with {@link Mesh#setPointSize(float) }.
+ */
+ Points(true),
+
+ /**
+ * A primitive is a line segment. Every two vertices specify
+ * a single line. {@link Mesh#setLineWidth(float) } can be used
+ * to set the width of the lines.
+ */
+ Lines(true),
+
+ /**
+ * A primitive is a line segment. The first two vertices specify
+ * a single line, while subsequent vertices are combined with the
+ * previous vertex to make a line. {@link Mesh#setLineWidth(float) } can
+ * be used to set the width of the lines.
+ */
+ LineStrip(false),
+
+ /**
+ * Identical to {@link #LineStrip} except that at the end
+ * the last vertex is connected with the first to form a line.
+ * {@link Mesh#setLineWidth(float) } can be used
+ * to set the width of the lines.
+ */
+ LineLoop(false),
+
+ /**
+ * A primitive is a triangle. Each 3 vertices specify a single
+ * triangle.
+ */
+ Triangles(true),
+
+ /**
+ * Similar to {@link #Triangles}, the first 3 vertices
+ * specify a triangle, while subsequent vertices are combined with
+ * the previous two to form a triangle.
+ */
+ TriangleStrip(false),
+
+ /**
+ * Similar to {@link #Triangles}, the first 3 vertices
+ * specify a triangle, each 2 subsequent vertices are combined
+ * with the very first vertex to make a triangle.
+ */
+ TriangleFan(false),
+
+ /**
+ * A combination of various triangle modes. It is best to avoid
+ * using this mode as it may not be supported by all renderers.
+ * The {@link Mesh#setModeStart(int[]) mode start points} and
+ * {@link Mesh#setElementLengths(int[]) element lengths} must
+ * be specified for this mode.
+ */
+ Hybrid(false);
+
+ private boolean listMode = false;
+
+ private Mode(boolean listMode){
+ this.listMode = listMode;
+ }
+
+ /**
+ * Returns true if the specified mode is a list mode (meaning
+ * ,it specifies the indices as a linear list and not some special
+ * format).
+ * Will return true for the types {@link #Points}, {@link #Lines} and
+ * {@link #Triangles}.
+ *
+ * @return true if the mode is a list type mode
+ */
+ public boolean isListMode(){
+ return listMode;
+ }
+ }
+
+ /**
+ * The bounding volume that contains the mesh entirely.
+ * By default a BoundingBox (AABB).
+ */
+ private BoundingVolume meshBound = new BoundingBox();
+
+ private CollisionData collisionTree = null;
+
+ private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+ private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>();
+ private VertexBuffer[] lodLevels;
+ private float pointSize = 1;
+ private float lineWidth = 1;
+
+ private transient int vertexArrayID = -1;
+
+ private int vertCount = -1;
+ private int elementCount = -1;
+ private int maxNumWeights = -1; // only if using skeletal animation
+
+ private int[] elementLengths;
+ private int[] modeStart;
+
+ private Mode mode = Mode.Triangles;
+
+ /**
+ * Creates a new mesh with no {@link VertexBuffer vertex buffers}.
+ */
+ public Mesh(){
+ }
+
+ /**
+ * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
+ * buffers} are shared between this and the clone mesh, the rest
+ * of the data is cloned.
+ *
+ * @return A shallow clone of the mesh
+ */
+ @Override
+ public Mesh clone() {
+ try {
+ Mesh clone = (Mesh) super.clone();
+ clone.meshBound = meshBound.clone();
+ clone.collisionTree = collisionTree != null ? collisionTree : null;
+ clone.buffers = buffers.clone();
+ clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
+ clone.vertexArrayID = -1;
+ if (elementLengths != null) {
+ clone.elementLengths = elementLengths.clone();
+ }
+ if (modeStart != null) {
+ clone.modeStart = modeStart.clone();
+ }
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Creates a deep clone of this mesh.
+ * The {@link VertexBuffer vertex buffers} and the data inside them
+ * is cloned.
+ *
+ * @return a deep clone of this mesh.
+ */
+ public Mesh deepClone(){
+ try{
+ Mesh clone = (Mesh) super.clone();
+ clone.meshBound = meshBound != null ? meshBound.clone() : null;
+
+ // TODO: Collision tree cloning
+ //clone.collisionTree = collisionTree != null ? collisionTree : null;
+ clone.collisionTree = null; // it will get re-generated in any case
+
+ clone.buffers = new IntMap<VertexBuffer>();
+ clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+ for (Entry<VertexBuffer> ent : buffers){
+ VertexBuffer bufClone = ent.getValue().clone();
+ clone.buffers.put(ent.getKey(), bufClone);
+ clone.buffersList.add(bufClone);
+ }
+
+ clone.vertexArrayID = -1;
+ clone.vertCount = -1;
+ clone.elementCount = -1;
+
+ // although this could change
+ // if the bone weight/index buffers are modified
+ clone.maxNumWeights = maxNumWeights;
+
+ clone.elementLengths = elementLengths != null ? elementLengths.clone() : null;
+ clone.modeStart = modeStart != null ? modeStart.clone() : null;
+ return clone;
+ }catch (CloneNotSupportedException ex){
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Clone the mesh for animation use.
+ * This creates a shallow clone of the mesh, sharing most
+ * of the {@link VertexBuffer vertex buffer} data, however the
+ * {@link Type#Position}, {@link Type#Normal}, and {@link Type#Tangent} buffers
+ * are deeply cloned.
+ *
+ * @return A clone of the mesh for animation use.
+ */
+ public Mesh cloneForAnim(){
+ Mesh clone = clone();
+ if (getBuffer(Type.BindPosePosition) != null){
+ VertexBuffer oldPos = getBuffer(Type.Position);
+
+ // NOTE: creates deep clone
+ VertexBuffer newPos = oldPos.clone();
+ clone.clearBuffer(Type.Position);
+ clone.setBuffer(newPos);
+
+ if (getBuffer(Type.BindPoseNormal) != null){
+ VertexBuffer oldNorm = getBuffer(Type.Normal);
+ VertexBuffer newNorm = oldNorm.clone();
+ clone.clearBuffer(Type.Normal);
+ clone.setBuffer(newNorm);
+
+ if (getBuffer(Type.BindPoseTangent) != null){
+ VertexBuffer oldTang = getBuffer(Type.Tangent);
+ VertexBuffer newTang = oldTang.clone();
+ clone.clearBuffer(Type.Tangent);
+ clone.setBuffer(newTang);
+ }
+ }
+ }
+ return clone;
+ }
+
+ /**
+ * Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal},
+ * and {@link Type#BindPoseTangent}
+ * buffers for this mesh by duplicating them based on the position and normal
+ * buffers already set on the mesh.
+ * This method does nothing if the mesh has no bone weight or index
+ * buffers.
+ *
+ * @param forSoftwareAnim Should be true if the bind pose is to be generated.
+ */
+ public void generateBindPose(boolean forSoftwareAnim){
+ if (forSoftwareAnim){
+ VertexBuffer pos = getBuffer(Type.Position);
+ if (pos == null || getBuffer(Type.BoneIndex) == null) {
+ // ignore, this mesh doesn't have positional data
+ // or it doesn't have bone-vertex assignments, so its not animated
+ return;
+ }
+
+ VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition);
+ bindPos.setupData(Usage.CpuOnly,
+ 3,
+ Format.Float,
+ BufferUtils.clone(pos.getData()));
+ setBuffer(bindPos);
+
+ // XXX: note that this method also sets stream mode
+ // so that animation is faster. this is not needed for hardware skinning
+ pos.setUsage(Usage.Stream);
+
+ VertexBuffer norm = getBuffer(Type.Normal);
+ if (norm != null) {
+ VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal);
+ bindNorm.setupData(Usage.CpuOnly,
+ 3,
+ Format.Float,
+ BufferUtils.clone(norm.getData()));
+ setBuffer(bindNorm);
+ norm.setUsage(Usage.Stream);
+ }
+
+ VertexBuffer tangents = getBuffer(Type.Tangent);
+ if (tangents != null) {
+ VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
+ bindTangents.setupData(Usage.CpuOnly,
+ 4,
+ Format.Float,
+ BufferUtils.clone(tangents.getData()));
+ setBuffer(bindTangents);
+ tangents.setUsage(Usage.Stream);
+ }
+ }
+ }
+
+ /**
+ * Prepares the mesh for software skinning by converting the bone index
+ * and weight buffers to heap buffers.
+ *
+ * @param forSoftwareAnim Should be true to enable the conversion.
+ */
+ public void prepareForAnim(boolean forSoftwareAnim){
+ if (forSoftwareAnim){
+ // convert indices
+ VertexBuffer indices = getBuffer(Type.BoneIndex);
+ ByteBuffer originalIndex = (ByteBuffer) indices.getData();
+ ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
+ originalIndex.clear();
+ arrayIndex.put(originalIndex);
+ indices.updateData(arrayIndex);
+
+ // convert weights
+ VertexBuffer weights = getBuffer(Type.BoneWeight);
+ FloatBuffer originalWeight = (FloatBuffer) weights.getData();
+ FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());
+ originalWeight.clear();
+ arrayWeight.put(originalWeight);
+ weights.updateData(arrayWeight);
+ }
+ }
+
+ /**
+ * Set the LOD (level of detail) index buffers on this mesh.
+ *
+ * @param lodLevels The LOD levels to set
+ */
+ public void setLodLevels(VertexBuffer[] lodLevels){
+ this.lodLevels = lodLevels;
+ }
+
+ /**
+ * @return The number of LOD levels set on this mesh, including the main
+ * index buffer, returns zero if there are no lod levels.
+ */
+ public int getNumLodLevels(){
+ return lodLevels != null ? lodLevels.length : 0;
+ }
+
+ /**
+ * Returns the lod level at the given index.
+ *
+ * @param lod The lod level index, this does not include
+ * the main index buffer.
+ * @return The LOD index buffer at the index
+ *
+ * @throws IndexOutOfBoundsException If the index is outside of the
+ * range [0, {@link #getNumLodLevels()}].
+ *
+ * @see #setLodLevels(com.jme3.scene.VertexBuffer[])
+ */
+ public VertexBuffer getLodLevel(int lod){
+ return lodLevels[lod];
+ }
+
+ /**
+ * Get the element lengths for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return element lengths
+ */
+ public int[] getElementLengths() {
+ return elementLengths;
+ }
+
+ /**
+ * Set the element lengths for {@link Mode#Hybrid} mesh mode.
+ *
+ * @param elementLengths The element lengths to set
+ */
+ public void setElementLengths(int[] elementLengths) {
+ this.elementLengths = elementLengths;
+ }
+
+ /**
+ * Set the mode start indices for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return mode start indices
+ */
+ public int[] getModeStart() {
+ return modeStart;
+ }
+
+ /**
+ * Get the mode start indices for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return mode start indices
+ */
+ public void setModeStart(int[] modeStart) {
+ this.modeStart = modeStart;
+ }
+
+ /**
+ * Returns the mesh mode
+ *
+ * @return the mesh mode
+ *
+ * @see #setMode(com.jme3.scene.Mesh.Mode)
+ */
+ public Mode getMode() {
+ return mode;
+ }
+
+ /**
+ * Change the Mesh's mode. By default the mode is {@link Mode#Triangles}.
+ *
+ * @param mode The new mode to set
+ *
+ * @see Mode
+ */
+ public void setMode(Mode mode) {
+ this.mode = mode;
+ updateCounts();
+ }
+
+ /**
+ * Returns the maximum number of weights per vertex on this mesh.
+ *
+ * @return maximum number of weights per vertex
+ *
+ * @see #setMaxNumWeights(int)
+ */
+ public int getMaxNumWeights() {
+ return maxNumWeights;
+ }
+
+ /**
+ * Set the maximum number of weights per vertex on this mesh.
+ * Only relevant if this mesh has bone index/weight buffers.
+ * This value should be between 0 and 4.
+ *
+ * @param maxNumWeights
+ */
+ public void setMaxNumWeights(int maxNumWeights) {
+ this.maxNumWeights = maxNumWeights;
+ }
+
+ /**
+ * Returns the size of points for point meshes
+ *
+ * @return the size of points
+ *
+ * @see #setPointSize(float)
+ */
+ public float getPointSize() {
+ return pointSize;
+ }
+
+ /**
+ * Set the size of points for meshes of mode {@link Mode#Points}.
+ * The point size is specified as on-screen pixels, the default
+ * value is 1.0. The point size
+ * does nothing if {@link RenderState#setPointSprite(boolean) point sprite}
+ * render state is enabled, in that case, the vertex shader must specify the
+ * point size by writing to <code>gl_PointSize</code>.
+ *
+ * @param pointSize The size of points
+ */
+ public void setPointSize(float pointSize) {
+ this.pointSize = pointSize;
+ }
+
+ /**
+ * Returns the line width for line meshes.
+ *
+ * @return the line width
+ */
+ public float getLineWidth() {
+ return lineWidth;
+ }
+
+ /**
+ * Specify the line width for meshes of the line modes, such
+ * as {@link Mode#Lines}. The line width is specified as on-screen pixels,
+ * the default value is 1.0.
+ *
+ * @param lineWidth The line width
+ */
+ public void setLineWidth(float lineWidth) {
+ this.lineWidth = lineWidth;
+ }
+
+ /**
+ * Indicates to the GPU that this mesh will not be modified (a hint).
+ * Sets the usage mode to {@link Usage#Static}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
+ */
+ public void setStatic() {
+ for (Entry<VertexBuffer> entry : buffers){
+ entry.getValue().setUsage(Usage.Static);
+ }
+ }
+
+ /**
+ * Indicates to the GPU that this mesh will be modified occasionally (a hint).
+ * Sets the usage mode to {@link Usage#Dynamic}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
+ */
+ public void setDynamic() {
+ for (Entry<VertexBuffer> entry : buffers){
+ entry.getValue().setUsage(Usage.Dynamic);
+ }
+ }
+
+ /**
+ * Indicates to the GPU that this mesh will be modified every frame (a hint).
+ * Sets the usage mode to {@link Usage#Stream}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
+ */
+ public void setStreamed(){
+ for (Entry<VertexBuffer> entry : buffers){
+ entry.getValue().setUsage(Usage.Stream);
+ }
+ }
+
+ /**
+ * Interleaves the data in this mesh. This operation cannot be reversed.
+ * Some GPUs may prefer the data in this format, however it is a good idea
+ * to <em>avoid</em> using this method as it disables some engine features.
+ */
+ public void setInterleaved(){
+ ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>();
+ for (Entry<VertexBuffer> entry : buffers){
+ vbs.add(entry.getValue());
+ }
+// ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values());
+ // index buffer not included when interleaving
+ vbs.remove(getBuffer(Type.Index));
+
+ int stride = 0; // aka bytes per vertex
+ for (int i = 0; i < vbs.size(); i++){
+ VertexBuffer vb = vbs.get(i);
+// if (vb.getFormat() != Format.Float){
+// throw new UnsupportedOperationException("Cannot interleave vertex buffer.\n" +
+// "Contains not-float data.");
+// }
+ stride += vb.componentsLength;
+ vb.getData().clear(); // reset position & limit (used later)
+ }
+
+ VertexBuffer allData = new VertexBuffer(Type.InterleavedData);
+ ByteBuffer dataBuf = BufferUtils.createByteBuffer(stride * getVertexCount());
+ allData.setupData(Usage.Static, 1, Format.UnsignedByte, dataBuf);
+
+ // adding buffer directly so that no update counts is forced
+ buffers.put(Type.InterleavedData.ordinal(), allData);
+ buffersList.add(allData);
+
+ for (int vert = 0; vert < getVertexCount(); vert++){
+ for (int i = 0; i < vbs.size(); i++){
+ VertexBuffer vb = vbs.get(i);
+ switch (vb.getFormat()){
+ case Float:
+ FloatBuffer fb = (FloatBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putFloat(fb.get());
+ }
+ break;
+ case Byte:
+ case UnsignedByte:
+ ByteBuffer bb = (ByteBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.put(bb.get());
+ }
+ break;
+ case Half:
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sb = (ShortBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putShort(sb.get());
+ }
+ break;
+ case Int:
+ case UnsignedInt:
+ IntBuffer ib = (IntBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putInt(ib.get());
+ }
+ break;
+ case Double:
+ DoubleBuffer db = (DoubleBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putDouble(db.get());
+ }
+ break;
+ }
+ }
+ }
+
+ int offset = 0;
+ for (VertexBuffer vb : vbs){
+ vb.setOffset(offset);
+ vb.setStride(stride);
+
+ vb.updateData(null);
+ //vb.setupData(vb.usage, vb.components, vb.format, null);
+ offset += vb.componentsLength;
+ }
+ }
+
+ private int computeNumElements(int bufSize){
+ switch (mode){
+ case Triangles:
+ return bufSize / 3;
+ case TriangleFan:
+ case TriangleStrip:
+ return bufSize - 2;
+ case Points:
+ return bufSize;
+ case Lines:
+ return bufSize / 2;
+ case LineLoop:
+ return bufSize;
+ case LineStrip:
+ return bufSize - 1;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Update the {@link #getVertexCount() vertex} and
+ * {@link #getTriangleCount() triangle} counts for this mesh
+ * based on the current data. This method should be called
+ * after the {@link Buffer#capacity() capacities} of the mesh's
+ * {@link VertexBuffer vertex buffers} has been altered.
+ *
+ * @throws IllegalStateException If this mesh is in
+ * {@link #setInterleaved() interleaved} format.
+ */
+ public void updateCounts(){
+ if (getBuffer(Type.InterleavedData) != null)
+ throw new IllegalStateException("Should update counts before interleave");
+
+ VertexBuffer pb = getBuffer(Type.Position);
+ VertexBuffer ib = getBuffer(Type.Index);
+ if (pb != null){
+ vertCount = pb.getData().capacity() / pb.getNumComponents();
+ }
+ if (ib != null){
+ elementCount = computeNumElements(ib.getData().capacity());
+ }else{
+ elementCount = computeNumElements(vertCount);
+ }
+ }
+
+ /**
+ * Returns the triangle count for the given LOD level.
+ *
+ * @param lod The lod level to look up
+ * @return The triangle count for that LOD level
+ */
+ public int getTriangleCount(int lod){
+ if (lodLevels != null){
+ if (lod < 0)
+ throw new IllegalArgumentException("LOD level cannot be < 0");
+
+ if (lod >= lodLevels.length)
+ throw new IllegalArgumentException("LOD level "+lod+" does not exist!");
+
+ return computeNumElements(lodLevels[lod].getData().capacity());
+ }else if (lod == 0){
+ return elementCount;
+ }else{
+ throw new IllegalArgumentException("There are no LOD levels on the mesh!");
+ }
+ }
+
+ /**
+ * Returns how many triangles or elements are on this Mesh.
+ * This value is only updated when {@link #updateCounts() } is called.
+ * If the mesh mode is not a triangle mode, then this returns the
+ * number of elements/primitives, e.g. how many lines or how many points,
+ * instead of how many triangles.
+ *
+ * @return how many triangles/elements are on this Mesh.
+ */
+ public int getTriangleCount(){
+ return elementCount;
+ }
+
+ /**
+ * Returns the number of vertices on this mesh.
+ * The value is computed based on the position buffer, which
+ * must be set on all meshes.
+ *
+ * @return Number of vertices on the mesh
+ */
+ public int getVertexCount(){
+ return vertCount;
+ }
+
+ /**
+ * Gets the triangle vertex positions at the given triangle index
+ * and stores them into the v1, v2, v3 arguments.
+ *
+ * @param index The index of the triangle.
+ * Should be between 0 and {@link #getTriangleCount()}.
+ *
+ * @param v1 Vector to contain first vertex position
+ * @param v2 Vector to contain second vertex position
+ * @param v3 Vector to contain third vertex position
+ */
+ public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3){
+ VertexBuffer pb = getBuffer(Type.Position);
+ IndexBuffer ib = getIndicesAsList();
+ if (pb != null && pb.getFormat() == Format.Float && pb.getNumComponents() == 3){
+ FloatBuffer fpb = (FloatBuffer) pb.getData();
+
+ // aquire triangle's vertex indices
+ int vertIndex = index * 3;
+ int vert1 = ib.get(vertIndex);
+ int vert2 = ib.get(vertIndex+1);
+ int vert3 = ib.get(vertIndex+2);
+
+ BufferUtils.populateFromBuffer(v1, fpb, vert1);
+ BufferUtils.populateFromBuffer(v2, fpb, vert2);
+ BufferUtils.populateFromBuffer(v3, fpb, vert3);
+ }else{
+ throw new UnsupportedOperationException("Position buffer not set or "
+ + " has incompatible format");
+ }
+ }
+
+ /**
+ * Gets the triangle vertex positions at the given triangle index
+ * and stores them into the {@link Triangle} argument.
+ * Also sets the triangle index to the <code>index</code> argument.
+ *
+ * @param index The index of the triangle.
+ * Should be between 0 and {@link #getTriangleCount()}.
+ *
+ * @param tri The triangle to store the positions in
+ */
+ public void getTriangle(int index, Triangle tri){
+ getTriangle(index, tri.get1(), tri.get2(), tri.get3());
+ tri.setIndex(index);
+ tri.setNormal(null);
+ }
+
+ /**
+ * Gets the triangle vertex indices at the given triangle index
+ * and stores them into the given int array.
+ *
+ * @param index The index of the triangle.
+ * Should be between 0 and {@link #getTriangleCount()}.
+ *
+ * @param indices Indices of the triangle's vertices
+ */
+ public void getTriangle(int index, int[] indices){
+ IndexBuffer ib = getIndicesAsList();
+
+ // acquire triangle's vertex indices
+ int vertIndex = index * 3;
+ indices[0] = ib.get(vertIndex);
+ indices[1] = ib.get(vertIndex+1);
+ indices[2] = ib.get(vertIndex+2);
+ }
+
+ /**
+ * Returns the mesh's VAO ID. Internal use only.
+ */
+ public int getId(){
+ return vertexArrayID;
+ }
+
+ /**
+ * Sets the mesh's VAO ID. Internal use only.
+ */
+ public void setId(int id){
+ if (vertexArrayID != -1)
+ throw new IllegalStateException("ID has already been set.");
+
+ vertexArrayID = id;
+ }
+
+ /**
+ * Generates a collision tree for the mesh.
+ * Called automatically by {@link #collideWith(com.jme3.collision.Collidable,
+ * com.jme3.math.Matrix4f,
+ * com.jme3.bounding.BoundingVolume,
+ * com.jme3.collision.CollisionResults) }.
+ */
+ public void createCollisionData(){
+ BIHTree tree = new BIHTree(this);
+ tree.construct();
+ collisionTree = tree;
+ }
+
+ /**
+ * Handles collision detection, internal use only.
+ * User code should only use collideWith() on scene
+ * graph elements such as {@link Spatial}s.
+ */
+ public int collideWith(Collidable other,
+ Matrix4f worldMatrix,
+ BoundingVolume worldBound,
+ CollisionResults results){
+
+ if (collisionTree == null){
+ createCollisionData();
+ }
+
+ return collisionTree.collideWith(other, worldMatrix, worldBound, results);
+ }
+
+ /**
+ * Set a floating point {@link VertexBuffer} on the mesh.
+ *
+ * @param type The type of {@link VertexBuffer},
+ * e.g. {@link Type#Position}, {@link Type#Normal}, etc.
+ *
+ * @param components Number of components on the vertex buffer, should
+ * be between 1 and 4.
+ *
+ * @param buf The floating point data to contain
+ */
+ public void setBuffer(Type type, int components, FloatBuffer buf) {
+// VertexBuffer vb = buffers.get(type);
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ if (buf == null)
+ return;
+
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.Float, buf);
+// buffers.put(type, vb);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ }else{
+ vb.setupData(Usage.Dynamic, components, Format.Float, buf);
+ }
+ updateCounts();
+ }
+
+ public void setBuffer(Type type, int components, float[] buf){
+ setBuffer(type, components, BufferUtils.createFloatBuffer(buf));
+ }
+
+ public void setBuffer(Type type, int components, IntBuffer buf) {
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.UnsignedInt, buf);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(Type type, int components, int[] buf){
+ setBuffer(type, components, BufferUtils.createIntBuffer(buf));
+ }
+
+ public void setBuffer(Type type, int components, ShortBuffer buf) {
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.UnsignedShort, buf);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(Type type, int components, byte[] buf){
+ setBuffer(type, components, BufferUtils.createByteBuffer(buf));
+ }
+
+ public void setBuffer(Type type, int components, ByteBuffer buf) {
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.UnsignedByte, buf);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(VertexBuffer vb){
+ if (buffers.containsKey(vb.getBufferType().ordinal()))
+ throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType());
+
+ buffers.put(vb.getBufferType().ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+
+ /**
+ * Clears or unsets the {@link VertexBuffer} set on this mesh
+ * with the given type.
+ * Does nothing if the vertex buffer type is not set initially
+ *
+ * @param type The type to remove
+ */
+ public void clearBuffer(VertexBuffer.Type type){
+ VertexBuffer vb = buffers.remove(type.ordinal());
+ if (vb != null){
+ buffersList.remove(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(Type type, int components, short[] buf){
+ setBuffer(type, components, BufferUtils.createShortBuffer(buf));
+ }
+
+ /**
+ * Get the {@link VertexBuffer} stored on this mesh with the given
+ * type.
+ *
+ * @param type The type of VertexBuffer
+ * @return the VertexBuffer data, or null if not set
+ */
+ public VertexBuffer getBuffer(Type type){
+ return buffers.get(type.ordinal());
+ }
+
+ /**
+ * Get the {@link VertexBuffer} data stored on this mesh in float
+ * format.
+ *
+ * @param type The type of VertexBuffer
+ * @return the VertexBuffer data, or null if not set
+ */
+ public FloatBuffer getFloatBuffer(Type type) {
+ VertexBuffer vb = getBuffer(type);
+ if (vb == null)
+ return null;
+
+ return (FloatBuffer) vb.getData();
+ }
+
+ /**
+ * Get the {@link VertexBuffer} data stored on this mesh in short
+ * format.
+ *
+ * @param type The type of VertexBuffer
+ * @return the VertexBuffer data, or null if not set
+ */
+ public ShortBuffer getShortBuffer(Type type) {
+ VertexBuffer vb = getBuffer(type);
+ if (vb == null)
+ return null;
+
+ return (ShortBuffer) vb.getData();
+ }
+
+ /**
+ * Acquires an index buffer that will read the vertices on the mesh
+ * as a list.
+ *
+ * @return A virtual or wrapped index buffer to read the data as a list
+ */
+ public IndexBuffer getIndicesAsList(){
+ if (mode == Mode.Hybrid)
+ throw new UnsupportedOperationException("Hybrid mode not supported");
+
+ IndexBuffer ib = getIndexBuffer();
+ if (ib != null){
+ if (mode.isListMode()){
+ // already in list mode
+ return ib;
+ }else{
+ // not in list mode but it does have an index buffer
+ // wrap it so the data is converted to list format
+ return new WrappedIndexBuffer(this);
+ }
+ }else{
+ // return a virtual index buffer that will supply
+ // "fake" indices in list format
+ return new VirtualIndexBuffer(vertCount, mode);
+ }
+ }
+
+ /**
+ * Get the index buffer for this mesh.
+ * Will return <code>null</code> if no index buffer is set.
+ *
+ * @return The index buffer of this mesh.
+ *
+ * @see Type#Index
+ */
+ public IndexBuffer getIndexBuffer() {
+ VertexBuffer vb = getBuffer(Type.Index);
+ if (vb == null)
+ return null;
+
+ Buffer buf = vb.getData();
+ if (buf instanceof ByteBuffer) {
+ return new IndexByteBuffer((ByteBuffer) buf);
+ } else if (buf instanceof ShortBuffer) {
+ return new IndexShortBuffer((ShortBuffer) buf);
+ } else if (buf instanceof IntBuffer) {
+ return new IndexIntBuffer((IntBuffer) buf);
+ } else {
+ throw new UnsupportedOperationException("Index buffer type unsupported: "+ buf.getClass());
+ }
+ }
+
+ /**
+ * Extracts the vertex attributes from the given mesh into
+ * this mesh, by using this mesh's {@link #getIndexBuffer() index buffer}
+ * to index into the attributes of the other mesh.
+ * Note that this will also change this mesh's index buffer so that
+ * the references to the vertex data match the new indices.
+ *
+ * @param other The mesh to extract the vertex data from
+ */
+ public void extractVertexData(Mesh other) {
+ // Determine the number of unique vertices need to
+ // be created. Also determine the mappings
+ // between old indices to new indices (since we avoid duplicating
+ // vertices, this is a map and not an array).
+ VertexBuffer oldIdxBuf = getBuffer(Type.Index);
+ IndexBuffer indexBuf = getIndexBuffer();
+ int numIndices = indexBuf.size();
+
+ IntMap<Integer> oldIndicesToNewIndices = new IntMap<Integer>(numIndices);
+ ArrayList<Integer> newIndicesToOldIndices = new ArrayList<Integer>();
+ int newIndex = 0;
+
+ for (int i = 0; i < numIndices; i++) {
+ int oldIndex = indexBuf.get(i);
+
+ if (!oldIndicesToNewIndices.containsKey(oldIndex)) {
+ // this vertex has not been added, so allocate a
+ // new index for it and add it to the map
+ oldIndicesToNewIndices.put(oldIndex, newIndex);
+ newIndicesToOldIndices.add(oldIndex);
+
+ // increment to have the next index
+ newIndex++;
+ }
+ }
+
+ // Number of unique verts to be created now available
+ int newNumVerts = newIndicesToOldIndices.size();
+
+ if (newIndex != newNumVerts) {
+ throw new AssertionError();
+ }
+
+ // Create the new index buffer.
+ // Do not overwrite the old one because we might be able to
+ // convert from int index buffer to short index buffer
+ IndexBuffer newIndexBuf;
+ if (newNumVerts >= 65536) {
+ newIndexBuf = new IndexIntBuffer(BufferUtils.createIntBuffer(numIndices));
+ } else {
+ newIndexBuf = new IndexShortBuffer(BufferUtils.createShortBuffer(numIndices));
+ }
+
+ for (int i = 0; i < numIndices; i++) {
+ // Map the old indices to the new indices
+ int oldIndex = indexBuf.get(i);
+ newIndex = oldIndicesToNewIndices.get(oldIndex);
+
+ newIndexBuf.put(i, newIndex);
+ }
+
+ VertexBuffer newIdxBuf = new VertexBuffer(Type.Index);
+ newIdxBuf.setupData(oldIdxBuf.getUsage(),
+ oldIdxBuf.getNumComponents(),
+ newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort,
+ newIndexBuf.getBuffer());
+ clearBuffer(Type.Index);
+ setBuffer(newIdxBuf);
+
+ // Now, create the vertex buffers
+ SafeArrayList<VertexBuffer> oldVertexData = other.getBufferList();
+ for (VertexBuffer oldVb : oldVertexData) {
+ if (oldVb.getBufferType() == VertexBuffer.Type.Index) {
+ // ignore the index buffer
+ continue;
+ }
+
+ // Create a new vertex buffer with similar configuration, but
+ // with the capacity of number of unique vertices
+ Buffer buffer = VertexBuffer.createBuffer(oldVb.getFormat(), oldVb.getNumComponents(), newNumVerts);
+
+ VertexBuffer newVb = new VertexBuffer(oldVb.getBufferType());
+ newVb.setNormalized(oldVb.isNormalized());
+ newVb.setupData(oldVb.getUsage(), oldVb.getNumComponents(), oldVb.getFormat(), buffer);
+
+ // Copy the vertex data from the old buffer into the new buffer
+ for (int i = 0; i < newNumVerts; i++) {
+ int oldIndex = newIndicesToOldIndices.get(i);
+
+ // Copy the vertex attribute from the old index
+ // to the new index
+ oldVb.copyElement(oldIndex, newVb, i);
+ }
+
+ // Set the buffer on the mesh
+ clearBuffer(newVb.getBufferType());
+ setBuffer(newVb);
+ }
+
+ // Copy max weights per vertex as well
+ setMaxNumWeights(other.getMaxNumWeights());
+
+ // The data has been copied over, update informations
+ updateCounts();
+ updateBound();
+ }
+
+ /**
+ * Scales the texture coordinate buffer on this mesh by the given
+ * scale factor.
+ * <p>
+ * Note that values above 1 will cause the
+ * texture to tile, while values below 1 will cause the texture
+ * to stretch.
+ * </p>
+ *
+ * @param scaleFactor The scale factor to scale by. Every texture
+ * coordinate is multiplied by this vector to get the result.
+ *
+ * @throws IllegalStateException If there's no texture coordinate
+ * buffer on the mesh
+ * @throws UnsupportedOperationException If the texture coordinate
+ * buffer is not in 2D float format.
+ */
+ public void scaleTextureCoordinates(Vector2f scaleFactor){
+ VertexBuffer tc = getBuffer(Type.TexCoord);
+ if (tc == null)
+ throw new IllegalStateException("The mesh has no texture coordinates");
+
+ if (tc.getFormat() != VertexBuffer.Format.Float)
+ throw new UnsupportedOperationException("Only float texture coord format is supported");
+
+ if (tc.getNumComponents() != 2)
+ throw new UnsupportedOperationException("Only 2D texture coords are supported");
+
+ FloatBuffer fb = (FloatBuffer) tc.getData();
+ fb.clear();
+ for (int i = 0; i < fb.capacity() / 2; i++){
+ float x = fb.get();
+ float y = fb.get();
+ fb.position(fb.position()-2);
+ x *= scaleFactor.getX();
+ y *= scaleFactor.getY();
+ fb.put(x).put(y);
+ }
+ fb.clear();
+ tc.updateData(fb);
+ }
+
+ /**
+ * Updates the bounding volume of this mesh.
+ * The method does nothing if the mesh has no {@link Type#Position} buffer.
+ * It is expected that the position buffer is a float buffer with 3 components.
+ */
+ public void updateBound(){
+ VertexBuffer posBuf = getBuffer(VertexBuffer.Type.Position);
+ if (meshBound != null && posBuf != null){
+ meshBound.computeFromPoints((FloatBuffer)posBuf.getData());
+ }
+ }
+
+ /**
+ * Returns the {@link BoundingVolume} of this Mesh.
+ * By default the bounding volume is a {@link BoundingBox}.
+ *
+ * @return the bounding volume of this mesh
+ */
+ public BoundingVolume getBound() {
+ return meshBound;
+ }
+
+ /**
+ * Sets the {@link BoundingVolume} for this Mesh.
+ * The bounding volume is recomputed by calling {@link #updateBound() }.
+ *
+ * @param modelBound The model bound to set
+ */
+ public void setBound(BoundingVolume modelBound) {
+ meshBound = modelBound;
+ }
+
+ /**
+ * Returns a map of all {@link VertexBuffer vertex buffers} on this Mesh.
+ * The integer key for the map is the {@link Enum#ordinal() ordinal}
+ * of the vertex buffer's {@link Type}.
+ * Note that the returned map is a reference to the map used internally,
+ * modifying it will cause undefined results.
+ *
+ * @return map of vertex buffers on this mesh.
+ */
+ public IntMap<VertexBuffer> getBuffers(){
+ return buffers;
+ }
+
+ /**
+ * Returns a list of all {@link VertexBuffer vertex buffers} on this Mesh.
+ * Using a list instead an IntMap via the {@link #getBuffers() } method is
+ * better for iteration as there's no need to create an iterator instance.
+ * Note that the returned list is a reference to the list used internally,
+ * modifying it will cause undefined results.
+ *
+ * @return list of vertex buffers on this mesh.
+ */
+ public SafeArrayList<VertexBuffer> getBufferList(){
+ return buffersList;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule out = ex.getCapsule(this);
+
+// HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
+// for (Entry<VertexBuffer> buf : buffers){
+// if (buf.getValue() != null)
+// map.put(buf.getKey()+"a", buf.getValue());
+// }
+// out.writeStringSavableMap(map, "buffers", null);
+
+ out.write(meshBound, "modelBound", null);
+ out.write(vertCount, "vertCount", -1);
+ out.write(elementCount, "elementCount", -1);
+ out.write(maxNumWeights, "max_num_weights", -1);
+ out.write(mode, "mode", Mode.Triangles);
+ out.write(collisionTree, "collisionTree", null);
+ out.write(elementLengths, "elementLengths", null);
+ out.write(modeStart, "modeStart", null);
+ out.write(pointSize, "pointSize", 1f);
+
+ out.writeIntSavableMap(buffers, "buffers", null);
+ out.write(lodLevels, "lodLevels", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ meshBound = (BoundingVolume) in.readSavable("modelBound", null);
+ vertCount = in.readInt("vertCount", -1);
+ elementCount = in.readInt("elementCount", -1);
+ maxNumWeights = in.readInt("max_num_weights", -1);
+ mode = in.readEnum("mode", Mode.class, Mode.Triangles);
+ elementLengths = in.readIntArray("elementLengths", null);
+ modeStart = in.readIntArray("modeStart", null);
+ collisionTree = (BIHTree) in.readSavable("collisionTree", null);
+ elementLengths = in.readIntArray("elementLengths", null);
+ modeStart = in.readIntArray("modeStart", null);
+ pointSize = in.readFloat("pointSize", 1f);
+
+// in.readStringSavableMap("buffers", null);
+ buffers = (IntMap<VertexBuffer>) in.readIntSavableMap("buffers", null);
+ for (Entry<VertexBuffer> entry : buffers){
+ buffersList.add(entry.getValue());
+ }
+
+ Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null);
+ if (lodLevelsSavable != null) {
+ lodLevels = new VertexBuffer[lodLevelsSavable.length];
+ System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length);
+ }
+ }
+
+}