Saving memory, loading time of animation

Animation uses a large number of Vector3f/Quaternion instances.

So it is the one of the main reason for increasing loading time.

And always there are many same values for translation/rotation values in an animation.

I suggest using primitive array and indexing for Vector3f/Quaternion.



[patch]

Index: src/core/com/jme3/animation/CompactQuaternionArray.java

===================================================================

— src/core/com/jme3/animation/CompactQuaternionArray.java (revision 0)

+++ src/core/com/jme3/animation/CompactQuaternionArray.java (revision 0)

@@ -0,0 +1,64 @@

+package com.jme3.animation;

+

+import java.io.IOException;

+

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.export.Savable;

+import com.jme3.math.Quaternion;

+

+/**

    • Quaternion[] is indexed and stored in primitive float[]
    • @author Lim, YongHoon
  • */

    +public class CompactQuaternionArray extends CompactFloatArray<Quaternion> implements Savable {

    +
  • public CompactQuaternionArray() {
  • }

    +
  • public CompactQuaternionArray(float[] dataArray, int[] index) {
  •   super(dataArray, index);<br />
    
  • }

    +
  • @Override
  • public Quaternion get(float[] arr, int[] index, int i, Quaternion store) {
  •    int j = getDataIndex(index, i);<br />
    
  •    store.set(arr[j], arr[j+1], arr[j+2], arr[j+3]);<br />
    
  •    return store;<br />
    
  • }

    +
  • @Override
  • public void set(float[] arr, int[] index, int i, Quaternion value) {
  •    int j = getDataIndex(index, i);<br />
    
  •    arr[j] = value.getX();<br />
    
  •    arr[j+1] = value.getY();<br />
    
  •    arr[j+2] = value.getZ();<br />
    
  •    arr[j+3] = value.getW();<br />
    
  • }

    +
  • @Override
  • protected final int getTupleSize() {
  •   return 4;<br />
    
  • }

    +
  • @Override
  • protected final Class<Quaternion> getElementClass() {
  •   return Quaternion.class;<br />
    
  • }

    +
  • @Override
  • public void write(JmeExporter ex) throws IOException {
  •    OutputCapsule out = ex.getCapsule(this);<br />
    
  •    out.write(array, &quot;array&quot;, null);<br />
    
  •    out.write(index, &quot;index&quot;, null);<br />
    
  • }

    +
  • @Override
  • public void read(JmeImporter im) throws IOException {
  •    InputCapsule in = im.getCapsule(this);<br />
    
  •    array = in.readFloatArray(&quot;array&quot;, null);<br />
    
  •    index = in.readIntArray(&quot;index&quot;, null);<br />
    
  • }

    +}

    Index: src/core/com/jme3/animation/BoneTrack.java

    ===================================================================

    — src/core/com/jme3/animation/BoneTrack.java (revision 6654)

    +++ src/core/com/jme3/animation/BoneTrack.java (working copy)

    @@ -55,13 +55,15 @@

    /**
  • Transforms and times for track.

    */
  • private Vector3f[] translations;
  • private Quaternion[] rotations;
  • private CompactVector3Array translations;
  • private CompactQuaternionArray rotations;

    private float[] times;



    // temp vectors for interpolation

    private transient final Vector3f tempV = new Vector3f();

    private transient final Quaternion tempQ = new Quaternion();
  • private transient final Vector3f tempV2 = new Vector3f();
  • private transient final Quaternion tempQ2 = new Quaternion();



    /**
  • Serialization-only. Do not use.

    @@ -83,7 +85,7 @@

    }



    public Quaternion[] getRotations() {
  •    return rotations;<br />
    
  •    return rotations.toObjectArray();<br />
    

}



public float[] getTimes() {

@@ -91,7 +93,7 @@

}



public Vector3f[] getTranslations() {

  •    return translations;<br />
    
  •    return translations.toObjectArray();<br />
    

}



public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations){

@@ -101,8 +103,10 @@

assert (times.length == translations.length) && (times.length == rotations.length);



this.times = times;

  •    this.translations = translations;<br />
    
  •    this.rotations = rotations;<br />
    
  •    this.translations = new CompactVector3Array();<br />
    
  •    this.translations.add(translations);<br />
    
  •    this.rotations = new CompactQuaternionArray();<br />
    
  •    this.rotations.add(rotations);<br />
    

}



/**

@@ -115,11 +119,11 @@



int lastFrame = times.length - 1;

if (time < 0 || lastFrame == 0){

  •        tempQ.set(rotations[0]);<br />
    
  •        tempV.set(translations[0]);<br />
    
  •        rotations.get(0, tempQ);<br />
    
  •        translations.get(0, tempV);<br />
    

}else if (time >= times[lastFrame]){

  •        tempQ.set(rotations[lastFrame]);<br />
    
  •        tempV.set(translations[lastFrame]);<br />
    
  •        rotations.get(lastFrame, tempQ);<br />
    
  •        translations.get(lastFrame, tempV);<br />
    

}else{

int startFrame = 0;

int endFrame = 1;

@@ -133,8 +137,12 @@

float blend = (time - times[startFrame])

/ (times[endFrame] - times[startFrame]);


  •        tempQ.slerp(rotations[startFrame], rotations[endFrame], blend);<br />
    
  •        tempV.interpolate(translations[startFrame], translations[endFrame], blend);<br />
    
  •        rotations.get(startFrame, tempQ);<br />
    
  •        translations.get(startFrame, tempV);<br />
    
  •        rotations.get(endFrame, tempQ2);<br />
    
  •        translations.get(endFrame, tempV2);<br />
    
  •        tempQ.slerp(tempQ2, blend);<br />
    
  •        tempV.interpolate(tempV2, blend);<br />
    

}



if (weight != 1f){

@@ -161,17 +169,9 @@

InputCapsule ic = im.getCapsule(this);

targetBoneIndex = ic.readInt("boneIndex", 0);


  •    Savable[] sav = ic.readSavableArray(&quot;translations&quot;, null);<br />
    
  •    if (sav != null){<br />
    
  •        translations = new Vector3f[sav.length];<br />
    
  •        System.arraycopy(sav, 0, translations, 0, sav.length);<br />
    
  •    }<br />
    
  •    translations = (CompactVector3Array) ic.readSavable(&quot;translations&quot;, null);<br />
    

- sav = ic.readSavableArray("rotations", null);
- if (sav != null){
- rotations = new Quaternion[sav.length];
- System.arraycopy(sav, 0, rotations, 0, sav.length);
- }
+ rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
times = ic.readFloatArray("times", null);
}

Index: src/core/com/jme3/animation/CompactVector3Array.java
===================================================================
--- src/core/com/jme3/animation/CompactVector3Array.java (revision 0)
+++ src/core/com/jme3/animation/CompactVector3Array.java (revision 0)
@@ -0,0 +1,62 @@
+package com.jme3.animation;
+
+import java.io.IOException;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.Vector3f;
+
+/**
+ * @author Lim, YongHoon
+ */
+public class CompactVector3Array extends CompactFloatArray<Vector3f> implements Savable {
+
+ public CompactVector3Array() {
+ }
+
+ public CompactVector3Array(float[] dataArray, int[] index) {
+ super(dataArray, index);
+ }
+
+ @Override
+ protected Vector3f get(float[] arr, int[] index, int i, Vector3f store) {
+ int j = getDataIndex(index, i);
+ store.set(arr[j], arr[j+1], arr[j+2]);
+ return store;
+ }
+
+ @Override
+ protected void set(float[] arr, int[] index, int i, Vector3f value) {
+ int j = getDataIndex(index, i);
+ arr[j] = value.getX();
+ arr[j+1] = value.getY();
+ arr[j+2] = value.getZ();
+ }
+
+ @Override
+ protected final int getTupleSize() {
+ return 3;
+ }
+
+ @Override
+ protected final Class<Vector3f> getElementClass() {
+ return Vector3f.class;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule out = ex.getCapsule(this);
+ out.write(array, "array", null);
+ out.write(index, "index", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ array = in.readFloatArray("array", null);
+ index = in.readIntArray("index", null);
+ }
+}
Index: src/core/com/jme3/animation/CompactFloatArray.java
===================================================================
--- src/core/com/jme3/animation/CompactFloatArray.java (revision 0)
+++ src/core/com/jme3/animation/CompactFloatArray.java (revision 0)
@@ -0,0 +1,147 @@
+package com.jme3.animation;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Object is indexed and stored in primitive float[]
+ * @author Lim, YongHoon
+ * @param <T>
+ */
+public abstract class CompactFloatArray<T> {
+
+ public CompactFloatArray() {}
+
+ public CompactFloatArray(float[] arr, int[] index) {
+ this.array = arr;
+ this.index = index;
+ }
+
+ protected int getArraySize() {
+ return array.length;
+ }
+
+ private Map<T,Integer> indexPool = new HashMap<T, Integer>();
+
+ protected int[] index;
+ protected float[] array;
+
+
+ public void add(T... objArray) {
+ if (array != null) {
+ throw new RuntimeException("Already settled");
+ }
+ if (objArray == null || objArray.length == 0)
+ return;
+ int base = 0;
+ if (index == null) {
+ index = new int[objArray.length];
+ } else {
+ base = index.length;
+ index = Arrays.copyOf(index, base+objArray.length);
+ }
+ for (int j=0; j < objArray.length; j++) {
+ T obj = objArray[j];
+ if (obj == null) {
+ index[base+j] = -1;
+ } else {
+ Integer i = indexPool.get(obj);
+ if (i == null) {
+ i = indexPool.size();
+ indexPool.put(obj, i);
+ }
+ index[base+j] = i;
+ }
+ }
+ }
+
+ /**
+ * @param i
+ * @param value
+ * @author mulova
+ */
+ public final void set(int i, T value) {
+ set(getPrimitiveArray(), index, i, value);
+ }
+
+ public final T get(int i, T store) {
+ return get(getPrimitiveArray(), index, i, store);
+ }
+
+ public final float[] getPrimitiveArray() {
+ if (array == null) {
+ array = new float[indexPool.size()*getTupleSize()];
+ for (Map.Entry<T, Integer> entry : indexPool.entrySet()) {
+ int i = entry.getValue();
+ T obj = entry.getKey();
+ set(array, null, i, obj);
+ }
+ }
+ return array;
+ }
+
+ public final int[] getIndex(T... objArray) {
+ int[] index = new int[objArray.length];
+ for (int i = 0; i < index.length; i++) {
+ T obj = objArray;
+ index = obj!=null? indexPool.get(obj): -1;
+ }
+ return index;
+ }
+
+ public int getObjectArraySize() {
+ assert getArraySize()%getTupleSize() == 0;
+ return index!=null? index.length: getArraySize()/getTupleSize();
+ }
+
+ public int getDataIndex(int[] index, int i) {
+ return index!=null ? index*getTupleSize(): i*getTupleSize();
+ }
+
+ public final float[] toPrimitiveArray(T[] array) {
+ if (array == null) return null;
+ int end = array.length-1;
+ while (array[end] == null) {
+ end--;
+ }
+ int size = (end+1)*getTupleSize();
+ float[] primitive = new float[size];
+ for (int i = 0; i <= end; i++) {
+ set(primitive, null, i, array);
+ }
+ return primitive;
+ }
+
+ public final T[] toObjectArray() {
+ try {
+ final int size = getObjectArraySize();
+ T[] objArr = (T[]) Array.newInstance(getElementClass(), size);
+ for (int i = 0; i < objArr.length; i++) {
+ objArr = getElementClass().newInstance();
+ get(array, index, i, objArr);
+ }
+ return objArr;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ protected abstract void set(float[] array, int[] index, int i, T value);
+
+ protected abstract T get(float[] array, int[] index, int i, T store);
+
+ protected abstract int getTupleSize();
+
+ protected abstract Class<T> getElementClass();
+
+ public void dispose() {
+ indexPool.clear();
+ array = null;
+ index = null;
+ }
+
+}

[/patch]
1 Like

wow big change.

Looks good, did you test testComplexOgreAnimation?

I tested TestAnimBlendBug, TestOgreAnim, TestOgreComplexAnim and worked well

perfect

committed in svn. rev. 6670