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/util | |
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/util')
16 files changed, 4687 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/util/BufferUtils.java b/engine/src/core/com/jme3/util/BufferUtils.java new file mode 100644 index 0000000..f0cc698 --- /dev/null +++ b/engine/src/core/com/jme3/util/BufferUtils.java @@ -0,0 +1,1196 @@ +/* + * 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.util; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * <code>BufferUtils</code> is a helper class for generating nio buffers from + * jME data classes such as Vectors and ColorRGBA. + * + * @author Joshua Slack + * @version $Id: BufferUtils.java,v 1.16 2007/10/29 16:56:18 nca Exp $ + */ +public final class BufferUtils { + + private static final Map<Buffer, Object> trackingHash = Collections.synchronizedMap(new WeakHashMap<Buffer, Object>()); + private static final Object ref = new Object(); + + // Note: a WeakHashMap is really bad here since the hashCode() and + // equals() behavior of buffers will vary based on their contents. + // As it stands, put()'ing an empty buffer will wipe out the last + // empty buffer with the same size. So any tracked memory calculations + // could be lying. + // Besides, the hashmap behavior isn't even being used here and + // yet the expense is still incurred. For example, a newly allocated + // 10,000 byte buffer will iterate through the whole buffer of 0's + // to calculate the hashCode and then potentially do it again to + // calculate the equals()... which by the way is guaranteed for + // every empty buffer of an existing size since they will always produce + // the same hashCode(). + // It would be better to just keep a straight list of weak references + // and clean out the dead every time a new buffer is allocated. + // WeakHashMap is doing that anyway... so there is no extra expense + // incurred. + // Recommend a ConcurrentLinkedQueue of WeakReferences since it + // supports the threading semantics required with little extra overhead. + private static final boolean trackDirectMemory = false; + + /** + * Creates a clone of the given buffer. The clone's capacity is + * equal to the given buffer's limit. + * + * @param buf The buffer to clone + * @return The cloned buffer + */ + public static Buffer clone(Buffer buf) { + if (buf instanceof FloatBuffer) { + return clone((FloatBuffer) buf); + } else if (buf instanceof ShortBuffer) { + return clone((ShortBuffer) buf); + } else if (buf instanceof ByteBuffer) { + return clone((ByteBuffer) buf); + } else if (buf instanceof IntBuffer) { + return clone((IntBuffer) buf); + } else if (buf instanceof DoubleBuffer) { + return clone((DoubleBuffer) buf); + } else { + throw new UnsupportedOperationException(); + } + } + + private static void onBufferAllocated(Buffer buffer){ + /* + StackTraceElement[] stackTrace = new Throwable().getStackTrace(); + int initialIndex = 0; + + for (int i = 0; i < stackTrace.length; i++){ + if (!stackTrace[i].getClassName().equals(BufferUtils.class.getName())){ + initialIndex = i; + break; + } + } + + int allocated = buffer.capacity(); + int size = 0; + + if (buffer instanceof FloatBuffer){ + size = 4; + }else if (buffer instanceof ShortBuffer){ + size = 2; + }else if (buffer instanceof ByteBuffer){ + size = 1; + }else if (buffer instanceof IntBuffer){ + size = 4; + }else if (buffer instanceof DoubleBuffer){ + size = 8; + } + + allocated *= size; + + for (int i = initialIndex; i < stackTrace.length; i++){ + StackTraceElement element = stackTrace[i]; + if (element.getClassName().startsWith("java")){ + break; + } + + try { + Class clazz = Class.forName(element.getClassName()); + if (i == initialIndex){ + System.out.println(clazz.getSimpleName()+"."+element.getMethodName()+"():" + element.getLineNumber() + " allocated " + allocated); + }else{ + System.out.println(" at " + clazz.getSimpleName()+"."+element.getMethodName()+"()"); + } + } catch (ClassNotFoundException ex) { + } + }*/ + + if (trackDirectMemory){ + trackingHash.put(buffer, ref); + } + } + + /** + * Generate a new FloatBuffer using the given array of Vector3f objects. + * The FloatBuffer will be 3 * data.length long and contain the vector data + * as data[0].x, data[0].y, data[0].z, data[1].x... etc. + * + * @param data array of Vector3f objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(Vector3f... data) { + if (data == null) { + return null; + } + FloatBuffer buff = createFloatBuffer(3 * data.length); + for (int x = 0; x < data.length; x++) { + if (data[x] != null) { + buff.put(data[x].x).put(data[x].y).put(data[x].z); + } else { + buff.put(0).put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Generate a new FloatBuffer using the given array of Quaternion objects. + * The FloatBuffer will be 4 * data.length long and contain the vector data. + * + * @param data array of Quaternion objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(Quaternion... data) { + if (data == null) { + return null; + } + FloatBuffer buff = createFloatBuffer(4 * data.length); + for (int x = 0; x < data.length; x++) { + if (data[x] != null) { + buff.put(data[x].getX()).put(data[x].getY()).put(data[x].getZ()).put(data[x].getW()); + } else { + buff.put(0).put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Generate a new FloatBuffer using the given array of float primitives. + * @param data array of float primitives to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(float... data) { + if (data == null) { + return null; + } + FloatBuffer buff = createFloatBuffer(data.length); + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified + * number of Vector3f object data. + * + * @param vertices + * number of vertices that need to be held by the newly created + * buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector3Buffer(int vertices) { + FloatBuffer vBuff = createFloatBuffer(3 * vertices); + return vBuff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified + * number of Vector3f object data only if the given buffer if not already + * the right size. + * + * @param buf + * the buffer to first check and rewind + * @param vertices + * number of vertices that need to be held by the newly created + * buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector3Buffer(FloatBuffer buf, int vertices) { + if (buf != null && buf.limit() == 3 * vertices) { + buf.rewind(); + return buf; + } + + return createFloatBuffer(3 * vertices); + } + + /** + * Sets the data contained in the given color into the FloatBuffer at the + * specified index. + * + * @param color + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the postion to place the data; in terms of colors not floats + */ + public static void setInBuffer(ColorRGBA color, FloatBuffer buf, + int index) { + buf.position(index * 4); + buf.put(color.r); + buf.put(color.g); + buf.put(color.b); + buf.put(color.a); + } + + /** + * Sets the data contained in the given quaternion into the FloatBuffer at the + * specified index. + * + * @param quat + * the {@link Quaternion} to insert + * @param buf + * the buffer to insert into + * @param index + * the postion to place the data; in terms of quaternions not floats + */ + public static void setInBuffer(Quaternion quat, FloatBuffer buf, + int index) { + buf.position(index * 4); + buf.put(quat.getX()); + buf.put(quat.getY()); + buf.put(quat.getZ()); + buf.put(quat.getW()); + } + + /** + * Sets the data contained in the given Vector3F into the FloatBuffer at the + * specified index. + * + * @param vector + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the postion to place the data; in terms of vectors not floats + */ + public static void setInBuffer(Vector3f vector, FloatBuffer buf, int index) { + if (buf == null) { + return; + } + if (vector == null) { + buf.put(index * 3, 0); + buf.put((index * 3) + 1, 0); + buf.put((index * 3) + 2, 0); + } else { + buf.put(index * 3, vector.x); + buf.put((index * 3) + 1, vector.y); + buf.put((index * 3) + 2, vector.z); + } + } + + /** + * Updates the values of the given vector from the specified buffer at the + * index provided. + * + * @param vector + * the vector to set data on + * @param buf + * the buffer to read from + * @param index + * the position (in terms of vectors, not floats) to read from + * the buf + */ + public static void populateFromBuffer(Vector3f vector, FloatBuffer buf, int index) { + vector.x = buf.get(index * 3); + vector.y = buf.get(index * 3 + 1); + vector.z = buf.get(index * 3 + 2); + } + + /** + * Generates a Vector3f array from the given FloatBuffer. + * + * @param buff + * the FloatBuffer to read from + * @return a newly generated array of Vector3f objects + */ + public static Vector3f[] getVector3Array(FloatBuffer buff) { + buff.clear(); + Vector3f[] verts = new Vector3f[buff.limit() / 3]; + for (int x = 0; x < verts.length; x++) { + Vector3f v = new Vector3f(buff.get(), buff.get(), buff.get()); + verts[x] = v; + } + return verts; + } + + /** + * Copies a Vector3f from one position in the buffer to another. The index + * values are in terms of vector number (eg, vector number 0 is postions 0-2 + * in the FloatBuffer.) + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the index of the vector to copy + * @param toPos + * the index to copy the vector to + */ + public static void copyInternalVector3(FloatBuffer buf, int fromPos, int toPos) { + copyInternal(buf, fromPos * 3, toPos * 3, 3); + } + + /** + * Normalize a Vector3f in-buffer. + * + * @param buf + * the buffer to find the Vector3f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to normalize + */ + public static void normalizeVector3(FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector3f tempVec3 = vars.vect1; + populateFromBuffer(tempVec3, buf, index); + tempVec3.normalizeLocal(); + setInBuffer(tempVec3, buf, index); + vars.release(); + } + + /** + * Add to a Vector3f in-buffer. + * + * @param toAdd + * the vector to add from + * @param buf + * the buffer to find the Vector3f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to add to + */ + public static void addInBuffer(Vector3f toAdd, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector3f tempVec3 = vars.vect1; + populateFromBuffer(tempVec3, buf, index); + tempVec3.addLocal(toAdd); + setInBuffer(tempVec3, buf, index); + vars.release(); + } + + /** + * Multiply and store a Vector3f in-buffer. + * + * @param toMult + * the vector to multiply against + * @param buf + * the buffer to find the Vector3f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to multiply + */ + public static void multInBuffer(Vector3f toMult, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector3f tempVec3 = vars.vect1; + populateFromBuffer(tempVec3, buf, index); + tempVec3.multLocal(toMult); + setInBuffer(tempVec3, buf, index); + vars.release(); + } + + /** + * Checks to see if the given Vector3f is equals to the data stored in the + * buffer at the given data index. + * + * @param check + * the vector to check against - null will return false. + * @param buf + * the buffer to compare data with + * @param index + * the position (in terms of vectors, not floats) of the vector + * in the buffer to check against + * @return + */ + public static boolean equals(Vector3f check, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector3f tempVec3 = vars.vect1; + populateFromBuffer(tempVec3, buf, index); + boolean eq = tempVec3.equals(check); + vars.release(); + return eq; + } + + // // -- VECTOR2F METHODS -- //// + /** + * Generate a new FloatBuffer using the given array of Vector2f objects. + * The FloatBuffer will be 2 * data.length long and contain the vector data + * as data[0].x, data[0].y, data[1].x... etc. + * + * @param data array of Vector2f objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(Vector2f... data) { + if (data == null) { + return null; + } + FloatBuffer buff = createFloatBuffer(2 * data.length); + for (int x = 0; x < data.length; x++) { + if (data[x] != null) { + buff.put(data[x].x).put(data[x].y); + } else { + buff.put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified + * number of Vector2f object data. + * + * @param vertices + * number of vertices that need to be held by the newly created + * buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector2Buffer(int vertices) { + FloatBuffer vBuff = createFloatBuffer(2 * vertices); + return vBuff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified + * number of Vector2f object data only if the given buffer if not already + * the right size. + * + * @param buf + * the buffer to first check and rewind + * @param vertices + * number of vertices that need to be held by the newly created + * buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector2Buffer(FloatBuffer buf, int vertices) { + if (buf != null && buf.limit() == 2 * vertices) { + buf.rewind(); + return buf; + } + + return createFloatBuffer(2 * vertices); + } + + /** + * Sets the data contained in the given Vector2F into the FloatBuffer at the + * specified index. + * + * @param vector + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the postion to place the data; in terms of vectors not floats + */ + public static void setInBuffer(Vector2f vector, FloatBuffer buf, int index) { + buf.put(index * 2, vector.x); + buf.put((index * 2) + 1, vector.y); + } + + /** + * Updates the values of the given vector from the specified buffer at the + * index provided. + * + * @param vector + * the vector to set data on + * @param buf + * the buffer to read from + * @param index + * the position (in terms of vectors, not floats) to read from + * the buf + */ + public static void populateFromBuffer(Vector2f vector, FloatBuffer buf, int index) { + vector.x = buf.get(index * 2); + vector.y = buf.get(index * 2 + 1); + } + + /** + * Generates a Vector2f array from the given FloatBuffer. + * + * @param buff + * the FloatBuffer to read from + * @return a newly generated array of Vector2f objects + */ + public static Vector2f[] getVector2Array(FloatBuffer buff) { + buff.clear(); + Vector2f[] verts = new Vector2f[buff.limit() / 2]; + for (int x = 0; x < verts.length; x++) { + Vector2f v = new Vector2f(buff.get(), buff.get()); + verts[x] = v; + } + return verts; + } + + /** + * Copies a Vector2f from one position in the buffer to another. The index + * values are in terms of vector number (eg, vector number 0 is postions 0-1 + * in the FloatBuffer.) + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the index of the vector to copy + * @param toPos + * the index to copy the vector to + */ + public static void copyInternalVector2(FloatBuffer buf, int fromPos, int toPos) { + copyInternal(buf, fromPos * 2, toPos * 2, 2); + } + + /** + * Normalize a Vector2f in-buffer. + * + * @param buf + * the buffer to find the Vector2f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to normalize + */ + public static void normalizeVector2(FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + populateFromBuffer(tempVec2, buf, index); + tempVec2.normalizeLocal(); + setInBuffer(tempVec2, buf, index); + vars.release(); + } + + /** + * Add to a Vector2f in-buffer. + * + * @param toAdd + * the vector to add from + * @param buf + * the buffer to find the Vector2f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to add to + */ + public static void addInBuffer(Vector2f toAdd, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + populateFromBuffer(tempVec2, buf, index); + tempVec2.addLocal(toAdd); + setInBuffer(tempVec2, buf, index); + vars.release(); + } + + /** + * Multiply and store a Vector2f in-buffer. + * + * @param toMult + * the vector to multiply against + * @param buf + * the buffer to find the Vector2f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to multiply + */ + public static void multInBuffer(Vector2f toMult, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + populateFromBuffer(tempVec2, buf, index); + tempVec2.multLocal(toMult); + setInBuffer(tempVec2, buf, index); + vars.release(); + } + + /** + * Checks to see if the given Vector2f is equals to the data stored in the + * buffer at the given data index. + * + * @param check + * the vector to check against - null will return false. + * @param buf + * the buffer to compare data with + * @param index + * the position (in terms of vectors, not floats) of the vector + * in the buffer to check against + * @return + */ + public static boolean equals(Vector2f check, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + populateFromBuffer(tempVec2, buf, index); + boolean eq = tempVec2.equals(check); + vars.release(); + return eq; + } + + //// -- INT METHODS -- //// + /** + * Generate a new IntBuffer using the given array of ints. The IntBuffer + * will be data.length long and contain the int data as data[0], data[1]... + * etc. + * + * @param data + * array of ints to place into a new IntBuffer + */ + public static IntBuffer createIntBuffer(int... data) { + if (data == null) { + return null; + } + IntBuffer buff = createIntBuffer(data.length); + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + /** + * Create a new int[] array and populate it with the given IntBuffer's + * contents. + * + * @param buff + * the IntBuffer to read from + * @return a new int array populated from the IntBuffer + */ + public static int[] getIntArray(IntBuffer buff) { + if (buff == null) { + return null; + } + buff.clear(); + int[] inds = new int[buff.limit()]; + for (int x = 0; x < inds.length; x++) { + inds[x] = buff.get(); + } + return inds; + } + + /** + * Create a new float[] array and populate it with the given FloatBuffer's + * contents. + * + * @param buff + * the FloatBuffer to read from + * @return a new float array populated from the FloatBuffer + */ + public static float[] getFloatArray(FloatBuffer buff) { + if (buff == null) { + return null; + } + buff.clear(); + float[] inds = new float[buff.limit()]; + for (int x = 0; x < inds.length; x++) { + inds[x] = buff.get(); + } + return inds; + } + + //// -- GENERAL DOUBLE ROUTINES -- //// + /** + * Create a new DoubleBuffer of the specified size. + * + * @param size + * required number of double to store. + * @return the new DoubleBuffer + */ + public static DoubleBuffer createDoubleBuffer(int size) { + DoubleBuffer buf = ByteBuffer.allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer(); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Create a new DoubleBuffer of an appropriate size to hold the specified + * number of doubles only if the given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of doubles that need to be held by the newly created + * buffer + * @return the requested new DoubleBuffer + */ + public static DoubleBuffer createDoubleBuffer(DoubleBuffer buf, int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createDoubleBuffer(size); + return buf; + } + + /** + * Creates a new DoubleBuffer with the same contents as the given + * DoubleBuffer. The new DoubleBuffer is seperate from the old one and + * changes are not reflected across. If you want to reflect changes, + * consider using Buffer.duplicate(). + * + * @param buf + * the DoubleBuffer to copy + * @return the copy + */ + public static DoubleBuffer clone(DoubleBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + DoubleBuffer copy; + if (buf.isDirect()) { + copy = createDoubleBuffer(buf.limit()); + } else { + copy = DoubleBuffer.allocate(buf.limit()); + } + copy.put(buf); + + return copy; + } + + //// -- GENERAL FLOAT ROUTINES -- //// + /** + * Create a new FloatBuffer of the specified size. + * + * @param size + * required number of floats to store. + * @return the new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(int size) { + FloatBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer(); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Copies floats from one position in the buffer to another. + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the starting point to copy from + * @param toPos + * the starting point to copy to + * @param length + * the number of floats to copy + */ + public static void copyInternal(FloatBuffer buf, int fromPos, int toPos, int length) { + float[] data = new float[length]; + buf.position(fromPos); + buf.get(data); + buf.position(toPos); + buf.put(data); + } + + /** + * Creates a new FloatBuffer with the same contents as the given + * FloatBuffer. The new FloatBuffer is seperate from the old one and changes + * are not reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the FloatBuffer to copy + * @return the copy + */ + public static FloatBuffer clone(FloatBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + FloatBuffer copy; + if (buf.isDirect()) { + copy = createFloatBuffer(buf.limit()); + } else { + copy = FloatBuffer.allocate(buf.limit()); + } + copy.put(buf); + + return copy; + } + + //// -- GENERAL INT ROUTINES -- //// + /** + * Create a new IntBuffer of the specified size. + * + * @param size + * required number of ints to store. + * @return the new IntBuffer + */ + public static IntBuffer createIntBuffer(int size) { + IntBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer(); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Create a new IntBuffer of an appropriate size to hold the specified + * number of ints only if the given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of ints that need to be held by the newly created + * buffer + * @return the requested new IntBuffer + */ + public static IntBuffer createIntBuffer(IntBuffer buf, int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createIntBuffer(size); + return buf; + } + + /** + * Creates a new IntBuffer with the same contents as the given IntBuffer. + * The new IntBuffer is seperate from the old one and changes are not + * reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the IntBuffer to copy + * @return the copy + */ + public static IntBuffer clone(IntBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + IntBuffer copy; + if (buf.isDirect()) { + copy = createIntBuffer(buf.limit()); + } else { + copy = IntBuffer.allocate(buf.limit()); + } + copy.put(buf); + + return copy; + } + + //// -- GENERAL BYTE ROUTINES -- //// + /** + * Create a new ByteBuffer of the specified size. + * + * @param size + * required number of ints to store. + * @return the new IntBuffer + */ + public static ByteBuffer createByteBuffer(int size) { + ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Create a new ByteBuffer of an appropriate size to hold the specified + * number of ints only if the given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of bytes that need to be held by the newly created + * buffer + * @return the requested new IntBuffer + */ + public static ByteBuffer createByteBuffer(ByteBuffer buf, int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createByteBuffer(size); + return buf; + } + + public static ByteBuffer createByteBuffer(byte... data) { + ByteBuffer bb = createByteBuffer(data.length); + bb.put(data); + bb.flip(); + return bb; + } + + public static ByteBuffer createByteBuffer(String data) { + byte[] bytes = data.getBytes(); + ByteBuffer bb = createByteBuffer(bytes.length); + bb.put(bytes); + bb.flip(); + return bb; + } + + /** + * Creates a new ByteBuffer with the same contents as the given ByteBuffer. + * The new ByteBuffer is seperate from the old one and changes are not + * reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the ByteBuffer to copy + * @return the copy + */ + public static ByteBuffer clone(ByteBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + ByteBuffer copy; + if (buf.isDirect()) { + copy = createByteBuffer(buf.limit()); + } else { + copy = ByteBuffer.allocate(buf.limit()); + } + copy.put(buf); + + return copy; + } + + //// -- GENERAL SHORT ROUTINES -- //// + /** + * Create a new ShortBuffer of the specified size. + * + * @param size + * required number of shorts to store. + * @return the new ShortBuffer + */ + public static ShortBuffer createShortBuffer(int size) { + ShortBuffer buf = ByteBuffer.allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer(); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Create a new ShortBuffer of an appropriate size to hold the specified + * number of shorts only if the given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of shorts that need to be held by the newly created + * buffer + * @return the requested new ShortBuffer + */ + public static ShortBuffer createShortBuffer(ShortBuffer buf, int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createShortBuffer(size); + return buf; + } + + public static ShortBuffer createShortBuffer(short... data) { + if (data == null) { + return null; + } + ShortBuffer buff = createShortBuffer(data.length); + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + /** + * Creates a new ShortBuffer with the same contents as the given ShortBuffer. + * The new ShortBuffer is seperate from the old one and changes are not + * reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the ShortBuffer to copy + * @return the copy + */ + public static ShortBuffer clone(ShortBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + ShortBuffer copy; + if (buf.isDirect()) { + copy = createShortBuffer(buf.limit()); + } else { + copy = ShortBuffer.allocate(buf.limit()); + } + copy.put(buf); + + return copy; + } + + /** + * Ensures there is at least the <code>required</code> number of entries left after the current position of the + * buffer. If the buffer is too small a larger one is created and the old one copied to the new buffer. + * @param buffer buffer that should be checked/copied (may be null) + * @param required minimum number of elements that should be remaining in the returned buffer + * @return a buffer large enough to receive at least the <code>required</code> number of entries, same position as + * the input buffer, not null + */ + public static FloatBuffer ensureLargeEnough(FloatBuffer buffer, int required) { + if (buffer == null || (buffer.remaining() < required)) { + int position = (buffer != null ? buffer.position() : 0); + FloatBuffer newVerts = createFloatBuffer(position + required); + if (buffer != null) { + buffer.rewind(); + newVerts.put(buffer); + newVerts.position(position); + } + buffer = newVerts; + } + return buffer; + } + + public static ShortBuffer ensureLargeEnough(ShortBuffer buffer, int required) { + if (buffer == null || (buffer.remaining() < required)) { + int position = (buffer != null ? buffer.position() : 0); + ShortBuffer newVerts = createShortBuffer(position + required); + if (buffer != null) { + buffer.rewind(); + newVerts.put(buffer); + newVerts.position(position); + } + buffer = newVerts; + } + return buffer; + } + + public static ByteBuffer ensureLargeEnough(ByteBuffer buffer, int required) { + if (buffer == null || (buffer.remaining() < required)) { + int position = (buffer != null ? buffer.position() : 0); + ByteBuffer newVerts = createByteBuffer(position + required); + if (buffer != null) { + buffer.rewind(); + newVerts.put(buffer); + newVerts.position(position); + } + buffer = newVerts; + } + return buffer; + } + + public static void printCurrentDirectMemory(StringBuilder store) { + long totalHeld = 0; + // make a new set to hold the keys to prevent concurrency issues. + ArrayList<Buffer> bufs = new ArrayList<Buffer>(trackingHash.keySet()); + int fBufs = 0, bBufs = 0, iBufs = 0, sBufs = 0, dBufs = 0; + int fBufsM = 0, bBufsM = 0, iBufsM = 0, sBufsM = 0, dBufsM = 0; + for (Buffer b : bufs) { + if (b instanceof ByteBuffer) { + totalHeld += b.capacity(); + bBufsM += b.capacity(); + bBufs++; + } else if (b instanceof FloatBuffer) { + totalHeld += b.capacity() * 4; + fBufsM += b.capacity() * 4; + fBufs++; + } else if (b instanceof IntBuffer) { + totalHeld += b.capacity() * 4; + iBufsM += b.capacity() * 4; + iBufs++; + } else if (b instanceof ShortBuffer) { + totalHeld += b.capacity() * 2; + sBufsM += b.capacity() * 2; + sBufs++; + } else if (b instanceof DoubleBuffer) { + totalHeld += b.capacity() * 8; + dBufsM += b.capacity() * 8; + dBufs++; + } + } + long heapMem = Runtime.getRuntime().totalMemory() + - Runtime.getRuntime().freeMemory(); + + boolean printStout = store == null; + if (store == null) { + store = new StringBuilder(); + } + store.append("Existing buffers: ").append(bufs.size()).append("\n"); + store.append("(b: ").append(bBufs).append(" f: ").append(fBufs).append(" i: ").append(iBufs).append(" s: ").append(sBufs).append(" d: ").append(dBufs).append(")").append("\n"); + store.append("Total heap memory held: ").append(heapMem / 1024).append("kb\n"); + store.append("Total direct memory held: ").append(totalHeld / 1024).append("kb\n"); + store.append("(b: ").append(bBufsM / 1024).append("kb f: ").append(fBufsM / 1024).append("kb i: ").append(iBufsM / 1024).append("kb s: ").append(sBufsM / 1024).append("kb d: ").append(dBufsM / 1024).append("kb)").append("\n"); + if (printStout) { + System.out.println(store.toString()); + } + } + + /** + * Direct buffers are garbage collected by using a phantom reference and a + * reference queue. Every once a while, the JVM checks the reference queue and + * cleans the direct buffers. However, as this doesn't happen + * immediately after discarding all references to a direct buffer, it's + * easy to OutOfMemoryError yourself using direct buffers. This function + * explicitly calls the Cleaner method of a direct buffer. + * + * @param toBeDestroyed + * The direct buffer that will be "cleaned". Utilizes reflection. + * + */ + public static void destroyDirectBuffer(Buffer toBeDestroyed) { + + if (!toBeDestroyed.isDirect()) { + return; + } + try { + Method cleanerMethod = toBeDestroyed.getClass().getMethod("cleaner"); + cleanerMethod.setAccessible(true); + Object cleaner = cleanerMethod.invoke(toBeDestroyed); + if (cleaner != null) { + Method cleanMethod = cleaner.getClass().getMethod("clean"); + cleanMethod.setAccessible(true); + cleanMethod.invoke(cleaner); + } else { + // Try the alternate approach of getting the viewed buffer + Method viewedBufferMethod = toBeDestroyed.getClass().getMethod("viewedBuffer"); + viewedBufferMethod.setAccessible(true); + Object viewedBuffer = viewedBufferMethod.invoke(toBeDestroyed); + if (viewedBuffer != null) { + destroyDirectBuffer( (Buffer)viewedBuffer ); + } else { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "Buffer cannot be destroyed: {0}", toBeDestroyed); + } + } + } catch (IllegalAccessException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (IllegalArgumentException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (InvocationTargetException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (NoSuchMethodException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (SecurityException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } + } + +} diff --git a/engine/src/core/com/jme3/util/IntMap.java b/engine/src/core/com/jme3/util/IntMap.java new file mode 100644 index 0000000..edf659b --- /dev/null +++ b/engine/src/core/com/jme3/util/IntMap.java @@ -0,0 +1,308 @@ +/* + * 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.util; + +import com.jme3.util.IntMap.Entry; +import java.util.Iterator; +import java.util.Map; + +/** + * Similar to a {@link Map} except that ints are used as keys. + * + * Taken from <a href="http://code.google.com/p/skorpios/">http://code.google.com/p/skorpios/</a> + * + * @author Nate + */ +public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable { + + private final IntMapIterator iterator = new IntMapIterator(); + + private Entry[] table; + private final float loadFactor; + private int size, mask, capacity, threshold; + + public IntMap() { + this(16, 0.75f); + } + + public IntMap(int initialCapacity) { + this(initialCapacity, 0.75f); + } + + public IntMap(int initialCapacity, float loadFactor) { + if (initialCapacity > 1 << 30){ + throw new IllegalArgumentException("initialCapacity is too large."); + } + if (initialCapacity < 0){ + throw new IllegalArgumentException("initialCapacity must be greater than zero."); + } + if (loadFactor <= 0){ + throw new IllegalArgumentException("initialCapacity must be greater than zero."); + } + capacity = 1; + while (capacity < initialCapacity){ + capacity <<= 1; + } + this.loadFactor = loadFactor; + this.threshold = (int) (capacity * loadFactor); + this.table = new Entry[capacity]; + this.mask = capacity - 1; + } + + @Override + public IntMap<T> clone(){ + try{ + IntMap<T> clone = (IntMap<T>) super.clone(); + Entry[] newTable = new Entry[table.length]; + for (int i = table.length - 1; i >= 0; i--){ + if (table[i] != null) + newTable[i] = table[i].clone(); + } + clone.table = newTable; + return clone; + }catch (CloneNotSupportedException ex){ + } + return null; + } + + public boolean containsValue(Object value) { + Entry[] table = this.table; + for (int i = table.length; i-- > 0;){ + for (Entry e = table[i]; e != null; e = e.next){ + if (e.value.equals(value)){ + return true; + } + } + } + return false; + } + + public boolean containsKey(int key) { + int index = ((int) key) & mask; + for (Entry e = table[index]; e != null; e = e.next){ + if (e.key == key){ + return true; + } + } + return false; + } + + public T get(int key) { + int index = key & mask; + for (Entry e = table[index]; e != null; e = e.next){ + if (e.key == key){ + return (T) e.value; + } + } + return null; + } + + public T put(int key, T value) { + int index = key & mask; + // Check if key already exists. + for (Entry e = table[index]; e != null; e = e.next){ + if (e.key != key){ + continue; + } + Object oldValue = e.value; + e.value = value; + return (T) oldValue; + } + table[index] = new Entry(key, value, table[index]); + if (size++ >= threshold){ + // Rehash. + int newCapacity = 2 * capacity; + Entry[] newTable = new Entry[newCapacity]; + Entry[] src = table; + int bucketmask = newCapacity - 1; + for (int j = 0; j < src.length; j++){ + Entry e = src[j]; + if (e != null){ + src[j] = null; + do{ + Entry next = e.next; + index = e.key & bucketmask; + e.next = newTable[index]; + newTable[index] = e; + e = next; + }while (e != null); + } + } + table = newTable; + capacity = newCapacity; + threshold = (int) (newCapacity * loadFactor); + mask = capacity - 1; + } + return null; + } + + public T remove(int key) { + int index = key & mask; + Entry prev = table[index]; + Entry e = prev; + while (e != null){ + Entry next = e.next; + if (e.key == key){ + size--; + if (prev == e){ + table[index] = next; + }else{ + prev.next = next; + } + return (T) e.value; + } + prev = e; + e = next; + } + return null; + } + + public int size() { + return size; + } + + public void clear() { + Entry[] table = this.table; + for (int index = table.length; --index >= 0;){ + table[index] = null; + } + size = 0; + } + + public Iterator<Entry<T>> iterator() { + iterator.beginUse(); + return iterator; + } + + final class IntMapIterator implements Iterator<Entry<T>> { + + /** + * Current entry. + */ + private Entry cur; + + /** + * Entry in the table + */ + private int idx = 0; + + /** + * Element in the entry + */ + private int el = 0; + + public IntMapIterator() { + } + + public void beginUse(){ + cur = table[0]; + idx = 0; + el = 0; + } + + public boolean hasNext() { + return el < size; + } + + public Entry next() { + if (el >= size) + throw new IllegalStateException("No more elements!"); + + if (cur != null){ + Entry e = cur; + cur = cur.next; + el++; + return e; + } +// if (cur != null && cur.next != null){ + // if we have a current entry, continue to the next entry in the list +// cur = cur.next; +// el++; +// return cur; +// } + + do { + // either we exhausted the current entry list, or + // the entry was null. find another non-null entry. + cur = table[++idx]; + } while (cur == null); + + Entry e = cur; + cur = cur.next; + el ++; + + return e; + } + + public void remove() { + } + + } + + public static final class Entry<T> implements Cloneable { + + final int key; + T value; + Entry next; + + Entry(int k, T v, Entry n) { + key = k; + value = v; + next = n; + } + + public int getKey(){ + return key; + } + + public T getValue(){ + return value; + } + + @Override + public String toString(){ + return key + " => " + value; + } + + @Override + public Entry<T> clone(){ + try{ + Entry<T> clone = (Entry<T>) super.clone(); + clone.next = next != null ? next.clone() : null; + return clone; + }catch (CloneNotSupportedException ex){ + } + return null; + } + } +} diff --git a/engine/src/core/com/jme3/util/JmeFormatter.java b/engine/src/core/com/jme3/util/JmeFormatter.java new file mode 100644 index 0000000..998438a --- /dev/null +++ b/engine/src/core/com/jme3/util/JmeFormatter.java @@ -0,0 +1,93 @@ +/* + * 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.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.MessageFormat; +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +/** + * More simple formatter than the default one used in Java logging. + * Example output: <br/> + * INFO Display3D 12:00 PM: Display created. + */ +public class JmeFormatter extends Formatter { + + private Date calendar = new Date(); + private String lineSeperator; + private MessageFormat format; + private Object args[] = new Object[1]; + private StringBuffer store = new StringBuffer(); + + public JmeFormatter(){ + lineSeperator = System.getProperty("line.separator"); + format = new MessageFormat("{0,time}"); + } + + @Override + public String format(LogRecord record) { + StringBuffer sb = new StringBuffer(); + + calendar.setTime(record.getMillis()); + args[0] = calendar; + store.setLength(0); + format.format(args, store, null); + + String clazz = null; + try{ + clazz = Class.forName(record.getSourceClassName()).getSimpleName(); + } catch (ClassNotFoundException ex){ + } + + sb.append(record.getLevel().getLocalizedName()).append(" "); + sb.append(clazz).append(" "); + sb.append(store.toString()).append(" "); + sb.append(formatMessage(record)).append(lineSeperator); + + if (record.getThrown() != null) { + try { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + record.getThrown().printStackTrace(pw); + pw.close(); + sb.append(sw.toString()); + } catch (Exception ex) { + } + } + + return sb.toString(); + } +} diff --git a/engine/src/core/com/jme3/util/ListMap.java b/engine/src/core/com/jme3/util/ListMap.java new file mode 100644 index 0000000..c5b6de4 --- /dev/null +++ b/engine/src/core/com/jme3/util/ListMap.java @@ -0,0 +1,317 @@ +/* + * 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.util; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Implementation of a Map that favors iteration speed rather than + * get/put speed. + * + * @author Kirill Vainer + */ +public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable { + + public static void main(String[] args){ + Map<String, String> map = new ListMap<String, String>(); + map.put( "bob", "hello"); + System.out.println(map.get("bob")); + map.remove("bob"); + System.out.println(map.size()); + + map.put("abc", "1"); + map.put("def", "2"); + map.put("ghi", "3"); + map.put("jkl", "4"); + map.put("mno", "5"); + System.out.println(map.get("ghi")); + } + + private final static class ListMapEntry<K, V> implements Map.Entry<K, V>, Cloneable { + + private final K key; + private V value; + + public ListMapEntry(K key, V value){ + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V v) { + throw new UnsupportedOperationException(); + } + + @Override + public ListMapEntry<K, V> clone(){ + return new ListMapEntry<K, V>(key, value); + } + + } + + private final HashMap<K, V> backingMap; + private ListMapEntry<K, V>[] entries; + +// private final ArrayList<ListMapEntry<K,V>> entries; + + public ListMap(){ + entries = new ListMapEntry[4]; + backingMap = new HashMap<K, V>(4); +// entries = new ArrayList<ListMapEntry<K,V>>(); + } + + public ListMap(int initialCapacity){ + entries = new ListMapEntry[initialCapacity]; + backingMap = new HashMap<K, V>(initialCapacity); +// entries = new ArrayList<ListMapEntry<K, V>>(initialCapacity); + } + + public ListMap(Map<? extends K, ? extends V> map){ + entries = new ListMapEntry[map.size()]; + backingMap = new HashMap<K, V>(map.size()); +// entries = new ArrayList<ListMapEntry<K, V>>(map.size()); + putAll(map); + } + + public int size() { +// return entries.size(); + return backingMap.size(); + } + + public Entry<K, V> getEntry(int index){ +// return entries.get(index); + return entries[index]; + } + + public V getValue(int index){ +// return entries.get(index).value; + return entries[index].value; + } + + public K getKey(int index){ +// return entries.get(index).key; + return entries[index].key; + } + + public boolean isEmpty() { + return size() == 0; + } + + private static boolean keyEq(Object keyA, Object keyB){ + return keyA.hashCode() == keyB.hashCode() ? (keyA == keyB) || keyA.equals(keyB) : false; + } +// +// private static boolean valEq(Object a, Object b){ +// return a == null ? (b == null) : a.equals(b); +// } + + public boolean containsKey(Object key) { + return backingMap.containsKey( (K) key); +// if (key == null) +// throw new IllegalArgumentException(); +// +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry<K,V> entry = entries.get(i); +// if (keyEq(entry.key, key)) +// return true; +// } +// return false; + } + + public boolean containsValue(Object value) { + return backingMap.containsValue( (V) value); +// for (int i = 0; i < entries.size(); i++){ +// if (valEq(entries.get(i).value, value)) +// return true; +// } +// return false; + } + + public V get(Object key) { + return backingMap.get( (K) key); +// if (key == null) +// throw new IllegalArgumentException(); +// +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry<K,V> entry = entries.get(i); +// if (keyEq(entry.key, key)) +// return entry.value; +// } +// return null; + } + + public V put(K key, V value) { + if (backingMap.containsKey(key)){ + // set the value on the entry + int size = size(); + for (int i = 0; i < size; i++){ + ListMapEntry<K, V> entry = entries[i]; + if (keyEq(entry.key, key)){ + entry.value = value; + break; + } + } + }else{ + int size = size(); + // expand list as necessary + if (size == entries.length){ + ListMapEntry<K, V>[] tmpEntries = entries; + entries = new ListMapEntry[size * 2]; + System.arraycopy(tmpEntries, 0, entries, 0, size); + } + entries[size] = new ListMapEntry<K, V>(key, value); + } + return backingMap.put(key, value); +// if (key == null) +// throw new IllegalArgumentException(); +// +// // check if entry exists, if yes, overwrite it with new value +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry<K,V> entry = entries.get(i); +// if (keyEq(entry.key, key)){ +// V prevValue = entry.value; +// entry.value = value; +// return prevValue; +// } +// } +// +// // add a new entry +// entries.add(new ListMapEntry<K, V>(key, value)); +// return null; + } + + public V remove(Object key) { + V element = backingMap.remove( (K) key); + if (element != null){ + // find removed element + int size = size() + 1; // includes removed element + int removedIndex = -1; + for (int i = 0; i < size; i++){ + ListMapEntry<K, V> entry = entries[i]; + if (keyEq(entry.key, key)){ + removedIndex = i; + break; + } + } + assert removedIndex >= 0; + + size --; + for (int i = removedIndex; i < size; i++){ + entries[i] = entries[i+1]; + } + } + return element; +// if (key == null) +// throw new IllegalArgumentException(); +// +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry<K,V> entry = entries.get(i); +// if (keyEq(entry.key, key)){ +// return entries.remove(i).value; +// } +// } +// return null; + } + + public void putAll(Map<? extends K, ? extends V> map) { + for (Entry<? extends K, ? extends V> entry : map.entrySet()){ + put(entry.getKey(), entry.getValue()); + } + + +// if (map instanceof ListMap){ +// ListMap<K, V> listMap = (ListMap<K, V>) map; +// ArrayList<ListMapEntry<K, V>> otherEntries = listMap.entries; +// for (int i = 0; i < otherEntries.size(); i++){ +// ListMapEntry<K, V> entry = otherEntries.get(i); +// put(entry.key, entry.value); +// } +// }else{ +// for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()){ +// put(entry.getKey(), entry.getValue()); +// } +// } + } + + public void clear() { + backingMap.clear(); +// entries.clear(); + } + + @Override + public ListMap<K, V> clone(){ + ListMap<K, V> clone = new ListMap<K, V>(size()); + clone.putAll(this); + return clone; + } + + public Set<K> keySet() { + return backingMap.keySet(); +// HashSet<K> keys = new HashSet<K>(); +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry<K,V> entry = entries.get(i); +// keys.add(entry.key); +// } +// return keys; + } + + public Collection<V> values() { + return backingMap.values(); +// ArrayList<V> values = new ArrayList<V>(); +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry<K,V> entry = entries.get(i); +// values.add(entry.value); +// } +// return values; + } + + public Set<Entry<K, V>> entrySet() { + return backingMap.entrySet(); +// HashSet<Entry<K, V>> entryset = new HashSet<Entry<K, V>>(); +// entryset.addAll(entries); +// return entryset; + } + +} diff --git a/engine/src/core/com/jme3/util/LittleEndien.java b/engine/src/core/com/jme3/util/LittleEndien.java new file mode 100644 index 0000000..0f71596 --- /dev/null +++ b/engine/src/core/com/jme3/util/LittleEndien.java @@ -0,0 +1,160 @@ +/* + * 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.util; + +import java.io.*; + +/** + * <code>LittleEndien</code> is a class to read littleendien stored data + * via a InputStream. All functions work as defined in DataInput, but + * assume they come from a LittleEndien input stream. Currently used to read .ms3d and .3ds files. + * @author Jack Lindamood + */ +public class LittleEndien extends InputStream implements DataInput { + + private BufferedInputStream in; + private BufferedReader inRead; + + /** + * Creates a new LittleEndien reader from the given input stream. The + * stream is wrapped in a BufferedReader automatically. + * @param in The input stream to read from. + */ + public LittleEndien(InputStream in) { + this.in = new BufferedInputStream(in); + inRead = new BufferedReader(new InputStreamReader(in)); + } + + public int read() throws IOException { + return in.read(); + } + + @Override + public int read(byte[] buf) throws IOException { + return in.read(buf); + } + + @Override + public int read(byte[] buf, int off, int len) throws IOException { + return in.read(buf, off, len); + } + + public int readUnsignedShort() throws IOException { + return (in.read() & 0xff) | ((in.read() & 0xff) << 8); + } + + /** + * read an unsigned int as a long + */ + public long readUInt() throws IOException { + return ((in.read() & 0xff) + | ((in.read() & 0xff) << 8) + | ((in.read() & 0xff) << 16) + | (((long) (in.read() & 0xff)) << 24)); + } + + public boolean readBoolean() throws IOException { + return (in.read() != 0); + } + + public byte readByte() throws IOException { + return (byte) in.read(); + } + + public int readUnsignedByte() throws IOException { + return in.read(); + } + + public short readShort() throws IOException { + return (short) this.readUnsignedShort(); + } + + public char readChar() throws IOException { + return (char) this.readUnsignedShort(); + } + + public int readInt() throws IOException { + return ((in.read() & 0xff) + | ((in.read() & 0xff) << 8) + | ((in.read() & 0xff) << 16) + | ((in.read() & 0xff) << 24)); + } + + public long readLong() throws IOException { + return ((in.read() & 0xff) + | ((long) (in.read() & 0xff) << 8) + | ((long) (in.read() & 0xff) << 16) + | ((long) (in.read() & 0xff) << 24) + | ((long) (in.read() & 0xff) << 32) + | ((long) (in.read() & 0xff) << 40) + | ((long) (in.read() & 0xff) << 48) + | ((long) (in.read() & 0xff) << 56)); + } + + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + public void readFully(byte b[]) throws IOException { + in.read(b, 0, b.length); + } + + public void readFully(byte b[], int off, int len) throws IOException { + in.read(b, off, len); + } + + public int skipBytes(int n) throws IOException { + return (int) in.skip(n); + } + + public String readLine() throws IOException { + return inRead.readLine(); + } + + public String readUTF() throws IOException { + throw new IOException("Unsupported operation"); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public int available() throws IOException { + return in.available(); + } +} diff --git a/engine/src/core/com/jme3/util/NativeObject.java b/engine/src/core/com/jme3/util/NativeObject.java new file mode 100644 index 0000000..a59cb05 --- /dev/null +++ b/engine/src/core/com/jme3/util/NativeObject.java @@ -0,0 +1,172 @@ +/* + * 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.util; + +/** + * Describes a native object. An encapsulation of a certain object + * on the native side of the graphics or audio library. + * + * This class is used to track when OpenGL and OpenAL native objects are + * collected by the garbage collector, and then invoke the proper destructor + * on the OpenGL library to delete it from memory. + */ +public abstract class NativeObject implements Cloneable { + + /** + * The ID of the object, usually depends on its type. + * Typically returned from calls like glGenTextures, glGenBuffers, etc. + */ + protected int id = -1; + + /** + * A reference to a "handle". By hard referencing a certain object, it's + * possible to find when a certain GLObject is no longer used, and to delete + * its instance from the graphics library. + */ + protected Object handleRef = null; + + /** + * True if the data represented by this GLObject has been changed + * and needs to be updated before used. + */ + protected boolean updateNeeded = true; + + /** + * The type of the GLObject, usually specified by a subclass. + */ + protected final Class<?> type; + + /** + * Creates a new GLObject with the given type. Should be + * called by the subclasses. + * + * @param type The type that the subclass represents. + */ + public NativeObject(Class<?> type){ + this.type = type; + this.handleRef = new Object(); + } + + /** + * Protected constructor that doesn't allocate handle ref. + * This is used in subclasses for the createDestructableClone(). + */ + protected NativeObject(Class<?> type, int id){ + this.type = type; + this.id = id; + } + + /** + * Sets the ID of the GLObject. This method is used in Renderer and must + * not be called by the user. + * @param id The ID to set + */ + public void setId(int id){ + if (this.id != -1) + throw new IllegalStateException("ID has already been set for this GL object."); + + this.id = id; + } + + /** + * @return The ID of the object. Should not be used by user code in most + * cases. + */ + public int getId(){ + return id; + } + + /** + * Internal use only. Indicates that the object has changed + * and its state needs to be updated. + */ + public void setUpdateNeeded(){ + updateNeeded = true; + } + + /** + * Internal use only. Indicates that the state changes were applied. + */ + public void clearUpdateNeeded(){ + updateNeeded = false; + } + + /** + * Internal use only. Check if {@link #setUpdateNeeded()} was called before. + */ + public boolean isUpdateNeeded(){ + return updateNeeded; + } + + @Override + public String toString(){ + return "Native" + type.getSimpleName() + " " + id; + } + + /** + * This should create a deep clone. For a shallow clone, use + * createDestructableClone(). + */ + @Override + protected NativeObject clone(){ + try{ + NativeObject obj = (NativeObject) super.clone(); + obj.handleRef = new Object(); + obj.id = -1; + obj.updateNeeded = true; + return obj; + }catch (CloneNotSupportedException ex){ + throw new AssertionError(); + } + } + + /** + * Called when the GL context is restarted to reset all IDs. Prevents + * "white textures" on display restart. + */ + public abstract void resetObject(); + + /** + * Deletes the GL object from the GPU when it is no longer used. Called + * automatically by the GL object manager. + * + * @param rendererObject The renderer to be used to delete the object + */ + public abstract void deleteObject(Object rendererObject); + + /** + * Creates a shallow clone of this GL Object. The deleteObject method + * should be functional for this object. + */ + public abstract NativeObject createDestructableClone(); +} diff --git a/engine/src/core/com/jme3/util/NativeObjectManager.java b/engine/src/core/com/jme3/util/NativeObjectManager.java new file mode 100644 index 0000000..f8d1d18 --- /dev/null +++ b/engine/src/core/com/jme3/util/NativeObjectManager.java @@ -0,0 +1,148 @@ +/* + * 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.util; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * GLObjectManager tracks all GLObjects used by the Renderer. Using a + * <code>ReferenceQueue</code> the <code>GLObjectManager</code> can delete + * unused objects from GPU when their counterparts on the CPU are no longer used. + * + * On restart, the renderer may request the objects to be reset, thus allowing + * the GLObjects to re-initialize with the new display context. + */ +public class NativeObjectManager { + + private static final Logger logger = Logger.getLogger(NativeObjectManager.class.getName()); + + /** + * The queue will receive notifications of {@link NativeObject}s which are no longer + * referenced. + */ + private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>(); + + /** + * List of currently active GLObjects. + */ + private ArrayList<NativeObjectRef> refList + = new ArrayList<NativeObjectRef>(); + + private class NativeObjectRef extends PhantomReference<Object>{ + + private NativeObject objClone; + private WeakReference<NativeObject> realObj; + + public NativeObjectRef(NativeObject obj){ + super(obj.handleRef, refQueue); + assert obj.handleRef != null; + + this.realObj = new WeakReference<NativeObject>(obj); + this.objClone = obj.createDestructableClone(); + } + } + + /** + * Register a GLObject with the manager. + */ + public void registerForCleanup(NativeObject obj){ + NativeObjectRef ref = new NativeObjectRef(obj); + refList.add(ref); + if (logger.isLoggable(Level.FINEST)) + logger.log(Level.FINEST, "Registered: {0}", new String[]{obj.toString()}); + } + + /** + * Deletes unused GLObjects + */ + public void deleteUnused(Object rendererObject){ + while (true){ + NativeObjectRef ref = (NativeObjectRef) refQueue.poll(); + if (ref == null) + return; + + refList.remove(ref); + ref.objClone.deleteObject(rendererObject); + if (logger.isLoggable(Level.FINEST)) + logger.log(Level.FINEST, "Deleted: {0}", ref.objClone); + } + } + + /** + * Deletes all objects. Must only be called when display is destroyed. + */ + public void deleteAllObjects(Object rendererObject){ + deleteUnused(rendererObject); + for (NativeObjectRef ref : refList){ + ref.objClone.deleteObject(rendererObject); + NativeObject realObj = ref.realObj.get(); + if (realObj != null){ + // Note: make sure to reset them as well + // They may get used in a new renderer in the future + realObj.resetObject(); + } + } + refList.clear(); + } + + /** + * Resets all {@link NativeObject}s. + */ + public void resetObjects(){ + for (NativeObjectRef ref : refList){ + // here we use the actual obj not the clone, + // otherwise its useless + NativeObject realObj = ref.realObj.get(); + if (realObj == null) + continue; + + realObj.resetObject(); + if (logger.isLoggable(Level.FINEST)) + logger.log(Level.FINEST, "Reset: {0}", realObj); + } + refList.clear(); + } + +// public void printObjects(){ +// System.out.println(" ------------------- "); +// System.out.println(" GL Object count: "+ objectList.size()); +// for (GLObject obj : objectList){ +// System.out.println(obj); +// } +// } +} diff --git a/engine/src/core/com/jme3/util/PlaceholderAssets.java b/engine/src/core/com/jme3/util/PlaceholderAssets.java new file mode 100644 index 0000000..c36abc9 --- /dev/null +++ b/engine/src/core/com/jme3/util/PlaceholderAssets.java @@ -0,0 +1,72 @@ +package com.jme3.util; + +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioBuffer; +import com.jme3.audio.AudioData; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.nio.ByteBuffer; + +public class PlaceholderAssets { + + /** + * Checkerboard of white and red squares + */ + private static final byte[] imageData = { + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + }; + + public static Image getPlaceholderImage(){ + ByteBuffer tempData = BufferUtils.createByteBuffer(3 * 4 * 4); + tempData.put(imageData).flip(); + return new Image(Format.RGB8, 4, 4, tempData); + } + + public static Material getPlaceholderMaterial(AssetManager assetManager){ + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Red); + return mat; + } + + public static Spatial getPlaceholderModel(AssetManager assetManager){ + // What should be the size? Nobody knows + // the user's expected scale... + Box box = new Box(1, 1, 1); + Geometry geom = new Geometry("placeholder", box); + geom.setMaterial(getPlaceholderMaterial(assetManager)); + return geom; + } + + public static AudioData getPlaceholderAudio(){ + AudioBuffer audioBuf = new AudioBuffer(); + audioBuf.setupFormat(1, 8, 44100); + ByteBuffer bb = BufferUtils.createByteBuffer(1); + bb.put((byte)0).flip(); + audioBuf.updateData(bb); + return audioBuf; + } + +} diff --git a/engine/src/core/com/jme3/util/SafeArrayList.java b/engine/src/core/com/jme3/util/SafeArrayList.java new file mode 100644 index 0000000..fcd6971 --- /dev/null +++ b/engine/src/core/com/jme3/util/SafeArrayList.java @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2009-2011 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.util; + +import java.util.*; + +/** + * <p>Provides a list with similar modification semantics to java.util.concurrent's + * CopyOnWriteArrayList except that it is not concurrent and also provides + * direct access to the current array. This List allows modification of the + * contents while iterating as any iterators will be looking at a snapshot of + * the list at the time they were created. Similarly, access the raw internal + * array is only presenting a snap shot and so can be safely iterated while + * the list is changing.</p> + * + * <p>All modifications, including set() operations will cause a copy of the + * data to be created that replaces the old version. Because this list is + * not designed for threading concurrency it further optimizes the "many modifications" + * case by buffering them as a normal ArrayList until the next time the contents + * are accessed.</p> + * + * <p>Normal list modification performance should be equal to ArrayList in a + * many situations and always better than CopyOnWriteArrayList. Optimum usage + * is when modifications are done infrequently or in batches... as is often the + * case in a scene graph. Read operations perform superior to all other methods + * as the array can be accessed directly.</p> + * + * <p>Important caveats over normal java.util.Lists:</p> + * <ul> + * <li>Even though this class supports modifying the list, the subList() method + * returns a read-only list. This technically breaks the List contract.</li> + * <li>The ListIterators returned by this class only support the remove() + * modification method. add() and set() are not supported on the iterator. + * Even after ListIterator.remove() or Iterator.remove() is called, this change + * is not reflected in the iterator instance as it is still refering to its + * original snapshot. + * </ul> + * + * @version $Revision: 8940 $ + * @author Paul Speed + */ +public class SafeArrayList<E> implements List<E> { + + // Implementing List directly to avoid accidentally acquiring + // incorrect or non-optimal behavior from AbstractList. For + // example, the default iterator() method will not work for + // this list. + + // Note: given the particular use-cases this was intended, + // it would make sense to nerf the public mutators and + // make this publicly act like a read-only list. + // SafeArrayList-specific methods could then be exposed + // for the classes like Node and Spatial to use to manage + // the list. This was the callers couldn't remove a child + // without it being detached properly, for example. + + private Class<E> elementType; + private List<E> buffer; + private E[] backingArray; + private int size = 0; + + public SafeArrayList(Class<E> elementType) { + this.elementType = elementType; + } + + public SafeArrayList(Class<E> elementType, Collection<? extends E> c) { + this.elementType = elementType; + addAll(c); + } + + protected final <T> T[] createArray(Class<T> type, int size) { + return (T[])java.lang.reflect.Array.newInstance(type, size); + } + + protected final E[] createArray(int size) { + return createArray(elementType, size); + } + + /** + * Returns a current snapshot of this List's backing array that + * is guaranteed not to change through further List manipulation. + * Changes to this array may or may not be reflected in the list and + * should be avoided. + */ + public final E[] getArray() { + if( backingArray != null ) + return backingArray; + + if( buffer == null ) { + backingArray = createArray(0); + } else { + // Only keep the array or the buffer but never both at + // the same time. 1) it saves space, 2) it keeps the rest + // of the code safer. + backingArray = buffer.toArray( createArray(buffer.size()) ); + buffer = null; + } + return backingArray; + } + + protected final List<E> getBuffer() { + if( buffer != null ) + return buffer; + + if( backingArray == null ) { + buffer = new ArrayList(); + } else { + // Only keep the array or the buffer but never both at + // the same time. 1) it saves space, 2) it keeps the rest + // of the code safer. + buffer = new ArrayList( Arrays.asList(backingArray) ); + backingArray = null; + } + return buffer; + } + + public final int size() { + return size; + } + + public final boolean isEmpty() { + return size == 0; + } + + public boolean contains(Object o) { + return indexOf(o) >= 0; + } + + public Iterator<E> iterator() { + return listIterator(); + } + + public Object[] toArray() { + return getArray(); + } + + public <T> T[] toArray(T[] a) { + + E[] array = getArray(); + if (a.length < array.length) { + return (T[])Arrays.copyOf(array, array.length, a.getClass()); + } + + System.arraycopy( array, 0, a, 0, array.length ); + + if (a.length > array.length) { + a[array.length] = null; + } + + return a; + } + + public boolean add(E e) { + boolean result = getBuffer().add(e); + size = getBuffer().size(); + return result; + } + + public boolean remove(Object o) { + boolean result = getBuffer().remove(o); + size = getBuffer().size(); + return result; + } + + public boolean containsAll(Collection<?> c) { + return Arrays.asList(getArray()).containsAll(c); + } + + public boolean addAll(Collection<? extends E> c) { + boolean result = getBuffer().addAll(c); + size = getBuffer().size(); + return result; + } + + public boolean addAll(int index, Collection<? extends E> c) { + boolean result = getBuffer().addAll(index, c); + size = getBuffer().size(); + return result; + } + + public boolean removeAll(Collection<?> c) { + boolean result = getBuffer().removeAll(c); + size = getBuffer().size(); + return result; + } + + public boolean retainAll(Collection<?> c) { + boolean result = getBuffer().retainAll(c); + size = getBuffer().size(); + return result; + } + + public void clear() { + getBuffer().clear(); + size = 0; + } + + public boolean equals(Object o) { + if( o == this ) + return true; + if( !(o instanceof List) ) //covers null too + return false; + List other = (List)o; + Iterator i1 = iterator(); + Iterator i2 = other.iterator(); + while( i1.hasNext() && i2.hasNext() ) { + Object o1 = i1.next(); + Object o2 = i2.next(); + if( o1 == o2 ) + continue; + if( o1 == null || !o1.equals(o2) ) + return false; + } + return !(i1.hasNext() || !i2.hasNext()); + } + + public int hashCode() { + // Exactly the hash code described in the List interface, basically + E[] array = getArray(); + int result = 1; + for( E e : array ) { + result = 31 * result + (e == null ? 0 : e.hashCode()); + } + return result; + } + + public final E get(int index) { + if( backingArray != null ) + return backingArray[index]; + if( buffer != null ) + return buffer.get(index); + throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" ); + } + + public E set(int index, E element) { + return getBuffer().set(index, element); + } + + public void add(int index, E element) { + getBuffer().add(index, element); + size = getBuffer().size(); + } + + public E remove(int index) { + E result = getBuffer().remove(index); + size = getBuffer().size(); + return result; + } + + public int indexOf(Object o) { + E[] array = getArray(); + for( int i = 0; i < array.length; i++ ) { + E element = array[i]; + if( element == o ) { + return i; + } + if( element != null && element.equals(o) ) { + return i; + } + } + return -1; + } + + public int lastIndexOf(Object o) { + E[] array = getArray(); + for( int i = array.length - 1; i >= 0; i-- ) { + E element = array[i]; + if( element == o ) { + return i; + } + if( element != null && element.equals(o) ) { + return i; + } + } + return -1; + } + + public ListIterator<E> listIterator() { + return new ArrayIterator<E>(getArray(), 0); + } + + public ListIterator<E> listIterator(int index) { + return new ArrayIterator<E>(getArray(), index); + } + + public List<E> subList(int fromIndex, int toIndex) { + + // So far JME doesn't use subList that I can see so I'm nerfing it. + List<E> raw = Arrays.asList(getArray()).subList(fromIndex, toIndex); + return Collections.unmodifiableList(raw); + } + + public String toString() { + + E[] array = getArray(); + if( array.length == 0 ) { + return "[]"; + } + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for( int i = 0; i < array.length; i++ ) { + if( i > 0 ) + sb.append( ", " ); + E e = array[i]; + sb.append( e == this ? "(this Collection)" : e ); + } + sb.append(']'); + return sb.toString(); + } + + protected class ArrayIterator<E> implements ListIterator<E> { + private E[] array; + private int next; + private int lastReturned; + + protected ArrayIterator( E[] array, int index ) { + this.array = array; + this.next = index; + this.lastReturned = -1; + } + + public boolean hasNext() { + return next != array.length; + } + + public E next() { + if( !hasNext() ) + throw new NoSuchElementException(); + lastReturned = next++; + return array[lastReturned]; + } + + public boolean hasPrevious() { + return next != 0; + } + + public E previous() { + if( !hasPrevious() ) + throw new NoSuchElementException(); + lastReturned = --next; + return array[lastReturned]; + } + + public int nextIndex() { + return next; + } + + public int previousIndex() { + return next - 1; + } + + public void remove() { + // This operation is not so easy to do but we will fake it. + // The issue is that the backing list could be completely + // different than the one this iterator is a snapshot of. + // We'll just remove(element) which in most cases will be + // correct. If the list had earlier .equals() equivalent + // elements then we'll remove one of those instead. Either + // way, none of those changes are reflected in this iterator. + SafeArrayList.this.remove( array[lastReturned] ); + } + + public void set(E e) { + throw new UnsupportedOperationException(); + } + + public void add(E e) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/engine/src/core/com/jme3/util/SkyFactory.java b/engine/src/core/com/jme3/util/SkyFactory.java new file mode 100644 index 0000000..2808696 --- /dev/null +++ b/engine/src/core/com/jme3/util/SkyFactory.java @@ -0,0 +1,214 @@ +package com.jme3.util; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.bounding.BoundingSphere; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.TextureCubeMap; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * <code>SkyFactory</code> is used to create jME {@link Spatial}s that can + * be attached to the scene to display a sky image in the background. + * + * @author Kirill Vainer + */ +public class SkyFactory { + + /** + * Creates a sky using the given texture (cubemap or spheremap). + * + * @param assetManager The asset manager to use to load materials + * @param texture Texture to use for the sky + * @param normalScale The normal scale is multiplied by the 3D normal + * to get a texture coordinate. Use Vector3f.UNIT_XYZ to not apply + * and transformation to the normal. + * @param sphereMap The way the texture is used + * depends on this value:<br> + * <ul> + * <li>true: Its a Texture2D with the pixels arranged for + * <a href="http://en.wikipedia.org/wiki/Sphere_mapping">sphere mapping</a>.</li> + * <li>false: Its either a TextureCubeMap or Texture2D. If its a Texture2D + * then the image is taken from it and is inserted into a TextureCubeMap</li> + * </ul> + * @return A spatial representing the sky + */ + public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, boolean sphereMap) { + return createSky(assetManager, texture, normalScale, sphereMap, 10); + } + + /** + * Creates a sky using the given texture (cubemap or spheremap). + * + * @param assetManager The asset manager to use to load materials + * @param texture Texture to use for the sky + * @param normalScale The normal scale is multiplied by the 3D normal + * to get a texture coordinate. Use Vector3f.UNIT_XYZ to not apply + * and transformation to the normal. + * @param sphereMap The way the texture is used + * depends on this value:<br> + * <ul> + * <li>true: Its a Texture2D with the pixels arranged for + * <a href="http://en.wikipedia.org/wiki/Sphere_mapping">sphere mapping</a>.</li> + * <li>false: Its either a TextureCubeMap or Texture2D. If its a Texture2D + * then the image is taken from it and is inserted into a TextureCubeMap</li> + * </ul> + * @param sphereRadius If specified, this will be the sky sphere's radius. + * This should be the camera's near plane for optimal quality. + * @return A spatial representing the sky + */ + public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, boolean sphereMap, int sphereRadius) { + if (texture == null) { + throw new IllegalArgumentException("texture cannot be null"); + } + final Sphere sphereMesh = new Sphere(10, 10, sphereRadius, false, true); + + Geometry sky = new Geometry("Sky", sphereMesh); + sky.setQueueBucket(Bucket.Sky); + sky.setCullHint(Spatial.CullHint.Never); + sky.setModelBound(new BoundingSphere(Float.POSITIVE_INFINITY, Vector3f.ZERO)); + + Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md"); + + skyMat.setVector3("NormalScale", normalScale); + if (sphereMap) { + skyMat.setBoolean("SphereMap", sphereMap); + } else if (!(texture instanceof TextureCubeMap)) { + // make sure its a cubemap + Image img = texture.getImage(); + texture = new TextureCubeMap(); + texture.setImage(img); + } + skyMat.setTexture("Texture", texture); + sky.setMaterial(skyMat); + + return sky; + } + + private static void checkImage(Image image) { +// if (image.getDepth() != 1) +// throw new IllegalArgumentException("3D/Array images not allowed"); + + if (image.getWidth() != image.getHeight()) { + throw new IllegalArgumentException("Image width and height must be the same"); + } + + if (image.getMultiSamples() != 1) { + throw new IllegalArgumentException("Multisample textures not allowed"); + } + } + + private static void checkImagesForCubeMap(Image... images) { + if (images.length == 1) { + return; + } + + Format fmt = images[0].getFormat(); + int width = images[0].getWidth(); + int height = images[0].getHeight(); + + ByteBuffer data = images[0].getData(0); + int size = data != null ? data.capacity() : 0; + + checkImage(images[0]); + + for (int i = 1; i < images.length; i++) { + Image image = images[i]; + checkImage(images[i]); + if (image.getFormat() != fmt) { + throw new IllegalArgumentException("Images must have same format"); + } + if (image.getWidth() != width || image.getHeight() != height) { + throw new IllegalArgumentException("Images must have same resolution"); + } + ByteBuffer data2 = image.getData(0); + if (data2 != null){ + if (data2.capacity() != size) { + throw new IllegalArgumentException("Images must have same size"); + } + } + } + } + + public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down, Vector3f normalScale) { + return createSky(assetManager, west, east, north, south, up, down, normalScale, 10); + } + + public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down, Vector3f normalScale, int sphereRadius) { + final Sphere sphereMesh = new Sphere(10, 10, sphereRadius, false, true); + Geometry sky = new Geometry("Sky", sphereMesh); + sky.setQueueBucket(Bucket.Sky); + sky.setCullHint(Spatial.CullHint.Never); + sky.setModelBound(new BoundingSphere(Float.POSITIVE_INFINITY, Vector3f.ZERO)); + + Image westImg = west.getImage(); + Image eastImg = east.getImage(); + Image northImg = north.getImage(); + Image southImg = south.getImage(); + Image upImg = up.getImage(); + Image downImg = down.getImage(); + + checkImagesForCubeMap(westImg, eastImg, northImg, southImg, upImg, downImg); + + Image cubeImage = new Image(westImg.getFormat(), westImg.getWidth(), westImg.getHeight(), null); + + cubeImage.addData(westImg.getData(0)); + cubeImage.addData(eastImg.getData(0)); + + cubeImage.addData(downImg.getData(0)); + cubeImage.addData(upImg.getData(0)); + + cubeImage.addData(southImg.getData(0)); + cubeImage.addData(northImg.getData(0)); + + if (westImg.getEfficentData() != null){ + // also consilidate efficient data + ArrayList<Object> efficientData = new ArrayList<Object>(6); + efficientData.add(westImg.getEfficentData()); + efficientData.add(eastImg.getEfficentData()); + efficientData.add(downImg.getEfficentData()); + efficientData.add(upImg.getEfficentData()); + efficientData.add(southImg.getEfficentData()); + efficientData.add(northImg.getEfficentData()); + cubeImage.setEfficentData(efficientData); + } + + TextureCubeMap cubeMap = new TextureCubeMap(cubeImage); + cubeMap.setAnisotropicFilter(0); + cubeMap.setMagFilter(Texture.MagFilter.Bilinear); + cubeMap.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + cubeMap.setWrap(Texture.WrapMode.EdgeClamp); + + Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md"); + skyMat.setTexture("Texture", cubeMap); + skyMat.setVector3("NormalScale", normalScale); + sky.setMaterial(skyMat); + + return sky; + } + + public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down) { + return createSky(assetManager, west, east, north, south, up, down, Vector3f.UNIT_XYZ); + } + + public static Spatial createSky(AssetManager assetManager, Texture texture, boolean sphereMap) { + return createSky(assetManager, texture, Vector3f.UNIT_XYZ, sphereMap); + } + + public static Spatial createSky(AssetManager assetManager, String textureName, boolean sphereMap) { + TextureKey key = new TextureKey(textureName, true); + key.setGenerateMips(true); + key.setAsCube(!sphereMap); + Texture tex = assetManager.loadTexture(key); + return createSky(assetManager, tex, sphereMap); + } +} diff --git a/engine/src/core/com/jme3/util/SortUtil.java b/engine/src/core/com/jme3/util/SortUtil.java new file mode 100644 index 0000000..fabe3bf --- /dev/null +++ b/engine/src/core/com/jme3/util/SortUtil.java @@ -0,0 +1,352 @@ +/* + * 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.util; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Quick and merge sort implementations that create no garbage, unlike {@link + * Arrays#sort}. The merge sort is stable, the quick sort is not. + */ +public class SortUtil { + + /** + * The size at or below which we will use insertion sort because it's + * probably faster. + */ + private static final int INSERTION_SORT_THRESHOLD = 7; + + + /** + procedure optimizedGnomeSort(a[]) + pos := 1 + last := 0 + while pos < length(a) + if (a[pos] >= a[pos-1]) + if (last != 0) + pos := last + last := 0 + end if + pos := pos + 1 + else + swap a[pos] and a[pos-1] + if (pos > 1) + if (last == 0) + last := pos + end if + pos := pos - 1 + else + pos := pos + 1 + end if + end if + end while +end procedure + */ + + public static void gsort(Object[] a, Comparator comp) { + int pos = 1; + int last = 0; + int length = a.length; + + while (pos < length){ + if ( comp.compare(a[pos], a[pos-1]) >= 0 ){ + if (last != 0){ + pos = last; + last = 0; + } + pos ++; + }else{ + Object tmp = a[pos]; + a[pos] = a[pos-1]; + a[pos-1] = tmp; + + if (pos > 1){ + if (last == 0){ + last = pos; + } + pos --; + }else{ + pos ++; + } + } + } + +// int p = 0; +// int l = a.length; +// while (p < l) { +// int pm1 = p - 1; +// if (p == 0 || comp.compare(a[p], a[pm1]) >= 0) { +// p++; +// } else { +// Object t = a[p]; +// a[p] = a[pm1]; +// a[pm1] = t; +// p--; +// } +// } + } + + private static void test(Float[] original, Float[] sorted, Comparator<Float> ic) { + long time, dt; + + time = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + System.arraycopy(original, 0, sorted, 0, original.length); + gsort(sorted, ic); + } + dt = System.nanoTime() - time; + System.out.println("GSort " + (dt/1000000.0) + " ms"); + + time = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + System.arraycopy(original, 0, sorted, 0, original.length); + qsort(sorted, ic); + } + dt = System.nanoTime() - time; + System.out.println("QSort " + (dt/1000000.0) + " ms"); + + time = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + System.arraycopy(original, 0, sorted, 0, original.length); + msort(original, sorted, ic); + } + dt = System.nanoTime() - time; + System.out.println("MSort " + (dt/1000000.0) + " ms"); + + time = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + System.arraycopy(original, 0, sorted, 0, original.length); + Arrays.sort(sorted, ic); + } + dt = System.nanoTime() - time; + System.out.println("ASort " + (dt/1000000.0) + " ms"); + } + + public static void main(String[] args) { + Comparator<Float> ic = new Comparator<Float>() { + + public int compare(Float o1, Float o2) { + return (int) (o1 - o2); + } + }; + Float[] original = new Float[]{2f, 1f, 5f, 3f, 4f, 6f, 8f, 9f, + 11f, 10f, 12f, 13f, 14f, 15f, 7f, 19f, 20f, 18f, 16f, 17f, + 21f, 23f, 22f, 24f, 25f, 27f, 26f, 29f, 28f, 30f, 31f}; + Float[] sorted = new Float[original.length]; + + while (true) { + test(original, sorted, ic); + } + } + + /** + * Quick sorts the supplied array using the specified comparator. + */ + public static void qsort(Object[] a, Comparator comp) { + qsort(a, 0, a.length - 1, comp); + } + + /** + * Quick sorts the supplied array using the specified comparator. + * + * @param lo0 the index of the lowest element to include in the sort. + * @param hi0 the index of the highest element to include in the sort. + */ + @SuppressWarnings("unchecked") + public static void qsort(Object[] a, int lo0, int hi0, Comparator comp) { + // bail out if we're already done + if (hi0 <= lo0) { + return; + } + + // if this is a two element list, do a simple sort on it + Object t; + if (hi0 - lo0 == 1) { + // if they're not already sorted, swap them + if (comp.compare(a[hi0], a[lo0]) < 0) { + t = a[lo0]; + a[lo0] = a[hi0]; + a[hi0] = t; + } + return; + } + + // the middle element in the array is our partitioning element + Object mid = a[(lo0 + hi0) / 2]; + + // set up our partitioning boundaries + int lo = lo0 - 1, hi = hi0 + 1; + + // loop through the array until indices cross + for (;;) { + // find the first element that is greater than or equal to + // the partition element starting from the left Index. + while (comp.compare(a[++lo], mid) < 0); + + // find an element that is smaller than or equal to + // the partition element starting from the right Index. + while (comp.compare(mid, a[--hi]) < 0); + + // swap the two elements or bail out of the loop + if (hi > lo) { + t = a[lo]; + a[lo] = a[hi]; + a[hi] = t; + } else { + break; + } + } + + // if the right index has not reached the left side of array + // must now sort the left partition + if (lo0 < lo - 1) { + qsort(a, lo0, lo - 1, comp); + } + + // if the left index has not reached the right side of array + // must now sort the right partition + if (hi + 1 < hi0) { + qsort(a, hi + 1, hi0, comp); + } + } + + public static void qsort(int[] a, int lo0, int hi0, Comparator comp) { + // bail out if we're already done + if (hi0 <= lo0) { + return; + } + + // if this is a two element list, do a simple sort on it + int t; + if (hi0 - lo0 == 1) { + // if they're not already sorted, swap them + if (comp.compare(a[hi0], a[lo0]) < 0) { + t = a[lo0]; + a[lo0] = a[hi0]; + a[hi0] = t; + } + return; + } + + // the middle element in the array is our partitioning element + int mid = a[(lo0 + hi0) / 2]; + + // set up our partitioning boundaries + int lo = lo0 - 1, hi = hi0 + 1; + + // loop through the array until indices cross + for (;;) { + // find the first element that is greater than or equal to + // the partition element starting from the left Index. + while (comp.compare(a[++lo], mid) < 0); + + // find an element that is smaller than or equal to + // the partition element starting from the right Index. + while (comp.compare(mid, a[--hi]) < 0); + + // swap the two elements or bail out of the loop + if (hi > lo) { + t = a[lo]; + a[lo] = a[hi]; + a[hi] = t; + } else { + break; + } + } + + // if the right index has not reached the left side of array + // must now sort the left partition + if (lo0 < lo - 1) { + qsort(a, lo0, lo - 1, comp); + } + + // if the left index has not reached the right side of array + // must now sort the right partition + if (hi + 1 < hi0) { + qsort(a, hi + 1, hi0, comp); + } + } + + /** + * Merge sort + */ + public static void msort(Object[] src, Object[] dest, Comparator comp){ + msort(src, dest, 0, src.length - 1, comp); + } + + /** + * Merge sort + * + * @param src Source array + * @param dest Destination array + * @param low Index of beginning element + * @param high Index of end element + * @param comp Comparator + */ + public static void msort(Object[] src, Object[] dest, int low, int high, + Comparator comp) { + if(low < high) { + int center = (low + high) / 2; + msort(src, dest, low, center, comp); + msort(src, dest, center + 1, high, comp); + merge(src, dest, low, center + 1, high, comp); + } + } + + private static void merge(Object[] src, Object[] dest, + int low, int middle, int high, Comparator comp) { + int leftEnd = middle - 1; + int pos = low; + int numElements = high - low + 1; + + while (low <= leftEnd && middle <= high) { + if (comp.compare(src[low], src[middle]) <= 0) { + dest[pos++] = src[low++]; + } else { + dest[pos++] = src[middle++]; + } + } + + while (low <= leftEnd) { + dest[pos++] = src[low++]; + } + + while (middle <= high) { + dest[pos++] = src[middle++]; + } + + for (int i = 0; i < numElements; i++, high--) { + src[high] = dest[high]; + } + } +} diff --git a/engine/src/core/com/jme3/util/TangentBinormalGenerator.java b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java new file mode 100644 index 0000000..88f6822 --- /dev/null +++ b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java @@ -0,0 +1,739 @@ +/* + * 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.util; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.*; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.mesh.IndexBuffer; +import static com.jme3.util.BufferUtils.*; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Lex (Aleksey Nikiforov) + */ +public class TangentBinormalGenerator { + + private static final float ZERO_TOLERANCE = 0.0000001f; + private static final Logger log = Logger.getLogger( + TangentBinormalGenerator.class.getName()); + private static float toleranceAngle; + private static float toleranceDot; + + static { + setToleranceAngle(45); + } + + + private static class VertexInfo { + public final Vector3f position; + public final Vector3f normal; + public final ArrayList<Integer> indices = new ArrayList<Integer>(); + + public VertexInfo(Vector3f position, Vector3f normal) { + this.position = position; + this.normal = normal; + } + } + + /** Collects all the triangle data for one vertex. + */ + private static class VertexData { + public final ArrayList<TriangleData> triangles = new ArrayList<TriangleData>(); + + public VertexData() { } + } + + /** Keeps track of tangent, binormal, and normal for one triangle. + */ + public static class TriangleData { + public final Vector3f tangent; + public final Vector3f binormal; + public final Vector3f normal; + + public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) { + this.tangent = tangent; + this.binormal = binormal; + this.normal = normal; + } + } + + private static VertexData[] initVertexData(int size) { + VertexData[] vertices = new VertexData[size]; + for (int i = 0; i < size; i++) { + vertices[i] = new VertexData(); + } + return vertices; + } + + public static void generate(Mesh mesh) { + generate(mesh, true); + } + + public static void generate(Spatial scene) { + if (scene instanceof Node) { + Node node = (Node) scene; + for (Spatial child : node.getChildren()) { + generate(child); + } + } else { + Geometry geom = (Geometry) scene; + generate(geom.getMesh()); + } + } + + public static void generate(Mesh mesh, boolean approxTangents) { + int[] index = new int[3]; + Vector3f[] v = new Vector3f[3]; + Vector2f[] t = new Vector2f[3]; + for (int i = 0; i < 3; i++) { + v[i] = new Vector3f(); + t[i] = new Vector2f(); + } + + if (mesh.getBuffer(Type.Normal) == null) { + throw new IllegalArgumentException("The given mesh has no normal data!"); + } + + VertexData[] vertices; + switch (mesh.getMode()) { + case Triangles: + vertices = processTriangles(mesh, index, v, t); + break; + case TriangleStrip: + vertices = processTriangleStrip(mesh, index, v, t); + break; + case TriangleFan: + vertices = processTriangleFan(mesh, index, v, t); + break; + default: + throw new UnsupportedOperationException( + mesh.getMode() + " is not supported."); + } + + processTriangleData(mesh, vertices, approxTangents); + + //if the mesh has a bind pose, we need to generate the bind pose for the tangent buffer + if (mesh.getBuffer(Type.BindPosePosition) != null) { + + VertexBuffer tangents = mesh.getBuffer(Type.Tangent); + if (tangents != null) { + VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent); + bindTangents.setupData(Usage.CpuOnly, + 4, + Format.Float, + BufferUtils.clone(tangents.getData())); + + if (mesh.getBuffer(Type.BindPoseTangent) != null) { + mesh.clearBuffer(Type.BindPoseTangent); + } + mesh.setBuffer(bindTangents); + tangents.setUsage(Usage.Stream); + } + } + } + + private static VertexData[] processTriangles(Mesh mesh, + int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + if (mesh.getBuffer(Type.TexCoord) == null) { + throw new IllegalArgumentException("Can only generate tangents for " + + "meshes with texture coordinates"); + } + + FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); + + VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3); + + for (int i = 0; i < indexBuffer.size() / 3; i++) { + for (int j = 0; j < 3; j++) { + index[j] = indexBuffer.get(i * 3 + j); + populateFromBuffer(v[j], vertexBuffer, index[j]); + populateFromBuffer(t[j], textureBuffer, index[j]); + } + + TriangleData triData = processTriangle(index, v, t); + if (triData != null) { + vertices[index[0]].triangles.add(triData); + vertices[index[1]].triangles.add(triData); + vertices[index[2]].triangles.add(triData); + } + } + + return vertices; + } + + private static VertexData[] processTriangleStrip(Mesh mesh, + int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); + + VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3); + + index[0] = indexBuffer.get(0); + index[1] = indexBuffer.get(1); + + populateFromBuffer(v[0], vertexBuffer, index[0]); + populateFromBuffer(v[1], vertexBuffer, index[1]); + + populateFromBuffer(t[0], textureBuffer, index[0]); + populateFromBuffer(t[1], textureBuffer, index[1]); + + for (int i = 2; i < indexBuffer.size(); i++) { + index[2] = indexBuffer.get(i); + BufferUtils.populateFromBuffer(v[2], vertexBuffer, index[2]); + BufferUtils.populateFromBuffer(t[2], textureBuffer, index[2]); + + boolean isDegenerate = isDegenerateTriangle(v[0], v[1], v[2]); + TriangleData triData = processTriangle(index, v, t); + + if (triData != null && !isDegenerate) { + vertices[index[0]].triangles.add(triData); + vertices[index[1]].triangles.add(triData); + vertices[index[2]].triangles.add(triData); + } + + Vector3f vTemp = v[0]; + v[0] = v[1]; + v[1] = v[2]; + v[2] = vTemp; + + Vector2f tTemp = t[0]; + t[0] = t[1]; + t[1] = t[2]; + t[2] = tTemp; + + index[0] = index[1]; + index[1] = index[2]; + } + + return vertices; + } + + private static VertexData[] processTriangleFan(Mesh mesh, + int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); + + VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3); + + index[0] = indexBuffer.get(0); + index[1] = indexBuffer.get(1); + + populateFromBuffer(v[0], vertexBuffer, index[0]); + populateFromBuffer(v[1], vertexBuffer, index[1]); + + populateFromBuffer(t[0], textureBuffer, index[0]); + populateFromBuffer(t[1], textureBuffer, index[1]); + + for (int i = 2; i < vertexBuffer.capacity() / 3; i++) { + index[2] = indexBuffer.get(i); + populateFromBuffer(v[2], vertexBuffer, index[2]); + populateFromBuffer(t[2], textureBuffer, index[2]); + + TriangleData triData = processTriangle(index, v, t); + if (triData != null) { + vertices[index[0]].triangles.add(triData); + vertices[index[1]].triangles.add(triData); + vertices[index[2]].triangles.add(triData); + } + + Vector3f vTemp = v[1]; + v[1] = v[2]; + v[2] = vTemp; + + Vector2f tTemp = t[1]; + t[1] = t[2]; + t[2] = tTemp; + + index[1] = index[2]; + } + + return vertices; + } + + // check if the area is greater than zero + private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) { + return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0; + } + + public static TriangleData processTriangle(int[] index, + Vector3f[] v, Vector2f[] t) { + Vector3f edge1 = new Vector3f(); + Vector3f edge2 = new Vector3f(); + Vector2f edge1uv = new Vector2f(); + Vector2f edge2uv = new Vector2f(); + + Vector3f tangent = new Vector3f(); + Vector3f binormal = new Vector3f(); + Vector3f normal = new Vector3f(); + + t[1].subtract(t[0], edge1uv); + t[2].subtract(t[0], edge2uv); + float det = edge1uv.x * edge2uv.y - edge1uv.y * edge2uv.x; + + boolean normalize = false; + if (Math.abs(det) < ZERO_TOLERANCE) { + log.log(Level.WARNING, "Colinear uv coordinates for triangle " + + "[{0}, {1}, {2}]; tex0 = [{3}, {4}], " + + "tex1 = [{5}, {6}], tex2 = [{7}, {8}]", + new Object[]{index[0], index[1], index[2], + t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y}); + det = 1; + normalize = true; + } + + v[1].subtract(v[0], edge1); + v[2].subtract(v[0], edge2); + + tangent.set(edge1); + tangent.normalizeLocal(); + binormal.set(edge2); + binormal.normalizeLocal(); + + if (Math.abs(Math.abs(tangent.dot(binormal)) - 1) + < ZERO_TOLERANCE) { + log.log(Level.WARNING, "Vertices are on the same line " + + "for triangle [{0}, {1}, {2}].", + new Object[]{index[0], index[1], index[2]}); + } + + float factor = 1 / det; + tangent.x = (edge2uv.y * edge1.x - edge1uv.y * edge2.x) * factor; + tangent.y = (edge2uv.y * edge1.y - edge1uv.y * edge2.y) * factor; + tangent.z = (edge2uv.y * edge1.z - edge1uv.y * edge2.z) * factor; + if (normalize) { + tangent.normalizeLocal(); + } + + binormal.x = (edge1uv.x * edge2.x - edge2uv.x * edge1.x) * factor; + binormal.y = (edge1uv.x * edge2.y - edge2uv.x * edge1.y) * factor; + binormal.z = (edge1uv.x * edge2.z - edge2uv.x * edge1.z) * factor; + if (normalize) { + binormal.normalizeLocal(); + } + + tangent.cross(binormal, normal); + normal.normalizeLocal(); + + return new TriangleData( + tangent, + binormal, + normal); + } + + public static void setToleranceAngle(float angle) { + if (angle < 0 || angle > 179) { + throw new IllegalArgumentException( + "The angle must be between 0 and 179 degrees."); + } + toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD); + toleranceAngle = angle; + } + + + private static boolean approxEqual(Vector3f u, Vector3f v) { + float tolerance = 1E-4f; + return (FastMath.abs(u.x - v.x) < tolerance) && + (FastMath.abs(u.y - v.y) < tolerance) && + (FastMath.abs(u.z - v.z) < tolerance); + } + + private static ArrayList<VertexInfo> linkVertices(Mesh mesh) { + ArrayList<VertexInfo> vertexMap = new ArrayList<VertexInfo>(); + + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); + + Vector3f position = new Vector3f(); + Vector3f normal = new Vector3f(); + + final int size = vertexBuffer.capacity() / 3; + for (int i = 0; i < size; i++) { + + populateFromBuffer(position, vertexBuffer, i); + populateFromBuffer(normal, normalBuffer, i); + + boolean found = false; + + for (int j = 0; j < vertexMap.size(); j++) { + VertexInfo vertexInfo = vertexMap.get(j); + if (approxEqual(vertexInfo.position, position) && + approxEqual(vertexInfo.normal, normal)) + { + vertexInfo.indices.add(i); + found = true; + break; + } + } + + if (!found) { + VertexInfo vertexInfo = new VertexInfo(position.clone(), normal.clone()); + vertexInfo.indices.add(i); + vertexMap.add(vertexInfo); + } + } + + return vertexMap; + } + + private static void processTriangleData(Mesh mesh, VertexData[] vertices, + boolean approxTangent) + { + ArrayList<VertexInfo> vertexMap = linkVertices(mesh); + + FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); + + FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 4); +// FloatBuffer binormals = BufferUtils.createFloatBuffer(vertices.length * 3); + + Vector3f tangent = new Vector3f(); + Vector3f binormal = new Vector3f(); + Vector3f normal = new Vector3f(); + Vector3f givenNormal = new Vector3f(); + + Vector3f tangentUnit = new Vector3f(); + Vector3f binormalUnit = new Vector3f(); + + for (int k = 0; k < vertexMap.size(); k++) { + float wCoord = -1; + + VertexInfo vertexInfo = vertexMap.get(k); + + givenNormal.set(vertexInfo.normal); + givenNormal.normalizeLocal(); + + TriangleData firstTriangle = vertices[vertexInfo.indices.get(0)].triangles.get(0); + + // check tangent and binormal consistency + tangent.set(firstTriangle.tangent); + tangent.normalizeLocal(); + binormal.set(firstTriangle.binormal); + binormal.normalizeLocal(); + + for (int i : vertexInfo.indices) { + ArrayList<TriangleData> triangles = vertices[i].triangles; + + for (int j = 0; j < triangles.size(); j++) { + TriangleData triangleData = triangles.get(j); + + tangentUnit.set(triangleData.tangent); + tangentUnit.normalizeLocal(); + if (tangent.dot(tangentUnit) < toleranceDot) { + log.log(Level.WARNING, + "Angle between tangents exceeds tolerance " + + "for vertex {0}.", i); + break; + } + + if (!approxTangent) { + binormalUnit.set(triangleData.binormal); + binormalUnit.normalizeLocal(); + if (binormal.dot(binormalUnit) < toleranceDot) { + log.log(Level.WARNING, + "Angle between binormals exceeds tolerance " + + "for vertex {0}.", i); + break; + } + } + } + } + + + // find average tangent + tangent.set(0, 0, 0); + binormal.set(0, 0, 0); + + int triangleCount = 0; + for (int i : vertexInfo.indices) { + ArrayList<TriangleData> triangles = vertices[i].triangles; + triangleCount += triangles.size(); + + boolean flippedNormal = false; + for (int j = 0; j < triangles.size(); j++) { + TriangleData triangleData = triangles.get(j); + tangent.addLocal(triangleData.tangent); + binormal.addLocal(triangleData.binormal); + + if (givenNormal.dot(triangleData.normal) < 0) { + flippedNormal = true; + } + } + if (flippedNormal /*&& approxTangent*/) { + // Generated normal is flipped for this vertex, + // so binormal = normal.cross(tangent) will be flipped in the shader + // log.log(Level.WARNING, + // "Binormal is flipped for vertex {0}.", i); + + wCoord = 1; + } + } + + + int blameVertex = vertexInfo.indices.get(0); + + if (tangent.length() < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Shared tangent is zero for vertex {0}.", blameVertex); + // attempt to fix from binormal + if (binormal.length() >= ZERO_TOLERANCE) { + binormal.cross(givenNormal, tangent); + tangent.normalizeLocal(); + } // if all fails use the tangent from the first triangle + else { + tangent.set(firstTriangle.tangent); + } + } else { + tangent.divideLocal(triangleCount); + } + + tangentUnit.set(tangent); + tangentUnit.normalizeLocal(); + if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1) + < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Normal and tangent are parallel for vertex {0}.", blameVertex); + } + + + if (!approxTangent) { + if (binormal.length() < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Shared binormal is zero for vertex {0}.", blameVertex); + // attempt to fix from tangent + if (tangent.length() >= ZERO_TOLERANCE) { + givenNormal.cross(tangent, binormal); + binormal.normalizeLocal(); + } // if all fails use the binormal from the first triangle + else { + binormal.set(firstTriangle.binormal); + } + } else { + binormal.divideLocal(triangleCount); + } + + binormalUnit.set(binormal); + binormalUnit.normalizeLocal(); + if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1) + < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Normal and binormal are parallel for vertex {0}.", blameVertex); + } + + if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1) + < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Tangent and binormal are parallel for vertex {0}.", blameVertex); + } + } + + for (int i : vertexInfo.indices) { + if (approxTangent) { + // This calculation ensures that normal and tagent have a 90 degree angle. + // Removing this will lead to visual artifacts. + givenNormal.cross(tangent, binormal); + binormal.cross(givenNormal, tangent); + + tangent.normalizeLocal(); + + tangents.put((i * 4), tangent.x); + tangents.put((i * 4) + 1, tangent.y); + tangents.put((i * 4) + 2, tangent.z); + tangents.put((i * 4) + 3, wCoord); + } else { + tangents.put((i * 4), tangent.x); + tangents.put((i * 4) + 1, tangent.y); + tangents.put((i * 4) + 2, tangent.z); + tangents.put((i * 4) + 3, wCoord); + + //setInBuffer(binormal, binormals, i); + } + } + } + + mesh.setBuffer(Type.Tangent, 4, tangents); +// if (!approxTangent) mesh.setBuffer(Type.Binormal, 3, binormals); + } + + public static Mesh genTbnLines(Mesh mesh, float scale) { + if (mesh.getBuffer(Type.Tangent) == null) { + return genNormalLines(mesh, scale); + } else { + return genTangentLines(mesh, scale); + } + } + + public static Mesh genNormalLines(Mesh mesh, float scale) { + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); + + ColorRGBA originColor = ColorRGBA.White; + ColorRGBA normalColor = ColorRGBA.Blue; + + Mesh lineMesh = new Mesh(); + lineMesh.setMode(Mesh.Mode.Lines); + + Vector3f origin = new Vector3f(); + Vector3f point = new Vector3f(); + + FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 2); + FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 2); + + for (int i = 0; i < vertexBuffer.capacity() / 3; i++) { + populateFromBuffer(origin, vertexBuffer, i); + populateFromBuffer(point, normalBuffer, i); + + int index = i * 2; + + setInBuffer(origin, lineVertex, index); + setInBuffer(originColor, lineColor, index); + + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 1); + setInBuffer(normalColor, lineColor, index + 1); + } + + lineMesh.setBuffer(Type.Position, 3, lineVertex); + lineMesh.setBuffer(Type.Color, 4, lineColor); + + lineMesh.setStatic(); + lineMesh.setInterleaved(); + return lineMesh; + } + + private static Mesh genTangentLines(Mesh mesh, float scale) { + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); + FloatBuffer tangentBuffer = (FloatBuffer) mesh.getBuffer(Type.Tangent).getData(); + + FloatBuffer binormalBuffer = null; + if (mesh.getBuffer(Type.Binormal) != null) { + binormalBuffer = (FloatBuffer) mesh.getBuffer(Type.Binormal).getData(); + } + + ColorRGBA originColor = ColorRGBA.White; + ColorRGBA tangentColor = ColorRGBA.Red; + ColorRGBA binormalColor = ColorRGBA.Green; + ColorRGBA normalColor = ColorRGBA.Blue; + + Mesh lineMesh = new Mesh(); + lineMesh.setMode(Mesh.Mode.Lines); + + Vector3f origin = new Vector3f(); + Vector3f point = new Vector3f(); + Vector3f tangent = new Vector3f(); + Vector3f normal = new Vector3f(); + + IntBuffer lineIndex = BufferUtils.createIntBuffer(vertexBuffer.capacity() / 3 * 6); + FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 4); + FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 4); + + boolean hasParity = mesh.getBuffer(Type.Tangent).getNumComponents() == 4; + float tangentW = 1; + + for (int i = 0; i < vertexBuffer.capacity() / 3; i++) { + populateFromBuffer(origin, vertexBuffer, i); + populateFromBuffer(normal, normalBuffer, i); + + if (hasParity) { + tangent.x = tangentBuffer.get(i * 4); + tangent.y = tangentBuffer.get(i * 4 + 1); + tangent.z = tangentBuffer.get(i * 4 + 2); + tangentW = tangentBuffer.get(i * 4 + 3); + } else { + populateFromBuffer(tangent, tangentBuffer, i); + } + + int index = i * 4; + + int id = i * 6; + lineIndex.put(id, index); + lineIndex.put(id + 1, index + 1); + lineIndex.put(id + 2, index); + lineIndex.put(id + 3, index + 2); + lineIndex.put(id + 4, index); + lineIndex.put(id + 5, index + 3); + + setInBuffer(origin, lineVertex, index); + setInBuffer(originColor, lineColor, index); + + point.set(tangent); + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 1); + setInBuffer(tangentColor, lineColor, index + 1); + + // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w + + if (binormalBuffer == null) { + normal.cross(tangent, point); + point.multLocal(-tangentW); + point.normalizeLocal(); + } else { + populateFromBuffer(point, binormalBuffer, i); + } + + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 2); + setInBuffer(binormalColor, lineColor, index + 2); + + point.set(normal); + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 3); + setInBuffer(normalColor, lineColor, index + 3); + } + + lineMesh.setBuffer(Type.Index, 1, lineIndex); + lineMesh.setBuffer(Type.Position, 3, lineVertex); + lineMesh.setBuffer(Type.Color, 4, lineColor); + + lineMesh.setStatic(); + lineMesh.setInterleaved(); + return lineMesh; + } +} diff --git a/engine/src/core/com/jme3/util/TempVars.java b/engine/src/core/com/jme3/util/TempVars.java new file mode 100644 index 0000000..2fdea36 --- /dev/null +++ b/engine/src/core/com/jme3/util/TempVars.java @@ -0,0 +1,221 @@ +/*
+ * 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.util;
+
+import com.jme3.collision.bih.BIHNode.BIHStackData;
+import com.jme3.math.*;
+import com.jme3.scene.Spatial;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+
+/**
+ * Temporary variables assigned to each thread. Engine classes may access
+ * these temp variables with TempVars.get(), all retrieved TempVars
+ * instances must be returned via TempVars.release().
+ * This returns an available instance of the TempVar class ensuring this
+ * particular instance is never used elsewhere in the mean time.
+ */
+public class TempVars {
+
+ /**
+ * Allow X instances of TempVars in a single thread.
+ */
+ private static final int STACK_SIZE = 5;
+
+ /**
+ * <code>TempVarsStack</code> contains a stack of TempVars.
+ * Every time TempVars.get() is called, a new entry is added to the stack,
+ * and the index incremented.
+ * When TempVars.release() is called, the entry is checked against
+ * the current instance and then the index is decremented.
+ */
+ private static class TempVarsStack {
+
+ int index = 0;
+ TempVars[] tempVars = new TempVars[STACK_SIZE];
+ }
+ /**
+ * ThreadLocal to store a TempVarsStack for each thread.
+ * This ensures each thread has a single TempVarsStack that is
+ * used only in method calls in that thread.
+ */
+ private static final ThreadLocal<TempVarsStack> varsLocal = new ThreadLocal<TempVarsStack>() {
+
+ @Override
+ public TempVarsStack initialValue() {
+ return new TempVarsStack();
+ }
+ };
+ /**
+ * This instance of TempVars has been retrieved but not released yet.
+ */
+ private boolean isUsed = false;
+
+ private TempVars() {
+ }
+
+ /**
+ * Acquire an instance of the TempVar class.
+ * You have to release the instance after use by calling the
+ * release() method.
+ * If more than STACK_SIZE (currently 5) instances are requested
+ * in a single thread then an ArrayIndexOutOfBoundsException will be thrown.
+ *
+ * @return A TempVar instance
+ */
+ public static TempVars get() {
+ TempVarsStack stack = varsLocal.get();
+
+ TempVars instance = stack.tempVars[stack.index];
+
+ if (instance == null) {
+ // Create new
+ instance = new TempVars();
+
+ // Put it in there
+ stack.tempVars[stack.index] = instance;
+ }
+
+ stack.index++;
+
+ instance.isUsed = true;
+
+ return instance;
+ }
+
+ /**
+ * Releases this instance of TempVars.
+ * Once released, the contents of the TempVars are undefined.
+ * The TempVars must be released in the opposite order that they are retrieved,
+ * e.g. Acquiring vars1, then acquiring vars2, vars2 MUST be released
+ * first otherwise an exception will be thrown.
+ */
+ public void release() {
+ if (!isUsed) {
+ throw new IllegalStateException("This instance of TempVars was already released!");
+ }
+
+ isUsed = false;
+
+ TempVarsStack stack = varsLocal.get();
+
+ // Return it to the stack
+ stack.index--;
+
+ // Check if it is actually there
+ if (stack.tempVars[stack.index] != this) {
+ throw new IllegalStateException("An instance of TempVars has not been released in a called method!");
+ }
+ }
+ /**
+ * For interfacing with OpenGL in Renderer.
+ */
+ public final IntBuffer intBuffer1 = BufferUtils.createIntBuffer(1);
+ public final IntBuffer intBuffer16 = BufferUtils.createIntBuffer(16);
+ public final FloatBuffer floatBuffer16 = BufferUtils.createFloatBuffer(16);
+ /**
+ * Skinning buffers
+ */
+ public final float[] skinPositions = new float[512 * 3];
+ public final float[] skinNormals = new float[512 * 3];
+ //tangent buffer as 4 components by elements
+ public final float[] skinTangents = new float[512 * 4];
+ /**
+ * Fetching triangle from mesh
+ */
+ public final Triangle triangle = new Triangle();
+ /**
+ * Color
+ */
+ public final ColorRGBA color = new ColorRGBA();
+ /**
+ * General vectors.
+ */
+ public final Vector3f vect1 = new Vector3f();
+ public final Vector3f vect2 = new Vector3f();
+ public final Vector3f vect3 = new Vector3f();
+ public final Vector3f vect4 = new Vector3f();
+ public final Vector3f vect5 = new Vector3f();
+ public final Vector3f vect6 = new Vector3f();
+ public final Vector3f vect7 = new Vector3f();
+ //seems the maximum number of vector used is 7 in com.jme3.bounding.java
+ public final Vector3f vect8 = new Vector3f();
+ public final Vector3f vect9 = new Vector3f();
+ public final Vector3f vect10 = new Vector3f();
+ public final Vector4f vect4f = new Vector4f();
+ public final Vector3f[] tri = {new Vector3f(),
+ new Vector3f(),
+ new Vector3f()};
+ /**
+ * 2D vector
+ */
+ public final Vector2f vect2d = new Vector2f();
+ public final Vector2f vect2d2 = new Vector2f();
+ /**
+ * General matrices.
+ */
+ public final Matrix3f tempMat3 = new Matrix3f();
+ public final Matrix4f tempMat4 = new Matrix4f();
+ public final Matrix4f tempMat42 = new Matrix4f();
+ /**
+ * General quaternions.
+ */
+ public final Quaternion quat1 = new Quaternion();
+ public final Quaternion quat2 = new Quaternion();
+ /**
+ * Eigen
+ */
+ public final Eigen3f eigen = new Eigen3f();
+ /**
+ * Plane
+ */
+ public final Plane plane = new Plane();
+ /**
+ * BoundingBox ray collision
+ */
+ public final float[] fWdU = new float[3];
+ public final float[] fAWdU = new float[3];
+ public final float[] fDdU = new float[3];
+ public final float[] fADdU = new float[3];
+ public final float[] fAWxDdU = new float[3];
+ /**
+ * Maximum tree depth .. 32 levels??
+ */
+ public final Spatial[] spatialStack = new Spatial[32];
+ public final float[] matrixWrite = new float[16];
+ /**
+ * BIHTree
+ */
+ public final float[] bihSwapTmp = new float[9];
+ public final ArrayList<BIHStackData> bihStack = new ArrayList<BIHStackData>();
+}
diff --git a/engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java b/engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java new file mode 100644 index 0000000..6dfa6b8 --- /dev/null +++ b/engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java @@ -0,0 +1,92 @@ +package com.jme3.util.blockparser; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +public class BlockLanguageParser { + + private Reader reader; + private ArrayList<Statement> statementStack = new ArrayList<Statement>(); + private Statement lastStatement; + private int lineNumber = 1; + + private BlockLanguageParser(){ + } + + private void reset(){ + statementStack.clear(); + statementStack.add(new Statement(0, "<root>")); + lastStatement = null; + lineNumber = 1; + } + + private void pushStatement(StringBuilder buffer){ + String content = buffer.toString().trim(); + if (content.length() > 0){ + // push last statement onto the list + lastStatement = new Statement(lineNumber, content); + + Statement parent = statementStack.get(statementStack.size()-1); + parent.addStatement(lastStatement); + + buffer.setLength(0); + } + } + + private void load(InputStream in) throws IOException{ + reset(); + + reader = new InputStreamReader(in); + + StringBuilder buffer = new StringBuilder(); + boolean insideComment = false; + char lastChar = '\0'; + + while (true){ + int ci = reader.read(); + char c = (char) ci; + if (c == '\r'){ + continue; + } + if (insideComment && c == '\n'){ + insideComment = false; + }else if (c == '/' && lastChar == '/'){ + buffer.deleteCharAt(buffer.length()-1); + insideComment = true; + pushStatement(buffer); + lastChar = '\0'; + }else if (!insideComment){ + if (ci == -1 || c == '{' || c == '}' || c == '\n' || c == ';'){ + pushStatement(buffer); + lastChar = '\0'; + if (c == '{'){ + // push last statement onto the stack + statementStack.add(lastStatement); + continue; + }else if (c == '}'){ + // pop statement from stack + statementStack.remove(statementStack.size()-1); + continue; + }else if (c == '\n'){ + lineNumber++; + }else if (ci == -1){ + break; + } + }else{ + buffer.append(c); + lastChar = c; + } + } + } + } + + public static List<Statement> parse(InputStream in) throws IOException { + BlockLanguageParser parser = new BlockLanguageParser(); + parser.load(in); + return parser.statementStack.get(0).getContents(); + } +} diff --git a/engine/src/core/com/jme3/util/blockparser/Statement.java b/engine/src/core/com/jme3/util/blockparser/Statement.java new file mode 100644 index 0000000..d1309ad --- /dev/null +++ b/engine/src/core/com/jme3/util/blockparser/Statement.java @@ -0,0 +1,61 @@ +package com.jme3.util.blockparser; + +import java.util.ArrayList; +import java.util.List; + +public class Statement { + + private int lineNumber; + private String line; + private List<Statement> contents = new ArrayList<Statement>(); + + Statement(int lineNumber, String line) { + this.lineNumber = lineNumber; + this.line = line; + } + + void addStatement(Statement statement){ +// if (contents == null){ +// contents = new ArrayList<Statement>(); +// } + contents.add(statement); + } + + public int getLineNumber(){ + return lineNumber; + } + + public String getLine() { + return line; + } + + public List<Statement> getContents() { + return contents; + } + + private String getIndent(int indent){ + return " ".substring(0, indent); + } + + private String toString(int indent){ + StringBuilder sb = new StringBuilder(); + sb.append(getIndent(indent)); + sb.append(line); + if (contents != null){ + sb.append(" {\n"); + for (Statement statement : contents){ + sb.append(statement.toString(indent+4)); + sb.append("\n"); + } + sb.append(getIndent(indent)); + sb.append("}"); + } + return sb.toString(); + } + + @Override + public String toString(){ + return toString(0); + } + +} diff --git a/engine/src/core/com/jme3/util/xml/SAXUtil.java b/engine/src/core/com/jme3/util/xml/SAXUtil.java new file mode 100644 index 0000000..1ac4936 --- /dev/null +++ b/engine/src/core/com/jme3/util/xml/SAXUtil.java @@ -0,0 +1,140 @@ +/* + * 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.util.xml; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +/** + * Utility methods for parsing XML data using SAX. + */ +public final class SAXUtil { + + /** + * Parses an integer from a string, if the string is null returns + * def. + * + * @param i + * @param def + * @return + * @throws SAXException + */ + public static int parseInt(String i, int def) throws SAXException{ + if (i == null) + return def; + else{ + try { + return Integer.parseInt(i); + } catch (NumberFormatException ex){ + throw new SAXException("Expected an integer, got '"+i+"'"); + } + } + } + + public static int parseInt(String i) throws SAXException{ + if (i == null) + throw new SAXException("Expected an integer"); + else{ + try { + return Integer.parseInt(i); + } catch (NumberFormatException ex){ + throw new SAXException("Expected an integer, got '"+i+"'"); + } + } + } + + public static float parseFloat(String f, float def) throws SAXException{ + if (f == null) + return def; + else{ + try { + return Float.parseFloat(f); + } catch (NumberFormatException ex){ + throw new SAXException("Expected a decimal, got '"+f+"'"); + } + } + } + + public static float parseFloat(String f) throws SAXException{ + if (f == null) + throw new SAXException("Expected a decimal"); + else{ + try { + return Float.parseFloat(f); + } catch (NumberFormatException ex){ + throw new SAXException("Expected a decimal, got '"+f+"'"); + } + } + } + + public static boolean parseBool(String bool, boolean def) throws SAXException{ + if (bool == null || bool.equals("")) + return def; + else + return Boolean.valueOf(bool); + //else + //else + // throw new SAXException("Expected a boolean, got'"+bool+"'"); + } + + public static String parseString(String str, String def){ + if (str == null) + return def; + else + return str; + } + + public static String parseString(String str) throws SAXException{ + if (str == null) + throw new SAXException("Expected a string"); + else + return str; + } + + public static Vector3f parseVector3(Attributes attribs) throws SAXException{ + float x = parseFloat(attribs.getValue("x")); + float y = parseFloat(attribs.getValue("y")); + float z = parseFloat(attribs.getValue("z")); + return new Vector3f(x,y,z); + } + + public static ColorRGBA parseColor(Attributes attribs) throws SAXException{ + float r = parseFloat(attribs.getValue("r")); + float g = parseFloat(attribs.getValue("g")); + float b = parseFloat(attribs.getValue("b")); + return new ColorRGBA(r, g, b, 1f); + } + +} |