diff options
author | Scott Barta <sbarta@google.com> | 2012-03-01 12:35:35 -0800 |
---|---|---|
committer | Scott Barta <sbarta@google.com> | 2012-03-01 12:40:08 -0800 |
commit | 59b2e6871c65f58fdad78cd7229c292f6a177578 (patch) | |
tree | 2d4e7bfc05b93f40b34675d77e403dd1c25efafd /engine/src/core/com/jme3/scene/Mesh.java | |
parent | f9b30489e75ac1eabc365064959804e99534f7ab (diff) | |
download | jmonkeyengine-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.java | 1315 |
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); + } + } + +} |