/*
 * Copyright (c) 2009-2010 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.scene.mesh.IndexShortBuffer;
import com.jme3.scene.mesh.IndexIntBuffer;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.scene.mesh.IndexByteBuffer;
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.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.InputCapsule;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
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.mesh.VirtualIndexBuffer;
import com.jme3.scene.mesh.WrappedIndexBuffer;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;

/**
 * <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 ArrayList<VertexBuffer> buffersList = new ArrayList<VertexBuffer>(5);
    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 ArrayList<VertexBuffer>(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 ArrayList<VertexBuffer>();
            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#BindPosePosition} and {@link Type#BindPoseNormal} 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);
            }
        }
        return clone;
    }

    /**
     * Generates the {@link Type#BindPosePosition} and {@link Type#BindPoseNormal}
     * 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);
            }
        }
    }

    /**
     * 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.
     * 
     * @param mesh The mesh to read from
     * @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());
        }
    }

    /**
     * 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;
    }
    
    public ArrayList<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);
        }
    }

}
