/* * 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.export.*; import com.jme3.math.FastMath; import com.jme3.renderer.Renderer; import com.jme3.util.BufferUtils; import com.jme3.util.NativeObject; import java.io.IOException; import java.nio.*; /** * A <code>VertexBuffer</code> contains a particular type of geometry * data used by {@link Mesh}es. Every VertexBuffer set on a <code>Mesh</code> * is sent as an attribute to the vertex shader to be processed. * <p> * Several terms are used throughout the javadoc for this class, explanation: * <ul> * <li>Element - A single element is the largest individual object * inside a VertexBuffer. E.g. if the VertexBuffer is used to store 3D position * data, then an element will be a single 3D vector.</li> * <li>Component - A component represents the parts inside an element. * For a 3D vector, a single component is one of the dimensions, X, Y or Z.</li> * </ul> */ public class VertexBuffer extends NativeObject implements Savable, Cloneable { /** * Type of buffer. Specifies the actual attribute it defines. */ public static enum Type { /** * Position of the vertex (3 floats) */ Position, /** * The size of the point when using point buffers (float). */ Size, /** * Normal vector, normalized (3 floats). */ Normal, /** * Texture coordinate (2 float) */ TexCoord, /** * Color and Alpha (4 floats) */ Color, /** * Tangent vector, normalized (4 floats) (x,y,z,w). The w component is * called the binormal parity, is not normalized, and is either 1f or * -1f. It's used to compute the direction on the binormal vector on the * GPU at render time. */ Tangent, /** * Binormal vector, normalized (3 floats, optional) */ Binormal, /** * Specifies the source data for various vertex buffers * when interleaving is used. By default the format is * byte. */ InterleavedData, /** * Do not use. */ @Deprecated Reserved0, /** * Specifies the index buffer, must contain integer data * (ubyte, ushort, or uint). */ Index, /** * Initial vertex position, used with animation. * Should have the same format and size as {@link Type#Position}. * If used with software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap. */ BindPosePosition, /** * Initial vertex normals, used with animation. * Should have the same format and size as {@link Type#Normal}. * If used with software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap. */ BindPoseNormal, /** * Bone weights, used with animation (4 floats). * Only used for software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap. */ BoneWeight, /** * Bone indices, used with animation (4 ubytes). * Only used for software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap as a ubytes buffer. */ BoneIndex, /** * Texture coordinate #2 */ TexCoord2, /** * Texture coordinate #3 */ TexCoord3, /** * Texture coordinate #4 */ TexCoord4, /** * Texture coordinate #5 */ TexCoord5, /** * Texture coordinate #6 */ TexCoord6, /** * Texture coordinate #7 */ TexCoord7, /** * Texture coordinate #8 */ TexCoord8, /** * Initial vertex tangents, used with animation. * Should have the same format and size as {@link Type#Tangent}. * If used with software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap. */ BindPoseTangent, /** * Bone weights, used with animation (4 floats). * for Hardware Skinning only */ HWBoneWeight, /** * Bone indices, used with animation (4 ubytes). * for Hardware Skinning only * either an int or float buffer due to shader attribute types restrictions. */ HWBoneIndex, /** * Information about this instance. * * Format should be {@link Format#Float} and number of components * should be 16. */ InstanceData } /** * The usage of the VertexBuffer, specifies how often the buffer * is used. This can determine if a vertex buffer is placed in VRAM * or held in video memory, but no guarantees are made- it's only a hint. */ public static enum Usage { /** * Mesh data is sent once and very rarely updated. */ Static, /** * Mesh data is updated occasionally (once per frame or less). */ Dynamic, /** * Mesh data is updated every frame. */ Stream, /** * Mesh data is <em>not</em> sent to GPU at all. It is only * used by the CPU. */ CpuOnly; } /** * Specifies format of the data stored in the buffer. * This should directly correspond to the buffer's class, for example, * an {@link Format#UnsignedShort} formatted buffer should use the * class {@link ShortBuffer} (e.g. the closest resembling type). * For the {@link Format#Half} type, {@link ByteBuffer}s should * be used. */ public static enum Format { /** * Half precision floating point. * 2 bytes, signed. */ Half(2), /** * Single precision floating point. * 4 bytes, signed */ Float(4), /** * Double precision floating point. * 8 bytes, signed. May not * be supported by all GPUs. */ Double(8), /** * 1 byte integer, signed. */ Byte(1), /** * 1 byte integer, unsigned. */ UnsignedByte(1), /** * 2 byte integer, signed. */ Short(2), /** * 2 byte integer, unsigned. */ UnsignedShort(2), /** * 4 byte integer, signed. */ Int(4), /** * 4 byte integer, unsigned. */ UnsignedInt(4); private int componentSize = 0; Format(int componentSize){ this.componentSize = componentSize; } /** * Returns the size in bytes of this data type. * * @return Size in bytes of this data type. */ public int getComponentSize(){ return componentSize; } } protected int offset = 0; protected int lastLimit = 0; protected int stride = 0; protected int components = 0; /** * derived from components * format.getComponentSize() */ protected transient int componentsLength = 0; protected Buffer data = null; protected Usage usage; protected Type bufType; protected Format format; protected boolean normalized = false; protected int instanceSpan = 0; protected transient boolean dataSizeChanged = false; /** * Creates an empty, uninitialized buffer. * Must call setupData() to initialize. */ public VertexBuffer(Type type){ super(); this.bufType = type; } /** * Serialization only. Do not use. */ public VertexBuffer(){ super(); } protected VertexBuffer(int id){ super(id); } public boolean invariant() { // Does the VB hold any data? if (data == null) { throw new AssertionError(); } // Position must be 0. if (data.position() != 0) { throw new AssertionError(); } // Is the size of the VB == 0? if (data.limit() == 0) { throw new AssertionError(); } // Does offset exceed buffer limit or negative? if (offset > data.limit() || offset < 0) { throw new AssertionError(); } // Are components between 1 and 4? // Are components between 1 and 4 and not InstanceData? if (bufType != Type.InstanceData) { if (components < 1 || components > 4) { throw new AssertionError(); } } // Does usage comply with buffer directness? //if (usage == Usage.CpuOnly && data.isDirect()) { // throw new AssertionError(); /*} else*/ if (usage != Usage.CpuOnly && !data.isDirect()) { throw new AssertionError(); } // Double/Char/Long buffers are not supported for VertexBuffers. // For the rest, ensure they comply with the "Format" value. if (data instanceof DoubleBuffer) { throw new AssertionError(); } else if (data instanceof CharBuffer) { throw new AssertionError(); } else if (data instanceof LongBuffer) { throw new AssertionError(); } else if (data instanceof FloatBuffer && format != Format.Float) { throw new AssertionError(); } else if (data instanceof IntBuffer && format != Format.Int && format != Format.UnsignedInt) { throw new AssertionError(); } else if (data instanceof ShortBuffer && format != Format.Short && format != Format.UnsignedShort) { throw new AssertionError(); } else if (data instanceof ByteBuffer && format != Format.Byte && format != Format.UnsignedByte) { throw new AssertionError(); } return true; } /** * @return The offset after which the data is sent to the GPU. * * @see #setOffset(int) */ public int getOffset() { return offset; } /** * @param offset Specify the offset (in bytes) from the start of the buffer * after which the data is sent to the GPU. */ public void setOffset(int offset) { this.offset = offset; } /** * @return The stride (in bytes) for the data. * * @see #setStride(int) */ public int getStride() { return stride; } /** * Set the stride (in bytes) for the data. * <p> * If the data is packed in the buffer, then stride is 0, if there's other * data that is between the current component and the next component in the * buffer, then this specifies the size in bytes of that additional data. * * @param stride the stride (in bytes) for the data */ public void setStride(int stride) { this.stride = stride; } /** * Returns the raw internal data buffer used by this VertexBuffer. * This buffer is not safe to call from multiple threads since buffers * have their own internal position state that cannot be shared. * Call getData().duplicate(), getData().asReadOnlyBuffer(), or * the more convenient getDataReadOnly() if the buffer may be accessed * from multiple threads. * * @return A native buffer, in the specified {@link Format format}. */ public Buffer getData(){ return data; } /** * Returns a safe read-only version of this VertexBuffer's data. The * contents of the buffer will reflect whatever changes are made on * other threads (eventually) but these should not be used in that way. * This method provides a read-only buffer that is safe to _read_ from * a separate thread since it has its own book-keeping state (position, limit, etc.) * * @return A rewound native buffer in the specified {@link Format format} * that is safe to read from a separate thread from other readers. */ public Buffer getDataReadOnly() { if (data == null) { return null; } // Create a read-only duplicate(). Note: this does not copy // the underlying memory, it just creates a new read-only wrapper // with its own buffer position state. // Unfortunately, this is not 100% straight forward since Buffer // does not have an asReadOnlyBuffer() method. Buffer result; if( data instanceof ByteBuffer ) { result = ((ByteBuffer)data).asReadOnlyBuffer(); } else if( data instanceof FloatBuffer ) { result = ((FloatBuffer)data).asReadOnlyBuffer(); } else if( data instanceof ShortBuffer ) { result = ((ShortBuffer)data).asReadOnlyBuffer(); } else if( data instanceof IntBuffer ) { result = ((IntBuffer)data).asReadOnlyBuffer(); } else { throw new UnsupportedOperationException( "Cannot get read-only view of buffer type:" + data ); } // Make sure the caller gets a consistent view since we may // have grabbed this buffer while another thread was reading // the raw data. result.rewind(); return result; } /** * @return The usage of this buffer. See {@link Usage} for more * information. */ public Usage getUsage(){ return usage; } /** * @param usage The usage of this buffer. See {@link Usage} for more * information. */ public void setUsage(Usage usage){ // if (id != -1) // throw new UnsupportedOperationException("Data has already been sent. Cannot set usage."); this.usage = usage; this.setUpdateNeeded(); } /** * @param normalized Set to true if integer components should be converted * from their maximal range into the range 0.0 - 1.0 when converted to * a floating-point value for the shader. * E.g. if the {@link Format} is {@link Format#UnsignedInt}, then * the components will be converted to the range 0.0 - 1.0 by dividing * every integer by 2^32. */ public void setNormalized(boolean normalized){ this.normalized = normalized; } /** * @return True if integer components should be converted to the range 0-1. * @see VertexBuffer#setNormalized(boolean) */ public boolean isNormalized(){ return normalized; } /** * Sets the instanceSpan to 1 or 0 depending on * the value of instanced and the existing value of * instanceSpan. */ public void setInstanced(boolean instanced) { if( instanced && instanceSpan == 0 ) { instanceSpan = 1; } else if( !instanced ) { instanceSpan = 0; } } /** * Returns true if instanceSpan is more than 0 indicating * that this vertex buffer contains per-instance data. */ public boolean isInstanced() { return instanceSpan > 0; } /** * Sets how this vertex buffer matches with rendered instances * where 0 means no instancing at all, ie: all elements are * per vertex. If set to 1 then each element goes with one * instance. If set to 2 then each element goes with two * instances and so on. */ public void setInstanceSpan(int i) { this.instanceSpan = i; } public int getInstanceSpan() { return instanceSpan; } /** * @return The type of information that this buffer has. */ public Type getBufferType(){ return bufType; } /** * @return The {@link Format format}, or data type of the data. */ public Format getFormat(){ return format; } /** * @return The number of components of the given {@link Format format} per * element. */ public int getNumComponents(){ return components; } /** * @return The total number of data elements in the data buffer. */ public int getNumElements(){ int elements = data.limit() / components; if (format == Format.Half) elements /= 2; return elements; } /** * Returns the number of 'instances' in this VertexBuffer. This * is dependent on the current instanceSpan. When instanceSpan * is 0 then 'instances' is 1. Otherwise, instances is elements * * instanceSpan. It is possible to render a mesh with more instances * but the instance data begins to repeat. */ public int getBaseInstanceCount() { if( instanceSpan == 0 ) { return 1; } return getNumElements() * instanceSpan; } /** * Called to initialize the data in the <code>VertexBuffer</code>. Must only * be called once. * * @param usage The usage for the data, or how often will the data * be updated per frame. See the {@link Usage} enum. * @param components The number of components per element. * @param format The {@link Format format}, or data-type of a single * component. * @param data A native buffer, the format of which matches the {@link Format} * argument. */ public void setupData(Usage usage, int components, Format format, Buffer data){ if (id != -1) throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again."); if (usage == null || format == null || data == null) throw new IllegalArgumentException("None of the arguments can be null"); if (data.isReadOnly()) throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." ); if (bufType != Type.InstanceData) { if (components < 1 || components > 4) { throw new IllegalArgumentException("components must be between 1 and 4"); } } this.data = data; this.components = components; this.usage = usage; this.format = format; this.componentsLength = components * format.getComponentSize(); this.lastLimit = data.limit(); setUpdateNeeded(); } /** * Called to update the data in the buffer with new data. Can only * be called after {@link VertexBuffer#setupData(com.jme3.scene.VertexBuffer.Usage, int, com.jme3.scene.VertexBuffer.Format, java.nio.Buffer) } * has been called. Note that it is fine to call this method on the * data already set, e.g. vb.updateData(vb.getData()), this will just * set the proper update flag indicating the data should be sent to the GPU * again. * <p> * It is allowed to specify a buffer with different capacity than the * originally set buffer, HOWEVER, if you do so, you must * call Mesh.updateCounts() otherwise bizarre errors can occur. * * @param data The data buffer to set */ public void updateData(Buffer data){ if (id != -1){ // request to update data is okay } // Check if the data buffer is read-only which is a sign // of a bug on the part of the caller if (data != null && data.isReadOnly()) { throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." ); } // will force renderer to call glBufferData again if (data != null && (this.data.getClass() != data.getClass() || data.limit() != lastLimit)){ dataSizeChanged = true; lastLimit = data.limit(); } this.data = data; setUpdateNeeded(); } /** * Returns true if the data size of the VertexBuffer has changed. * Internal use only. * @return true if the data size has changed */ public boolean hasDataSizeChanged() { return dataSizeChanged; } @Override public void clearUpdateNeeded(){ super.clearUpdateNeeded(); dataSizeChanged = false; } /** * Converts single floating-point data to {@link Format#Half half} floating-point data. */ public void convertToHalf(){ if (id != -1) throw new UnsupportedOperationException("Data has already been sent."); if (format != Format.Float) throw new IllegalStateException("Format must be float!"); int numElements = data.limit() / components; format = Format.Half; this.componentsLength = components * format.getComponentSize(); ByteBuffer halfData = BufferUtils.createByteBuffer(componentsLength * numElements); halfData.rewind(); FloatBuffer floatData = (FloatBuffer) data; floatData.rewind(); for (int i = 0; i < floatData.limit(); i++){ float f = floatData.get(i); short half = FastMath.convertFloatToHalf(f); halfData.putShort(half); } this.data = halfData; setUpdateNeeded(); dataSizeChanged = true; } /** * Reduces the capacity of the buffer to the given amount * of elements, any elements at the end of the buffer are truncated * as necessary. * * @param numElements The number of elements to reduce to. */ public void compact(int numElements){ int total = components * numElements; data.clear(); switch (format){ case Byte: case UnsignedByte: case Half: ByteBuffer bbuf = (ByteBuffer) data; bbuf.limit(total); ByteBuffer bnewBuf = BufferUtils.createByteBuffer(total); bnewBuf.put(bbuf); data = bnewBuf; break; case Short: case UnsignedShort: ShortBuffer sbuf = (ShortBuffer) data; sbuf.limit(total); ShortBuffer snewBuf = BufferUtils.createShortBuffer(total); snewBuf.put(sbuf); data = snewBuf; break; case Int: case UnsignedInt: IntBuffer ibuf = (IntBuffer) data; ibuf.limit(total); IntBuffer inewBuf = BufferUtils.createIntBuffer(total); inewBuf.put(ibuf); data = inewBuf; break; case Float: FloatBuffer fbuf = (FloatBuffer) data; fbuf.limit(total); FloatBuffer fnewBuf = BufferUtils.createFloatBuffer(total); fnewBuf.put(fbuf); data = fnewBuf; break; default: throw new UnsupportedOperationException("Unrecognized buffer format: "+format); } data.clear(); setUpdateNeeded(); dataSizeChanged = true; } /** * Modify a component inside an element. * The <code>val</code> parameter must be in the buffer's format: * {@link Format}. * * @param elementIndex The element index to modify * @param componentIndex The component index to modify * @param val The value to set, either byte, short, int or float depending * on the {@link Format}. */ public void setElementComponent(int elementIndex, int componentIndex, Object val){ int inPos = elementIndex * components; int elementPos = componentIndex; if (format == Format.Half){ inPos *= 2; elementPos *= 2; } data.clear(); switch (format){ case Byte: case UnsignedByte: case Half: ByteBuffer bin = (ByteBuffer) data; bin.put(inPos + elementPos, (Byte)val); break; case Short: case UnsignedShort: ShortBuffer sin = (ShortBuffer) data; sin.put(inPos + elementPos, (Short)val); break; case Int: case UnsignedInt: IntBuffer iin = (IntBuffer) data; iin.put(inPos + elementPos, (Integer)val); break; case Float: FloatBuffer fin = (FloatBuffer) data; fin.put(inPos + elementPos, (Float)val); break; default: throw new UnsupportedOperationException("Unrecognized buffer format: "+format); } } /** * Get the component inside an element. * * @param elementIndex The element index * @param componentIndex The component index * @return The component, as one of the primitive types, byte, short, * int or float. */ public Object getElementComponent(int elementIndex, int componentIndex){ int inPos = elementIndex * components; int elementPos = componentIndex; if (format == Format.Half){ inPos *= 2; elementPos *= 2; } Buffer srcData = getDataReadOnly(); switch (format){ case Byte: case UnsignedByte: case Half: ByteBuffer bin = (ByteBuffer) srcData; return bin.get(inPos + elementPos); case Short: case UnsignedShort: ShortBuffer sin = (ShortBuffer) srcData; return sin.get(inPos + elementPos); case Int: case UnsignedInt: IntBuffer iin = (IntBuffer) srcData; return iin.get(inPos + elementPos); case Float: FloatBuffer fin = (FloatBuffer) srcData; return fin.get(inPos + elementPos); default: throw new UnsupportedOperationException("Unrecognized buffer format: "+format); } } /** * Copies a single element of data from this <code>VertexBuffer</code> * to the given output VertexBuffer. * * @param inIndex The input element index * @param outVb The buffer to copy to * @param outIndex The output element index * * @throws IllegalArgumentException If the formats of the buffers do not * match. */ public void copyElement(int inIndex, VertexBuffer outVb, int outIndex){ copyElements(inIndex, outVb, outIndex, 1); } /** * Copies a sequence of elements of data from this <code>VertexBuffer</code> * to the given output VertexBuffer. * * @param inIndex The input element index * @param outVb The buffer to copy to * @param outIndex The output element index * @param len The number of elements to copy * * @throws IllegalArgumentException If the formats of the buffers do not * match. */ public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){ if (outVb.format != format || outVb.components != components) throw new IllegalArgumentException("Buffer format mismatch. Cannot copy"); int inPos = inIndex * components; int outPos = outIndex * components; int elementSz = components; if (format == Format.Half){ // because half is stored as bytebuf but its 2 bytes long inPos *= 2; outPos *= 2; elementSz *= 2; } // Make sure to grab a read-only copy in case some other // thread is also accessing the buffer and messing with its // position() Buffer srcData = getDataReadOnly(); outVb.data.clear(); switch (format){ case Byte: case UnsignedByte: case Half: ByteBuffer bin = (ByteBuffer) srcData; ByteBuffer bout = (ByteBuffer) outVb.data; bin.position(inPos).limit(inPos + elementSz * len); bout.position(outPos).limit(outPos + elementSz * len); bout.put(bin); break; case Short: case UnsignedShort: ShortBuffer sin = (ShortBuffer) srcData; ShortBuffer sout = (ShortBuffer) outVb.data; sin.position(inPos).limit(inPos + elementSz * len); sout.position(outPos).limit(outPos + elementSz * len); sout.put(sin); break; case Int: case UnsignedInt: IntBuffer iin = (IntBuffer) srcData; IntBuffer iout = (IntBuffer) outVb.data; iin.position(inPos).limit(inPos + elementSz * len); iout.position(outPos).limit(outPos + elementSz * len); iout.put(iin); break; case Float: FloatBuffer fin = (FloatBuffer) srcData; FloatBuffer fout = (FloatBuffer) outVb.data; fin.position(inPos).limit(inPos + elementSz * len); fout.position(outPos).limit(outPos + elementSz * len); fout.put(fin); break; default: throw new UnsupportedOperationException("Unrecognized buffer format: "+format); } // Clear the output buffer to rewind it and reset its // limit from where we shortened it above. outVb.data.clear(); } /** * Creates a {@link Buffer} that satisfies the given type and size requirements * of the parameters. The buffer will be of the type specified by * {@link Format format} and would be able to contain the given number * of elements with the given number of components in each element. */ public static Buffer createBuffer(Format format, int components, int numElements){ if (components < 1 || components > 4) throw new IllegalArgumentException("Num components must be between 1 and 4"); int total = numElements * components; switch (format){ case Byte: case UnsignedByte: return BufferUtils.createByteBuffer(total); case Half: return BufferUtils.createByteBuffer(total * 2); case Short: case UnsignedShort: return BufferUtils.createShortBuffer(total); case Int: case UnsignedInt: return BufferUtils.createIntBuffer(total); case Float: return BufferUtils.createFloatBuffer(total); case Double: return BufferUtils.createDoubleBuffer(total); default: throw new UnsupportedOperationException("Unrecoginized buffer format: "+format); } } /** * Creates a deep clone of the {@link VertexBuffer}. * * @return Deep clone of this buffer */ @Override public VertexBuffer clone(){ // NOTE: Superclass GLObject automatically creates shallow clone // e.g re-use ID. VertexBuffer vb = (VertexBuffer) super.clone(); vb.handleRef = new Object(); vb.id = -1; if (data != null) { // Make sure to pass a read-only buffer to clone so that // the position information doesn't get clobbered by another // reading thread during cloning (and vice versa) since this is // a purely read-only operation. vb.updateData(BufferUtils.clone(getDataReadOnly())); } return vb; } /** * Creates a deep clone of this VertexBuffer but overrides the * {@link Type}. * * @param overrideType The type of the cloned VertexBuffer * @return A deep clone of the buffer */ public VertexBuffer clone(Type overrideType){ VertexBuffer vb = new VertexBuffer(overrideType); vb.components = components; vb.componentsLength = componentsLength; // Make sure to pass a read-only buffer to clone so that // the position information doesn't get clobbered by another // reading thread during cloning (and vice versa) since this is // a purely read-only operation. vb.data = BufferUtils.clone(getDataReadOnly()); vb.format = format; vb.handleRef = new Object(); vb.id = -1; vb.normalized = normalized; vb.instanceSpan = instanceSpan; vb.offset = offset; vb.stride = stride; vb.updateNeeded = true; vb.usage = usage; return vb; } @Override public String toString(){ String dataTxt = null; if (data != null){ dataTxt = ", elements="+data.limit(); } return getClass().getSimpleName() + "[fmt="+format.name() +", type="+bufType.name() +", usage="+usage.name() +dataTxt+"]"; } @Override public void resetObject() { // assert this.id != -1; this.id = -1; setUpdateNeeded(); } @Override public void deleteObject(Object rendererObject) { ((Renderer)rendererObject).deleteBuffer(this); } @Override protected void deleteNativeBuffers() { if (data != null) { BufferUtils.destroyDirectBuffer(data); } } @Override public NativeObject createDestructableClone(){ return new VertexBuffer(id); } @Override public long getUniqueId() { return ((long)OBJTYPE_VERTEXBUFFER << 32) | ((long)id); } @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(components, "components", 0); oc.write(usage, "usage", Usage.Dynamic); oc.write(bufType, "buffer_type", null); oc.write(format, "format", Format.Float); oc.write(normalized, "normalized", false); oc.write(offset, "offset", 0); oc.write(stride, "stride", 0); oc.write(instanceSpan, "instanceSpan", 0); String dataName = "data" + format.name(); Buffer roData = getDataReadOnly(); switch (format){ case Float: oc.write((FloatBuffer) roData, dataName, null); break; case Short: case UnsignedShort: oc.write((ShortBuffer) roData, dataName, null); break; case UnsignedByte: case Byte: case Half: oc.write((ByteBuffer) roData, dataName, null); break; case Int: case UnsignedInt: oc.write((IntBuffer) roData, dataName, null); break; default: throw new IOException("Unsupported export buffer format: "+format); } } @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); components = ic.readInt("components", 0); usage = ic.readEnum("usage", Usage.class, Usage.Dynamic); bufType = ic.readEnum("buffer_type", Type.class, null); format = ic.readEnum("format", Format.class, Format.Float); normalized = ic.readBoolean("normalized", false); offset = ic.readInt("offset", 0); stride = ic.readInt("stride", 0); instanceSpan = ic.readInt("instanceSpan", 0); componentsLength = components * format.getComponentSize(); String dataName = "data" + format.name(); switch (format){ case Float: data = ic.readFloatBuffer(dataName, null); break; case Short: case UnsignedShort: data = ic.readShortBuffer(dataName, null); break; case UnsignedByte: case Byte: case Half: data = ic.readByteBuffer(dataName, null); break; case Int: case UnsignedInt: data = ic.readIntBuffer(dataName, null); break; default: throw new IOException("Unsupported import buffer format: "+format); } } }