aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/util
diff options
context:
space:
mode:
authorScott Barta <sbarta@google.com>2012-03-01 12:35:35 -0800
committerScott Barta <sbarta@google.com>2012-03-01 12:40:08 -0800
commit59b2e6871c65f58fdad78cd7229c292f6a177578 (patch)
tree2d4e7bfc05b93f40b34675d77e403dd1c25efafd /engine/src/core/com/jme3/util
parentf9b30489e75ac1eabc365064959804e99534f7ab (diff)
downloadjmonkeyengine-59b2e6871c65f58fdad78cd7229c292f6a177578.tar.gz
Adds the jMonkeyEngine library to the build.
Adds the jMonkeyEngine open source 3D game engine to the build. This is built as a static library and is only used by the Finsky client. Change-Id: I06a3f054df7b8a67757267d884854f70c5a16ca0
Diffstat (limited to 'engine/src/core/com/jme3/util')
-rw-r--r--engine/src/core/com/jme3/util/BufferUtils.java1196
-rw-r--r--engine/src/core/com/jme3/util/IntMap.java308
-rw-r--r--engine/src/core/com/jme3/util/JmeFormatter.java93
-rw-r--r--engine/src/core/com/jme3/util/ListMap.java317
-rw-r--r--engine/src/core/com/jme3/util/LittleEndien.java160
-rw-r--r--engine/src/core/com/jme3/util/NativeObject.java172
-rw-r--r--engine/src/core/com/jme3/util/NativeObjectManager.java148
-rw-r--r--engine/src/core/com/jme3/util/PlaceholderAssets.java72
-rw-r--r--engine/src/core/com/jme3/util/SafeArrayList.java402
-rw-r--r--engine/src/core/com/jme3/util/SkyFactory.java214
-rw-r--r--engine/src/core/com/jme3/util/SortUtil.java352
-rw-r--r--engine/src/core/com/jme3/util/TangentBinormalGenerator.java739
-rw-r--r--engine/src/core/com/jme3/util/TempVars.java221
-rw-r--r--engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java92
-rw-r--r--engine/src/core/com/jme3/util/blockparser/Statement.java61
-rw-r--r--engine/src/core/com/jme3/util/xml/SAXUtil.java140
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);
+ }
+
+}