commit d07f29503fa133d7b0da43df0435f65d4668e6f2
Author: Leszek Koltunski <leszek@distoretedandroid.org>
Date:   Fri Jun 10 12:30:24 2016 +0100

    Improve aborting Effects.

diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
index d3bfcc7..4599082 100644
--- a/src/main/java/org/distorted/library/Distorted.java
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -108,11 +108,11 @@ public class Distorted
       int realMaxV = (maxV-11)/4;   // adjust this in case of changes to the shaders...
       int realMaxF = (maxF- 2)/4;   //
     
-      if( EffectListVertex.getMax() > realMaxV )
+      if( EffectQueueVertex.getMax() > realMaxV )
         {
         throw new VertexUniformsException("Too many effects in the vertex shader, max is "+realMaxV, realMaxV);
         }
-      if( EffectListFragment.getMax() > realMaxF )
+      if( EffectQueueFragment.getMax() > realMaxF )
         {
         throw new FragmentUniformsException("Too many effects in the fragment shader, max is "+realMaxF, realMaxF);
         }
@@ -204,7 +204,7 @@ public class Distorted
    
     switch(type)
       {
-      case GLES20.GL_VERTEX_SHADER  : header += ("#define NUM_VERTEX "  +EffectListVertex.getMax()+"\n");
+      case GLES20.GL_VERTEX_SHADER  : header += ("#define NUM_VERTEX "  + EffectQueueVertex.getMax()+"\n");
      
                                       for(EffectNames name: EffectNames.values() )
                                         {
@@ -212,7 +212,7 @@ public class Distorted
                                         header += ("#define "+name.name()+" "+name.ordinal()+"\n");  
                                         }
                                       break;
-      case GLES20.GL_FRAGMENT_SHADER: header += ("#define NUM_FRAGMENT "+EffectListFragment.getMax()+"\n");
+      case GLES20.GL_FRAGMENT_SHADER: header += ("#define NUM_FRAGMENT "+ EffectQueueFragment.getMax()+"\n");
      
                                       for(EffectNames name: EffectNames.values() )
                                         {
@@ -342,9 +342,9 @@ public class Distorted
     mNormalH         = GLES20.glGetAttribLocation( mProgramH, "a_Normal"); 
     mTextureCoordH   = GLES20.glGetAttribLocation( mProgramH, "a_TexCoordinate");
     
-    EffectListFragment.getUniforms(mProgramH);
-    EffectListVertex.getUniforms(mProgramH);
-    EffectListMatrix.getUniforms(mProgramH);
+    EffectQueueFragment.getUniforms(mProgramH);
+    EffectQueueVertex.getUniforms(mProgramH);
+    EffectQueueMatrix.getUniforms(mProgramH);
     
     GLES20.glEnableVertexAttribArray(mPositionH);        
     GLES20.glEnableVertexAttribArray(mColorH);
@@ -379,9 +379,9 @@ public class Distorted
     DistortedObjectList.release();
     DistortedNode.release();
 
-    EffectListVertex.reset();
-    EffectListFragment.reset();
-    EffectListMatrix.reset();  // no need to reset Other EffectList
+    EffectQueueVertex.reset();
+    EffectQueueFragment.reset();
+    EffectQueueMatrix.reset();  // no need to reset Other EffectQueue
 
     EffectMessageSender.stopSending();
    
@@ -408,7 +408,7 @@ public class Distorted
  */
   public static int getMaxMatrix()
     {
-    return EffectListMatrix.getMax();
+    return EffectQueueMatrix.getMax();
     }
  
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -419,7 +419,7 @@ public class Distorted
  */  
   public static int getMaxVertex()
     {
-    return EffectListVertex.getMax();  
+    return EffectQueueVertex.getMax();
     }
   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -430,7 +430,7 @@ public class Distorted
  */  
   public static int getMaxFragment()
     {
-    return EffectListFragment.getMax();  
+    return EffectQueueFragment.getMax();
     }
   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -444,7 +444,7 @@ public class Distorted
  */
   public static boolean setMaxMatrix(int max)
     {
-    return EffectListMatrix.setMax(max);
+    return EffectQueueMatrix.setMax(max);
     }
   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -464,7 +464,7 @@ public class Distorted
  */
   public static boolean setMaxVertex(int max)
     {
-    return EffectListVertex.setMax(max);  
+    return EffectQueueVertex.setMax(max);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -484,7 +484,7 @@ public class Distorted
  */
   public static boolean setMaxFragment(int max)
     {
-    return EffectListFragment.setMax(max);  
+    return EffectQueueFragment.setMax(max);
     }
     
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/DistortedObject.java b/src/main/java/org/distorted/library/DistortedObject.java
index 5c8d578..4b3e1e4 100644
--- a/src/main/java/org/distorted/library/DistortedObject.java
+++ b/src/main/java/org/distorted/library/DistortedObject.java
@@ -12,10 +12,10 @@ public abstract class DistortedObject
 {
     private static float[] mViewMatrix = new float[16];
    
-    protected EffectListMatrix     mM;
-    protected EffectListFragment   mF;
-    protected EffectListVertex     mV;
-    protected EffectListOther      mO;
+    protected EffectQueueMatrix    mM;
+    protected EffectQueueFragment  mF;
+    protected EffectQueueVertex    mV;
+    protected EffectQueueOther mO;
 
     protected boolean matrixCloned, vertexCloned, fragmentCloned;
  
@@ -60,7 +60,7 @@ public abstract class DistortedObject
         } 
       else
         {
-        mM = new EffectListMatrix(d);
+        mM = new EffectQueueMatrix(d);
         matrixCloned = false;  
         }
     
@@ -71,7 +71,7 @@ public abstract class DistortedObject
         } 
       else
         {
-        mV = new EffectListVertex(d);
+        mV = new EffectQueueVertex(d);
         vertexCloned = false;  
         }
     
@@ -82,11 +82,11 @@ public abstract class DistortedObject
         } 
       else
         {
-        mF = new EffectListFragment(d);
+        mF = new EffectQueueFragment(d);
         fragmentCloned = false;   
         }
 
-      mO= new EffectListOther(d); // Other effects are never cloned.
+      mO= new EffectQueueOther(d); // Other effects are never cloned.
       }
     
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -187,10 +187,12 @@ public abstract class DistortedObject
    * Copy constructor used to create a DistortedObject based on various parts of another object.
    * <p>
    * Whatever we do not clone gets created just like in the default constructor.
+   * We only call this from the descendant's classes' constructors where we have to pay attention
+   * to give it the appropriate type of a DistortedObject!
    *
    * @param dc    Source object to create our object from
    * @param flags A bitmask of values specifying what to copy.
-   *              For example, CLONE_BITMAP | CLONE_PRESHADER.
+   *              For example, CLONE_BITMAP | CLONE_MATRIX.
    */
     public DistortedObject(DistortedObject dc, int flags)
       {
@@ -352,28 +354,31 @@ public abstract class DistortedObject
     
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Aborts all Effects. 
+ * Aborts all Effects.
+ * @return Number of effects aborted.
  */
-    public void abortAllEffects()
+    public int abortAllEffects()
       {
-      mM.abortAll();
-      mV.abortAll();
-      mF.abortAll();
-      mO.abortAll();
+      return mM.abortAll() + mV.abortAll() + mF.abortAll() + mO.abortAll();
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Aborts a subset of Effects.
+ * Aborts all Effects of a given type, for example all MATRIX Effects.
  * 
- * @param mask Bitmask of the types of effects we want to abort, e.g. TYPE_PRE | TYPE_VERT | TYPE_FRAG.
+ * @param type one of the constants defined in {@link EffectTypes}
+ * @return Number of effects aborted.
  */
-    public void abortAllEffects(int mask)
+    public int abortEffects(EffectTypes type)
       {
-      if( (mask & EffectTypes.MATRIX.type   ) != 0 ) mM.abortAll();
-      if( (mask & EffectTypes.VERTEX.type   ) != 0 ) mV.abortAll();
-      if( (mask & EffectTypes.FRAGMENT.type ) != 0 ) mF.abortAll();
-      if( (mask & EffectTypes.OTHER.type    ) != 0 ) mO.abortAll();
+      switch(type)
+        {
+        case MATRIX  : return mM.abortAll();
+        case VERTEX  : return mV.abortAll();
+        case FRAGMENT: return mF.abortAll();
+        case OTHER   : return mO.abortAll();
+        default      : return 0;
+        }
       }
     
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -399,18 +404,18 @@ public abstract class DistortedObject
 /**
  * Abort all Effects of a given type, for example all rotations.
  * 
- * @param effectType one of the constants defined in {@link EffectNames}
+ * @param name one of the constants defined in {@link EffectNames}
  * @return <code>true</code> if a single Effect of type effectType has been found and aborted. 
  */
-    public boolean abortEffectType(EffectNames effectType)
+    public boolean abortEffects(EffectNames name)
       {
-      switch(effectType.getType())
+      switch(name.getType())
         {
-        case MATRIX  : return mM.removeByType(effectType);
-        case VERTEX  : return mV.removeByType(effectType);
-        case FRAGMENT: return mF.removeByType(effectType);
-        case OTHER   : return mO.removeByType(effectType);
-        default           : return false;
+        case MATRIX  : return mM.removeByType(name);
+        case VERTEX  : return mV.removeByType(name);
+        case FRAGMENT: return mF.removeByType(name);
+        case OTHER   : return mO.removeByType(name);
+        default      : return false;
         }
       }
     
diff --git a/src/main/java/org/distorted/library/EffectList.java b/src/main/java/org/distorted/library/EffectList.java
deleted file mode 100644
index 3ca5e20..0000000
--- a/src/main/java/org/distorted/library/EffectList.java
+++ /dev/null
@@ -1,287 +0,0 @@
-package org.distorted.library;
-
-import java.util.Vector;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-abstract class EffectList
-  {
-  protected byte mNumEffects;   // number of effects at the moment
-  protected long mTotalEffects; // total number of effects ever created
-  
-  protected int[] mType;
-  protected float[] mUniforms;
-  protected Interpolator[] mInterP;  // center of the effect
-  protected Interpolator[] mInterI;  // all other interpolated values
-  protected long[] mCurrentDuration;
-  protected byte[] mFreeIndexes;
-  protected byte[] mIDIndex;
-  protected long[] mID;
-  
-  protected long mTime=0;
-  protected float mObjHalfX, mObjHalfY, mObjHalfZ;
-  
-  protected static int[] mMax = new int[EffectTypes.LENGTH];
-  protected int mMaxIndex;
-  protected static boolean mCreated;
- 
-  protected Vector<EffectListener> mListeners =null;
-  protected int mNumListeners=0;  // ==mListeners.length(), but we only create mListeners if the first one gets added
-  protected long mBitmapID;
-  
-  static
-    {
-    reset();
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  public EffectList(DistortedObject obj, int numUniforms, int index) 
-    {
-    mNumEffects   = 0;
-    mTotalEffects = 0;
-    mMaxIndex     = index;
-
-    if( obj!=null )
-      {
-      mObjHalfX = obj.getWidth() / 2.0f;
-      mObjHalfY = obj.getHeight() / 2.0f;
-      mObjHalfZ = obj.getDepth() / 2.0f;
-
-      mBitmapID = obj.getID();
-      }
-
-    if( mMax[mMaxIndex]>0 )
-      {
-      mType            = new int[mMax[mMaxIndex]];
-      mUniforms        = new float[numUniforms*mMax[mMaxIndex]];
-      mInterI          = new Interpolator[mMax[mMaxIndex]];
-      mInterP          = new Interpolator2D[mMax[mMaxIndex]];
-      mCurrentDuration = new long[mMax[mMaxIndex]];
-      mID              = new long[mMax[mMaxIndex]];
-      mIDIndex         = new byte[mMax[mMaxIndex]];
-      mFreeIndexes     = new byte[mMax[mMaxIndex]];
-     
-      for(byte i=0; i<mMax[mMaxIndex]; i++) mFreeIndexes[i] = i;
-      }
-   
-    mCreated = true;  
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getNumEffects()
-    {
-    return mNumEffects;  
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void addListener(EffectListener el)
-    {
-    if( mListeners==null ) mListeners = new Vector<>(2,2);
-   
-    mListeners.add(el);
-    mNumListeners++;
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void removeListener(EffectListener el)
-    {
-    if( mNumListeners>0 )  
-      {
-      mListeners.remove(el);
-      mNumListeners--;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void reset()
-    {
-    EffectTypes.reset(mMax);
-    mCreated = false;  
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized boolean removeByID(long id)
-    {
-    int i = getEffectIndex(id);
-   
-    if( i>=0 ) 
-      {
-      remove(i);
-      return true;
-      }
-   
-    return false; 
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized boolean removeByType(EffectNames effect)
-    {
-    boolean ret = false;  
-    int ord = effect.ordinal();  
-     
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mType[i]==ord )
-        {
-        remove(i);
-        ret = true;
-        }
-      }
-   
-    return ret;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  protected synchronized int getEffectIndex(long id)
-    {
-    int index = mIDIndex[(int)(id%mMax[mMaxIndex])];
-    return (index<mNumEffects && mID[index]==id ? index : -1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized void abortAll()
-    {
-    for(int i=0; i<mNumEffects; i++ )
-      {
-      mInterI[i] = null;
-      mInterP[i] = null;
-      } 
-   
-    mNumEffects= 0;  
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this assumes 0<=effect<mNumEffects
-  
-  protected void remove(int effect)
-    {
-    mNumEffects--;     
-    
-    byte removedIndex = (byte)(mID[effect]%mMax[mMaxIndex]);
-    byte removedPosition = mIDIndex[removedIndex];
-    mFreeIndexes[mNumEffects] = removedIndex;
-    
-    long removedID = mID[effect];
-    int removedType= mType[effect];
-    
-    for(int j=0; j<mMax[mMaxIndex]; j++)
-      {
-      if( mIDIndex[j] > removedPosition ) mIDIndex[j]--; 
-      }
-         
-    for(int j=effect; j<mNumEffects; j++ ) 
-      {
-      mType[j]            = mType[j+1];
-      mInterI[j]          = mInterI[j+1];
-      mInterP[j]          = mInterP[j+1];
-      mCurrentDuration[j] = mCurrentDuration[j+1];
-      mID[j]              = mID[j+1];
-    
-      moveEffect(j);
-      }
-   
-    mInterI[mNumEffects] = null;
-    mInterP[mNumEffects] = null;
-   
-    for(int i=0; i<mNumListeners; i++) 
-      EffectMessageSender.newMessage( mListeners.elementAt(i),
-                                      EffectMessage.EFFECT_REMOVED, 
-                                      (removedID<<EffectTypes.LENGTH)+EffectNames.getType(removedType).type,
-                                      removedType,
-                                      mBitmapID);  
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  protected long addBase(EffectNames eln)
-    {    
-    mType[mNumEffects]  = eln.ordinal();  
-    mCurrentDuration[mNumEffects] = 0;
-    
-    int index = mFreeIndexes[mNumEffects];
-    long id = mTotalEffects*mMax[mMaxIndex] + index;
-    mID[mNumEffects] = id;
-    mIDIndex[index] = mNumEffects;  
-   
-    mNumEffects++; 
-    mTotalEffects++;
-   
-    return (id<<EffectTypes.LENGTH)+eln.getType().type;
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// used only for debugging
-  
-  protected String printEffects(int max)
-    {
-    long[] indexes = new long[mMax[mMaxIndex]];
-   
-    for(int g=0; g<mMax[mMaxIndex]; g++)
-      {
-      indexes[g] = -1;  
-      }
-   
-    String ret="(";
-    int f;
-   
-    for(int g=0; g<max; g++) 
-      {
-      f = getEffectIndex(g);
-      if( f>=0 ) indexes[f] = g;
-      }
-   
-    for(int g=0; g<mMax[mMaxIndex]; g++)
-      {
-      ret += (g>0 ? ",":"")+(indexes[g]>=0 ? indexes[g] : " ");   
-      }
-   
-    ret += ")";
-   
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Only used for debugging
-  
-  protected boolean printByID(long id)
-    {
-    int index = getEffectIndex(id);
-   
-    if( index>=0 ) 
-      {
-      boolean interI = mInterI[index]==null; 
-      boolean interP = mInterP[index]==null; 
-      
-      android.util.Log.e("EffectList", "numEffects="+mNumEffects+" effect id="+id+" index="+index+" duration="+mCurrentDuration[index]+" interI null="+interI+" interP null="+interP);
-      
-      if( interI==false )
-        {
-        android.util.Log.e("EffectList","interI: "+mInterI[index].print());  
-        }
-      if( interP==false )
-        {
-        android.util.Log.e("EffectList","interP: "+mInterP[index].print());  
-        }
-     
-      return true;
-      }
-   
-    android.util.Log.e("EffectList", "effect id="+id+" not found");
-
-    return false;  
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  abstract void moveEffect(int index);
-  }
-///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectListFragment.java b/src/main/java/org/distorted/library/EffectListFragment.java
deleted file mode 100644
index b7610cb..0000000
--- a/src/main/java/org/distorted/library/EffectListFragment.java
+++ /dev/null
@@ -1,297 +0,0 @@
-package org.distorted.library;
-
-import android.opengl.GLES20;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectListFragment extends EffectList
-  {
-  private static final int NUM_UNIFORMS = 9;
-  private static final int INDEX = EffectTypes.FRAGMENT.ordinal();
-  private float[] mBuf;
-  private static int mNumEffectsH;
-  private static int mTypeH;
-  private static int mUniformsH;
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  public EffectListFragment(DistortedObject obj)
-    { 
-    super(obj,NUM_UNIFORMS,INDEX);
-   
-    if( mMax[INDEX]>0 )
-      {
-      mBuf= new float[4*mMax[INDEX]];
-      }
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Only max Byte.MAX_VALUE concurrent effects per bitmap.
-// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
-  
-  static boolean setMax(int m)
-    {
-    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[INDEX] )
-      {
-           if( m<0              ) m = 0;
-      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
-      
-      mMax[INDEX] = m;
-      return true;
-      }
-   
-    return false;
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static int getMax()
-    {
-    return mMax[INDEX];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void getUniforms(int mProgramH)
-    {
-    mNumEffectsH= GLES20.glGetUniformLocation( mProgramH, "fNumEffects");
-    mTypeH      = GLES20.glGetUniformLocation( mProgramH, "fType");
-    mUniformsH  = GLES20.glGetUniformLocation( mProgramH, "fUniforms");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized void compute(long currTime) 
-    { 
-    if( currTime==mTime ) return;
-    if( mTime==0 ) mTime = currTime;
-    long step = (currTime-mTime);
-   
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mInterI[i]==null ) continue;    
-      
-      if( mInterP[i]!=null ) mInterP[i].interpolateMain(mBuf, 4*i, mCurrentDuration[i]);
-        
-      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i, mCurrentDuration[i], step) )      
-        {
-        for(int j=0; j<mNumListeners; j++)   
-          EffectMessageSender.newMessage( mListeners.elementAt(j),
-                                          EffectMessage.EFFECT_FINISHED, 
-                                          (mID[i]<<EffectTypes.LENGTH)+EffectTypes.FRAGMENT.type,
-                                          mType[i], 
-                                          mBitmapID); 
-      
-        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i) )
-          {
-          remove(i);
-          i--;
-          continue;
-          }
-        }
-           
-      mCurrentDuration[i] += step;
-      }
-   
-    mTime = currTime;  
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected void moveEffect(int index)
-    {
-    mBuf[4*index  ] = mBuf[4*index+4];
-    mBuf[4*index+1] = mBuf[4*index+5];
-    mBuf[4*index+2] = mBuf[4*index+6];
-    mBuf[4*index+3] = mBuf[4*index+7];
-              
-    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
-    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
-    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];  
-    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];  
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized void send() 
-    {
-    GLES20.glUniform1i( mNumEffectsH, mNumEffects);
-      
-    if( mNumEffects>0 )
-      {     
-      GLES20.glUniform1iv( mTypeH    ,  mNumEffects, mType    ,0);
-      GLES20.glUniform3fv( mUniformsH,3*mNumEffects, mUniforms,0);
-      }  
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized void sendZero() 
-    {
-    GLES20.glUniform1i( mNumEffectsH, 0);
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Do various post-processing on already computed effects.
-// 1) move all Points and scale all Region radii by a ModelView matrix
-// 2) in case of macroblock, pre-compute some values so that we don't have to do it in the fragment shader.
-  
-  void postprocess(float[] MVmatrix)
-    {
-    if( mNumEffects>0 )
-      {
-      float tx,ty;   
-      float w = (float)Math.sqrt(MVmatrix[0]*MVmatrix[0] + MVmatrix[4]*MVmatrix[4]);  // The scale factors are the lengths of the first 3 vectors of the upper-left 3x3 submatrix; here
-      float h = (float)Math.sqrt(MVmatrix[1]*MVmatrix[1] + MVmatrix[5]*MVmatrix[5]);  // m[2]=m[6]=m[8]=m[9]=0 so it is really only the upper-left 2x2 matrix.
-   
-      for(int i=0; i<mNumEffects; i++)
-        {   
-        tx = mBuf[4*i  ]-mObjHalfX; // we have to invert y and move everything by (half of bmp width, half of bmp height)
-        ty =-mBuf[4*i+1]+mObjHalfY; //
-      
-        mUniforms[NUM_UNIFORMS*i+4] = w*mBuf[4*i+2];                                  // in fragment shader rx and ry radii are the second and third values of the Region thus 9*i+4 and 9*i+5
-        mUniforms[NUM_UNIFORMS*i+5] = h*mBuf[4*i+3];                                  // 
-     // mUniforms[NUM_UNIFORMS*i+6] =                                                 // this value is not used in Fragment Shader   
-        mUniforms[NUM_UNIFORMS*i+7] = MVmatrix[0]*tx + MVmatrix[4]*ty + MVmatrix[12]; // multiply the ModelView matrix times the (x,y,0,1) point, i.e. the (x,y) point on the surface of the bitmap.
-        mUniforms[NUM_UNIFORMS*i+8] = MVmatrix[1]*tx + MVmatrix[5]*ty + MVmatrix[13]; //  
-        
-        if( mType[i]==EffectNames.MACROBLOCK.ordinal() ) // fill up the .y and .z components of the Interpolated values already to avoid having to compute this in the fragment shader
-          {
-          mUniforms[NUM_UNIFORMS*i+1] = 2.0f*mObjHalfX/mUniforms[NUM_UNIFORMS*i];
-          mUniforms[NUM_UNIFORMS*i+2] = 2.0f*mObjHalfY/mUniforms[NUM_UNIFORMS*i];
-          }
-        }
-      }
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-       
-  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, Interpolator2D point)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects); 
-      mInterI[mNumEffects] = inter;
-      mInterP[mNumEffects] = point;
-      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
-      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
-   
-      return addBase(eln); 
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, float x, float y)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects);    
-      mInterI[mNumEffects] = inter;
-      mInterP[mNumEffects] = null;
-      mBuf[4*mNumEffects  ] = x;
-      mBuf[4*mNumEffects+1] = y;
-      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
-      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
-   
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-       
-  synchronized long add(EffectNames eln, Interpolator1D inter, Float3D c, Float4D region, Interpolator2D point)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInterI[mNumEffects] = inter;
-      mInterP[mNumEffects] = point;
-      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
-      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
-   
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
-      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
-     
-      return addBase(eln); 
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized long add(EffectNames eln, Interpolator1D inter, Float3D c, Float4D region, float x, float y)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInterI[mNumEffects] = inter;
-      mInterP[mNumEffects] = null;
-      mBuf[4*mNumEffects  ] = x;
-      mBuf[4*mNumEffects+1] = y;
-      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
-      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
-      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
-   
-      return addBase(eln);
-      }
-       
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-       
-  synchronized long add(EffectNames eln, float t, Float3D c, Float4D region, Interpolator2D point)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInterI[mNumEffects] = null;
-      mInterP[mNumEffects] = point;
-      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
-      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
-   
-      mUniforms[NUM_UNIFORMS*mNumEffects+0] = t;
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
-      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
-     
-      return addBase(eln); 
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized long add(EffectNames eln, float t, Float3D c, Float4D region, float x, float y)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInterI[mNumEffects] = null;
-      mInterP[mNumEffects] = null;
-      mBuf[4*mNumEffects  ] = x;
-      mBuf[4*mNumEffects+1] = y;
-      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
-      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects+0] = t;
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
-      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
-   
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// end of FragmentEffect   
-  }
diff --git a/src/main/java/org/distorted/library/EffectListMatrix.java b/src/main/java/org/distorted/library/EffectListMatrix.java
deleted file mode 100644
index 667c5e2..0000000
--- a/src/main/java/org/distorted/library/EffectListMatrix.java
+++ /dev/null
@@ -1,345 +0,0 @@
-package org.distorted.library;
-
-import android.opengl.GLES20;
-import android.opengl.Matrix;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectListMatrix extends EffectList
-  {   
-  private static final int NUM_UNIFORMS = 7;
-  private static final int INDEX = EffectTypes.MATRIX.ordinal();
-  private static float[] mMVPMatrix= new float[16];
-  private static float[] mTmpMatrix= new float[16];
-  
-  private static int mBmpDH;      // This is a handle to half a bitmap dimensions
-  private static int mDepthH;     // Handle to the max Depth, i.e (farplane-nearplane)/2
-  private static int mMVPMatrixH; // pass in the transformation matrix
-  private static int mMVMatrixH;  // pass in the modelview matrix.
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  public EffectListMatrix(DistortedObject obj)
-    { 
-    super(obj,NUM_UNIFORMS, INDEX );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void multiplyByQuat(float[] matrix, float X, float Y, float Z, float W)
-    {
-    float xx= X * X;
-    float xy= X * Y;
-    float xz= X * Z;
-    float xw= X * W;
-    float yy= Y * Y;
-    float yz= Y * Z;
-    float yw= Y * W;
-    float zz= Z * Z;
-    float zw= Z * W;
-
-    mTmpMatrix[0]  = 1 - 2 * ( yy + zz );
-    mTmpMatrix[1]  =     2 * ( xy - zw );
-    mTmpMatrix[2]  =     2 * ( xz + yw );
-    mTmpMatrix[4]  =     2 * ( xy + zw );
-    mTmpMatrix[5]  = 1 - 2 * ( xx + zz );
-    mTmpMatrix[6]  =     2 * ( yz - xw );
-    mTmpMatrix[8]  =     2 * ( xz - yw );
-    mTmpMatrix[9]  =     2 * ( yz + xw );
-    mTmpMatrix[10] = 1 - 2 * ( xx + yy );
-    mTmpMatrix[3]  = mTmpMatrix[7] = mTmpMatrix[11] = mTmpMatrix[12] = mTmpMatrix[13] = mTmpMatrix[14] = 0;
-    mTmpMatrix[15] = 1;
-    
-    Matrix.multiplyMM(mMVPMatrix, 0, matrix, 0, mTmpMatrix, 0);  
-    for(int j=0; j<16; j++) matrix[j] = mMVPMatrix[j];   
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Only max Byte.MAX_VALUE concurrent effects per bitmap.
-// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
-  
-  static boolean setMax(int m)
-    {
-    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[INDEX] )
-      {
-           if( m<0              ) m = 0;
-      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
-      
-      mMax[INDEX] = m;
-      return true;
-      }
-   
-    return false;
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static int getMax()
-    {
-    return mMax[INDEX];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void getUniforms(int mProgramH)
-    {
-    mBmpDH     = GLES20.glGetUniformLocation(mProgramH, "u_bmpD");
-    mDepthH    = GLES20.glGetUniformLocation(mProgramH, "u_Depth");
-    mMVPMatrixH= GLES20.glGetUniformLocation(mProgramH, "u_MVPMatrix");
-    mMVMatrixH = GLES20.glGetUniformLocation(mProgramH, "u_MVMatrix"); 
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized void compute(long currTime) 
-    {
-    if( currTime==mTime ) return;
-    if( mTime==0 ) mTime = currTime;
-    long step = (currTime-mTime);
-   
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mInterI[i]==null ) continue;    
-           
-      if( mInterP[i]!=null ) 
-        {
-        mInterP[i].interpolateMain(mUniforms, NUM_UNIFORMS*i, mCurrentDuration[i]);
-        }
-        
-      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i+3, mCurrentDuration[i], step) )      
-        {   
-        for(int j=0; j<mNumListeners; j++)   
-          EffectMessageSender.newMessage( mListeners.elementAt(j),
-                                          EffectMessage.EFFECT_FINISHED, 
-                                         (mID[i]<<EffectTypes.LENGTH)+EffectTypes.MATRIX.type,
-                                          mType[i], 
-                                          mBitmapID); 
-       
-        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i+3) )
-          {  
-          remove(i);
-          i--;
-          continue;
-          }
-        }
-    
-      mCurrentDuration[i] += step;
-      }
-     
-    mTime = currTime;  
-    }  
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected void moveEffect(int index)
-    {
-    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
-    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
-    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];
-    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];
-    mUniforms[NUM_UNIFORMS*index+4] = mUniforms[NUM_UNIFORMS*(index+1)+4];
-    mUniforms[NUM_UNIFORMS*index+5] = mUniforms[NUM_UNIFORMS*(index+1)+5];
-    mUniforms[NUM_UNIFORMS*index+6] = mUniforms[NUM_UNIFORMS*(index+1)+6];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// here construct the ModelView Matrix
-
-  synchronized void send(float[] viewMatrix, DistortedProjection dp) 
-    {
-    Matrix.setIdentityM(viewMatrix, 0);
-    Matrix.translateM(viewMatrix, 0, -dp.width/2, dp.height/2, -dp.distance);
-    
-    float x,y,z, sx,sy,sz=1.0f;
-   
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if (mType[i] == EffectNames.ROTATE.ordinal() )
-        {
-        x = mUniforms[NUM_UNIFORMS*i  ];
-        y = mUniforms[NUM_UNIFORMS*i+1];
-        z = mUniforms[NUM_UNIFORMS*i+2];
-     
-        Matrix.translateM(viewMatrix, 0, x,-y, z); 
-        Matrix.rotateM( viewMatrix, 0, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);  
-        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
-        }
-      else if(mType[i] == EffectNames.QUATERNION.ordinal() )
-        {
-        x = mUniforms[NUM_UNIFORMS*i  ];
-        y = mUniforms[NUM_UNIFORMS*i+1];
-        z = mUniforms[NUM_UNIFORMS*i+2];
-     	
-        Matrix.translateM(viewMatrix, 0, x,-y, z); 
-        multiplyByQuat(viewMatrix, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);
-        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
-        }
-      else if(mType[i] == EffectNames.MOVE.ordinal() )
-        {
-        sx = mUniforms[NUM_UNIFORMS*i+3];   
-        sy = mUniforms[NUM_UNIFORMS*i+4];   
-        sz = mUniforms[NUM_UNIFORMS*i+5];   
-        
-        Matrix.translateM(viewMatrix, 0, sx,-sy, sz);   
-        }
-      else if(mType[i] == EffectNames.SCALE.ordinal() )
-        {
-        sx = mUniforms[NUM_UNIFORMS*i+3];   
-        sy = mUniforms[NUM_UNIFORMS*i+4];   
-        sz = mUniforms[NUM_UNIFORMS*i+5];   
-
-        Matrix.scaleM(viewMatrix, 0, sx, sy, sz);  
-        }
-      else if(mType[i] == EffectNames.SHEAR.ordinal() )
-        {
-        x  = mUniforms[NUM_UNIFORMS*i  ];
-        y  = mUniforms[NUM_UNIFORMS*i+1];
-        z  = mUniforms[NUM_UNIFORMS*i+2];
-        
-        sx = mUniforms[NUM_UNIFORMS*i+3];   
-        sy = mUniforms[NUM_UNIFORMS*i+4];   
-        sz = mUniforms[NUM_UNIFORMS*i+5];   
-        
-        Matrix.translateM(viewMatrix, 0, x,-y, z); 
-      
-        viewMatrix[4] += sx*viewMatrix[0]; // Multiply viewMatrix by 1 x 0 0 , i.e. X-shear. TODO: change this so it is symmetric w respect to all the axis.
-        viewMatrix[5] += sx*viewMatrix[1]; //                        0 1 0 0 
-        viewMatrix[6] += sx*viewMatrix[2]; //                        0 0 1 0
-        viewMatrix[7] += sx*viewMatrix[3]; //                        0 0 0 1
-      
-        viewMatrix[0] += sy*viewMatrix[4]; // Multiply viewMatrix by 1 0 0 0 , i.e. Y-shear. TODO: change this so it is symmetric w respect to all the axis.
-        viewMatrix[1] += sy*viewMatrix[5]; //                        y 1 0 0
-        viewMatrix[2] += sy*viewMatrix[6]; //                        0 0 1 0
-        viewMatrix[3] += sy*viewMatrix[7]; //                        0 0 0 1      
-      
-        // TODO: implement Z-shear.
-        
-        Matrix.translateM(viewMatrix, 0,-x, y, -z);
-        }
-      }
-   
-    Matrix.translateM(viewMatrix, 0, mObjHalfX,-mObjHalfY, -mObjHalfZ);
-    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, viewMatrix, 0);
-    
-    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
-    GLES20.glUniform1f( mDepthH, dp.depth);   
-    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, viewMatrix, 0);
-    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// here construct the ModelView Matrix, but without any effects
-
-  synchronized void sendNoEffects(DistortedProjection dp) 
-    {
-    Matrix.setIdentityM(mTmpMatrix, 0);
-    Matrix.translateM(mTmpMatrix, 0, mObjHalfX-dp.width/2, dp.height/2-mObjHalfY, mObjHalfZ-dp.distance);
-    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, mTmpMatrix, 0);
-    
-    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
-    GLES20.glUniform1f( mDepthH, dp.depth);  
-    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, mTmpMatrix, 0);
-    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator i)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = p;
-      mInterI[mNumEffects] = i;
-      
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator i)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = null;
-      mInterI[mNumEffects] = i;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
-            
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator1D i, float aX, float aY, float aZ)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = null;
-      mInterI[mNumEffects] = i;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
-      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
-      
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator1D i, float aX, float aY, float aZ)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = p;
-      mInterI[mNumEffects] = i;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
-      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
-      
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, float x, float y, float z, float aA, float aX, float aY, float aZ)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = null; 
-      mInterI[mNumEffects] = null;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects  ] =  x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] =  y;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] =  z;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+3] = aA;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
-      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
-      
-      return addBase(eln);   
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  }
diff --git a/src/main/java/org/distorted/library/EffectListOther.java b/src/main/java/org/distorted/library/EffectListOther.java
deleted file mode 100644
index 21a0dcb..0000000
--- a/src/main/java/org/distorted/library/EffectListOther.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.distorted.library;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-import java.util.Vector;
-
-/**
- * Do NOT base this on EffectList - this is an entirely different animal than the first 3 EffectLists.
- * The Effects in here will be executed after all the shaders have been executed - thus there are no
- * uniforms to send, no real queues to maintain.
- * <p>
- * Only 2 effects here ATM:
- * - save current Surface to a PNG file
- * - save current animation to a .MP4 file
- *
- * In contrast to the other EffectLists, only one instance of each allowed at any given moment - thus
- * this is not even a real EffectList, it is named so only for consistency with the others.
- */
-public class EffectListOther
-  {
-  private Vector<EffectListener> mListeners =null;
-  private int mNumListeners=0;  // ==mListeners.length(), but we only create mListeners if the first one gets added
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public EffectListOther(DistortedObject obj)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized void send()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized void abortAll()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void addListener(EffectListener el)
-    {
-    if( mListeners==null ) mListeners = new Vector<>(2,2);
-
-    mListeners.add(el);
-    mNumListeners++;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void removeListener(EffectListener el)
-    {
-    if( mNumListeners>0 )
-      {
-      mListeners.remove(el);
-      mNumListeners--;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized boolean removeByID(long id)
-    {
-    //....
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized boolean removeByType(EffectNames effect)
-    {
-    // ...
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected boolean printByID(long id)
-    {
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized long add(EffectNames eln, String filename)
-    {
-    return 0;
-    }
-  }
diff --git a/src/main/java/org/distorted/library/EffectListVertex.java b/src/main/java/org/distorted/library/EffectListVertex.java
deleted file mode 100644
index bbb7309..0000000
--- a/src/main/java/org/distorted/library/EffectListVertex.java
+++ /dev/null
@@ -1,236 +0,0 @@
-package org.distorted.library;
-
-import android.opengl.GLES20;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectListVertex extends EffectList
-  { 
-  private static final int NUM_UNIFORMS = 9;
-  private static final int INDEX = EffectTypes.VERTEX.ordinal();
-  private static int mNumEffectsH;
-  private static int mTypeH;
-  private static int mUniformsH;
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  public EffectListVertex(DistortedObject obj)
-    { 
-    super(obj,NUM_UNIFORMS,INDEX);
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Only max Byte.MAX_VALUE concurrent effects per bitmap.
-// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
-  
-  static boolean setMax(int m)
-    {
-    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[INDEX] )
-      {
-           if( m<0              ) m = 0;
-      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
-      
-      mMax[INDEX] = m;
-      return true;
-      }
-   
-    return false;
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static int getMax()
-    {
-    return mMax[INDEX];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void getUniforms(int mProgramH)
-    {
-    mNumEffectsH= GLES20.glGetUniformLocation( mProgramH, "vNumEffects");
-    mTypeH      = GLES20.glGetUniformLocation( mProgramH, "vType");
-    mUniformsH  = GLES20.glGetUniformLocation( mProgramH, "vUniforms");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized void compute(long currTime) 
-    {
-    if( currTime==mTime ) return;
-    if( mTime==0 ) mTime = currTime;
-    long step = (currTime-mTime);
-   
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mInterI[i]==null ) continue;    
-      
-      if( mInterP[i]!=null ) 
-        {
-        mInterP[i].interpolateMain(mUniforms, NUM_UNIFORMS*i+7, mCurrentDuration[i]);
-      
-        mUniforms[NUM_UNIFORMS*i+7] = mUniforms[NUM_UNIFORMS*i+7]-mObjHalfX;
-        mUniforms[NUM_UNIFORMS*i+8] =-mUniforms[NUM_UNIFORMS*i+8]+mObjHalfY;
-        }
-        
-      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i, mCurrentDuration[i], step) )      
-        {
-        for(int j=0; j<mNumListeners; j++)   
-          EffectMessageSender.newMessage( mListeners.elementAt(j),
-                                          EffectMessage.EFFECT_FINISHED, 
-                                         (mID[i]<<EffectTypes.LENGTH)+EffectTypes.VERTEX.type,
-                                          mType[i], 
-                                          mBitmapID); 
-      
-        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i) )
-          {
-          remove(i);
-          i--;
-          continue;
-          }
-        }
-     
-      mCurrentDuration[i] += step;
-      }
-     
-    mTime = currTime;  
-    }  
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected void moveEffect(int index)
-    {
-    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
-    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
-    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];
-    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];
-    mUniforms[NUM_UNIFORMS*index+4] = mUniforms[NUM_UNIFORMS*(index+1)+4];
-    mUniforms[NUM_UNIFORMS*index+5] = mUniforms[NUM_UNIFORMS*(index+1)+5];
-    mUniforms[NUM_UNIFORMS*index+6] = mUniforms[NUM_UNIFORMS*(index+1)+6];
-    mUniforms[NUM_UNIFORMS*index+7] = mUniforms[NUM_UNIFORMS*(index+1)+7];
-    mUniforms[NUM_UNIFORMS*index+8] = mUniforms[NUM_UNIFORMS*(index+1)+8];
-    }
-   
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized void send() 
-    {
-    GLES20.glUniform1i( mNumEffectsH, mNumEffects);
-      
-    if( mNumEffects>0 )
-      {     
-      GLES20.glUniform1iv( mTypeH    ,  mNumEffects, mType    ,0);
-      GLES20.glUniform3fv( mUniformsH,3*mNumEffects, mUniforms,0);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized void sendZero() 
-    {
-    GLES20.glUniform1i( mNumEffectsH, 0);
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Do various post-processing on already computed effects.
-// 1) here unlike in the fragment queue, we don't have to multiply the points by ModelView matrix because that gets done in the shader.
-// 2) in case of swirl, pre-compute the sine and cosine of its rotation angle
-  
-  void postprocess()
-    {
-    double d;  
-     
-    for(int i=0; i<mNumEffects; i++)
-      {      
-      if( mType[i]==EffectNames.SWIRL.ordinal() )
-        {
-        d = Math.PI*mUniforms[NUM_UNIFORMS*i]/180;  
-        mUniforms[NUM_UNIFORMS*i+1] = (float)Math.sin(d);
-        mUniforms[NUM_UNIFORMS*i+2] = (float)Math.cos(d);
-        }
-      }
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, Interpolator2D point)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects);    
-      
-      mInterI[mNumEffects] = inter;
-      mInterP[mNumEffects] = point;
-
-      return addPriv(eln,region);
-      }
-      
-    return -1;
-    }
-   
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, float x, float y)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects);    
-      
-      mInterI[mNumEffects] = inter;
-      mInterP[mNumEffects] = null;
-      mUniforms[NUM_UNIFORMS*mNumEffects+7] = x-mObjHalfX;
-      mUniforms[NUM_UNIFORMS*mNumEffects+8] =-y+mObjHalfY;  
-     
-      return addPriv(eln,region);
-      }
-      
-    return -1;
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, float v1, float v2, float v3, Float4D region, float x, float y)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects); 
-      mUniforms[NUM_UNIFORMS*mNumEffects  ] = v1;
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = v2;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = v3;  
-     
-      mInterI[mNumEffects] = null;
-      mInterP[mNumEffects] = null;
-      mUniforms[NUM_UNIFORMS*mNumEffects+7] = x-mObjHalfX;
-      mUniforms[NUM_UNIFORMS*mNumEffects+8] =-y+mObjHalfY;  
-      
-      return addPriv(eln,region);    
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  private long addPriv(EffectNames eln, Float4D region)
-    {    
-    if( region!=null )
-      {
-      mUniforms[NUM_UNIFORMS*mNumEffects+3] = region.x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+4] =-region.y;   // invert y already
-      mUniforms[NUM_UNIFORMS*mNumEffects+5] = region.z<=0.0f ? 1000*mObjHalfX : region.z;
-      mUniforms[NUM_UNIFORMS*mNumEffects+6] = region.w;
-      }
-    else
-      {
-      mUniforms[NUM_UNIFORMS*mNumEffects+3] = 0.0f;
-      mUniforms[NUM_UNIFORMS*mNumEffects+4] = 0.0f;
-      mUniforms[NUM_UNIFORMS*mNumEffects+5] = 1000*mObjHalfX;
-      mUniforms[NUM_UNIFORMS*mNumEffects+6] = 0.0f;
-      }
-    
-    return addBase(eln);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// end of VertexEffect  
-  }
diff --git a/src/main/java/org/distorted/library/EffectQueue.java b/src/main/java/org/distorted/library/EffectQueue.java
new file mode 100644
index 0000000..3ca5ac5
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectQueue.java
@@ -0,0 +1,291 @@
+package org.distorted.library;
+
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+abstract class EffectQueue
+  {
+  protected byte mNumEffects;   // number of effects at the moment
+  protected long mTotalEffects; // total number of effects ever created
+  
+  protected int[] mType;
+  protected float[] mUniforms;
+  protected Interpolator[] mInterP;  // center of the effect
+  protected Interpolator[] mInterI;  // all other interpolated values
+  protected long[] mCurrentDuration;
+  protected byte[] mFreeIndexes;
+  protected byte[] mIDIndex;
+  protected long[] mID;
+  
+  protected long mTime=0;
+  protected float mObjHalfX, mObjHalfY, mObjHalfZ;
+  
+  protected static int[] mMax = new int[EffectTypes.LENGTH];
+  protected int mMaxIndex;
+  protected static boolean mCreated;
+ 
+  protected Vector<EffectListener> mListeners =null;
+  protected int mNumListeners=0;  // ==mListeners.length(), but we only create mListeners if the first one gets added
+  protected long mBitmapID;
+  
+  static
+    {
+    reset();
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectQueue(DistortedObject obj, int numUniforms, int index)
+    {
+    mNumEffects   = 0;
+    mTotalEffects = 0;
+    mMaxIndex     = index;
+
+    if( obj!=null )
+      {
+      mObjHalfX = obj.getWidth() / 2.0f;
+      mObjHalfY = obj.getHeight() / 2.0f;
+      mObjHalfZ = obj.getDepth() / 2.0f;
+
+      mBitmapID = obj.getID();
+      }
+
+    if( mMax[mMaxIndex]>0 )
+      {
+      mType            = new int[mMax[mMaxIndex]];
+      mUniforms        = new float[numUniforms*mMax[mMaxIndex]];
+      mInterI          = new Interpolator[mMax[mMaxIndex]];
+      mInterP          = new Interpolator2D[mMax[mMaxIndex]];
+      mCurrentDuration = new long[mMax[mMaxIndex]];
+      mID              = new long[mMax[mMaxIndex]];
+      mIDIndex         = new byte[mMax[mMaxIndex]];
+      mFreeIndexes     = new byte[mMax[mMaxIndex]];
+     
+      for(byte i=0; i<mMax[mMaxIndex]; i++) mFreeIndexes[i] = i;
+      }
+   
+    mCreated = true;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumEffects()
+    {
+    return mNumEffects;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void addListener(EffectListener el)
+    {
+    if( mListeners==null ) mListeners = new Vector<>(2,2);
+   
+    mListeners.add(el);
+    mNumListeners++;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeListener(EffectListener el)
+    {
+    if( mNumListeners>0 )  
+      {
+      mListeners.remove(el);
+      mNumListeners--;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void reset()
+    {
+    EffectTypes.reset(mMax);
+    mCreated = false;  
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized boolean removeByID(long id)
+    {
+    int i = getEffectIndex(id);
+   
+    if( i>=0 ) 
+      {
+      remove(i);
+      return true;
+      }
+   
+    return false; 
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized boolean removeByType(EffectNames effect)
+    {
+    boolean ret = false;  
+    int ord = effect.ordinal();  
+     
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mType[i]==ord )
+        {
+        remove(i);
+        ret = true;
+        }
+      }
+   
+    return ret;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  protected synchronized int getEffectIndex(long id)
+    {
+    int index = mIDIndex[(int)(id%mMax[mMaxIndex])];
+    return (index<mNumEffects && mID[index]==id ? index : -1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized int abortAll()
+    {
+    int ret = mNumEffects;
+
+    for(int i=0; i<ret; i++ )
+      {
+      mInterI[i] = null;
+      mInterP[i] = null;
+      }
+
+    mNumEffects= 0;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this assumes 0<=effect<mNumEffects
+  
+  protected void remove(int effect)
+    {
+    mNumEffects--;     
+    
+    byte removedIndex = (byte)(mID[effect]%mMax[mMaxIndex]);
+    byte removedPosition = mIDIndex[removedIndex];
+    mFreeIndexes[mNumEffects] = removedIndex;
+    
+    long removedID = mID[effect];
+    int removedType= mType[effect];
+    
+    for(int j=0; j<mMax[mMaxIndex]; j++)
+      {
+      if( mIDIndex[j] > removedPosition ) mIDIndex[j]--; 
+      }
+         
+    for(int j=effect; j<mNumEffects; j++ ) 
+      {
+      mType[j]            = mType[j+1];
+      mInterI[j]          = mInterI[j+1];
+      mInterP[j]          = mInterP[j+1];
+      mCurrentDuration[j] = mCurrentDuration[j+1];
+      mID[j]              = mID[j+1];
+    
+      moveEffect(j);
+      }
+   
+    mInterI[mNumEffects] = null;
+    mInterP[mNumEffects] = null;
+   
+    for(int i=0; i<mNumListeners; i++) 
+      EffectMessageSender.newMessage( mListeners.elementAt(i),
+                                      EffectMessage.EFFECT_REMOVED, 
+                                      (removedID<<EffectTypes.LENGTH)+EffectNames.getType(removedType).type,
+                                      removedType,
+                                      mBitmapID);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  protected long addBase(EffectNames eln)
+    {    
+    mType[mNumEffects]  = eln.ordinal();  
+    mCurrentDuration[mNumEffects] = 0;
+    
+    int index = mFreeIndexes[mNumEffects];
+    long id = mTotalEffects*mMax[mMaxIndex] + index;
+    mID[mNumEffects] = id;
+    mIDIndex[index] = mNumEffects;  
+   
+    mNumEffects++; 
+    mTotalEffects++;
+   
+    return (id<<EffectTypes.LENGTH)+eln.getType().type;
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// used only for debugging
+  
+  protected String printEffects(int max)
+    {
+    long[] indexes = new long[mMax[mMaxIndex]];
+   
+    for(int g=0; g<mMax[mMaxIndex]; g++)
+      {
+      indexes[g] = -1;  
+      }
+   
+    String ret="(";
+    int f;
+   
+    for(int g=0; g<max; g++) 
+      {
+      f = getEffectIndex(g);
+      if( f>=0 ) indexes[f] = g;
+      }
+   
+    for(int g=0; g<mMax[mMaxIndex]; g++)
+      {
+      ret += (g>0 ? ",":"")+(indexes[g]>=0 ? indexes[g] : " ");   
+      }
+   
+    ret += ")";
+   
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only used for debugging
+  
+  protected boolean printByID(long id)
+    {
+    int index = getEffectIndex(id);
+   
+    if( index>=0 ) 
+      {
+      boolean interI = mInterI[index]==null; 
+      boolean interP = mInterP[index]==null; 
+      
+      android.util.Log.e("EffectQueue", "numEffects="+mNumEffects+" effect id="+id+" index="+index+" duration="+mCurrentDuration[index]+" interI null="+interI+" interP null="+interP);
+      
+      if( interI==false )
+        {
+        android.util.Log.e("EffectQueue","interI: "+mInterI[index].print());
+        }
+      if( interP==false )
+        {
+        android.util.Log.e("EffectQueue","interP: "+mInterP[index].print());
+        }
+     
+      return true;
+      }
+   
+    android.util.Log.e("EffectQueue", "effect id="+id+" not found");
+
+    return false;  
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract void moveEffect(int index);
+  }
+///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectQueueFragment.java b/src/main/java/org/distorted/library/EffectQueueFragment.java
new file mode 100644
index 0000000..c524c8d
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectQueueFragment.java
@@ -0,0 +1,297 @@
+package org.distorted.library;
+
+import android.opengl.GLES20;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueueFragment extends EffectQueue
+  {
+  private static final int NUM_UNIFORMS = 9;
+  private static final int INDEX = EffectTypes.FRAGMENT.ordinal();
+  private float[] mBuf;
+  private static int mNumEffectsH;
+  private static int mTypeH;
+  private static int mUniformsH;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectQueueFragment(DistortedObject obj)
+    { 
+    super(obj,NUM_UNIFORMS,INDEX);
+   
+    if( mMax[INDEX]>0 )
+      {
+      mBuf= new float[4*mMax[INDEX]];
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only max Byte.MAX_VALUE concurrent effects per bitmap.
+// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
+  
+  static boolean setMax(int m)
+    {
+    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[INDEX] )
+      {
+           if( m<0              ) m = 0;
+      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
+      
+      mMax[INDEX] = m;
+      return true;
+      }
+   
+    return false;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static int getMax()
+    {
+    return mMax[INDEX];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mNumEffectsH= GLES20.glGetUniformLocation( mProgramH, "fNumEffects");
+    mTypeH      = GLES20.glGetUniformLocation( mProgramH, "fType");
+    mUniformsH  = GLES20.glGetUniformLocation( mProgramH, "fUniforms");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void compute(long currTime) 
+    { 
+    if( currTime==mTime ) return;
+    if( mTime==0 ) mTime = currTime;
+    long step = (currTime-mTime);
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mInterI[i]==null ) continue;    
+      
+      if( mInterP[i]!=null ) mInterP[i].interpolateMain(mBuf, 4*i, mCurrentDuration[i]);
+        
+      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i, mCurrentDuration[i], step) )      
+        {
+        for(int j=0; j<mNumListeners; j++)   
+          EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                          EffectMessage.EFFECT_FINISHED, 
+                                          (mID[i]<<EffectTypes.LENGTH)+EffectTypes.FRAGMENT.type,
+                                          mType[i], 
+                                          mBitmapID); 
+      
+        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i) )
+          {
+          remove(i);
+          i--;
+          continue;
+          }
+        }
+           
+      mCurrentDuration[i] += step;
+      }
+   
+    mTime = currTime;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected void moveEffect(int index)
+    {
+    mBuf[4*index  ] = mBuf[4*index+4];
+    mBuf[4*index+1] = mBuf[4*index+5];
+    mBuf[4*index+2] = mBuf[4*index+6];
+    mBuf[4*index+3] = mBuf[4*index+7];
+              
+    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
+    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
+    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];  
+    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void send() 
+    {
+    GLES20.glUniform1i( mNumEffectsH, mNumEffects);
+      
+    if( mNumEffects>0 )
+      {     
+      GLES20.glUniform1iv( mTypeH    ,  mNumEffects, mType    ,0);
+      GLES20.glUniform3fv( mUniformsH,3*mNumEffects, mUniforms,0);
+      }  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void sendZero() 
+    {
+    GLES20.glUniform1i( mNumEffectsH, 0);
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Do various post-processing on already computed effects.
+// 1) move all Points and scale all Region radii by a ModelView matrix
+// 2) in case of macroblock, pre-compute some values so that we don't have to do it in the fragment shader.
+  
+  void postprocess(float[] MVmatrix)
+    {
+    if( mNumEffects>0 )
+      {
+      float tx,ty;   
+      float w = (float)Math.sqrt(MVmatrix[0]*MVmatrix[0] + MVmatrix[4]*MVmatrix[4]);  // The scale factors are the lengths of the first 3 vectors of the upper-left 3x3 submatrix; here
+      float h = (float)Math.sqrt(MVmatrix[1]*MVmatrix[1] + MVmatrix[5]*MVmatrix[5]);  // m[2]=m[6]=m[8]=m[9]=0 so it is really only the upper-left 2x2 matrix.
+   
+      for(int i=0; i<mNumEffects; i++)
+        {   
+        tx = mBuf[4*i  ]-mObjHalfX; // we have to invert y and move everything by (half of bmp width, half of bmp height)
+        ty =-mBuf[4*i+1]+mObjHalfY; //
+      
+        mUniforms[NUM_UNIFORMS*i+4] = w*mBuf[4*i+2];                                  // in fragment shader rx and ry radii are the second and third values of the Region thus 9*i+4 and 9*i+5
+        mUniforms[NUM_UNIFORMS*i+5] = h*mBuf[4*i+3];                                  // 
+     // mUniforms[NUM_UNIFORMS*i+6] =                                                 // this value is not used in Fragment Shader   
+        mUniforms[NUM_UNIFORMS*i+7] = MVmatrix[0]*tx + MVmatrix[4]*ty + MVmatrix[12]; // multiply the ModelView matrix times the (x,y,0,1) point, i.e. the (x,y) point on the surface of the bitmap.
+        mUniforms[NUM_UNIFORMS*i+8] = MVmatrix[1]*tx + MVmatrix[5]*ty + MVmatrix[13]; //  
+        
+        if( mType[i]==EffectNames.MACROBLOCK.ordinal() ) // fill up the .y and .z components of the Interpolated values already to avoid having to compute this in the fragment shader
+          {
+          mUniforms[NUM_UNIFORMS*i+1] = 2.0f*mObjHalfX/mUniforms[NUM_UNIFORMS*i];
+          mUniforms[NUM_UNIFORMS*i+2] = 2.0f*mObjHalfY/mUniforms[NUM_UNIFORMS*i];
+          }
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+       
+  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, Interpolator2D point)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects); 
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = point;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+   
+      return addBase(eln); 
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, float x, float y)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects);    
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = null;
+      mBuf[4*mNumEffects  ] = x;
+      mBuf[4*mNumEffects+1] = y;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+   
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+       
+  synchronized long add(EffectNames eln, Interpolator1D inter, Float3D c, Float4D region, Interpolator2D point)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = point;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+   
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
+     
+      return addBase(eln); 
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(EffectNames eln, Interpolator1D inter, Float3D c, Float4D region, float x, float y)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = null;
+      mBuf[4*mNumEffects  ] = x;
+      mBuf[4*mNumEffects+1] = y;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
+   
+      return addBase(eln);
+      }
+       
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+       
+  synchronized long add(EffectNames eln, float t, Float3D c, Float4D region, Interpolator2D point)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      mInterI[mNumEffects] = null;
+      mInterP[mNumEffects] = point;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+   
+      mUniforms[NUM_UNIFORMS*mNumEffects+0] = t;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
+     
+      return addBase(eln); 
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(EffectNames eln, float t, Float3D c, Float4D region, float x, float y)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      mInterI[mNumEffects] = null;
+      mInterP[mNumEffects] = null;
+      mBuf[4*mNumEffects  ] = x;
+      mBuf[4*mNumEffects+1] = y;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+0] = t;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
+   
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of FragmentEffect   
+  }
diff --git a/src/main/java/org/distorted/library/EffectQueueMatrix.java b/src/main/java/org/distorted/library/EffectQueueMatrix.java
new file mode 100644
index 0000000..5dacdfd
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectQueueMatrix.java
@@ -0,0 +1,345 @@
+package org.distorted.library;
+
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueueMatrix extends EffectQueue
+  {   
+  private static final int NUM_UNIFORMS = 7;
+  private static final int INDEX = EffectTypes.MATRIX.ordinal();
+  private static float[] mMVPMatrix= new float[16];
+  private static float[] mTmpMatrix= new float[16];
+  
+  private static int mBmpDH;      // This is a handle to half a bitmap dimensions
+  private static int mDepthH;     // Handle to the max Depth, i.e (farplane-nearplane)/2
+  private static int mMVPMatrixH; // pass in the transformation matrix
+  private static int mMVMatrixH;  // pass in the modelview matrix.
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectQueueMatrix(DistortedObject obj)
+    { 
+    super(obj,NUM_UNIFORMS, INDEX );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void multiplyByQuat(float[] matrix, float X, float Y, float Z, float W)
+    {
+    float xx= X * X;
+    float xy= X * Y;
+    float xz= X * Z;
+    float xw= X * W;
+    float yy= Y * Y;
+    float yz= Y * Z;
+    float yw= Y * W;
+    float zz= Z * Z;
+    float zw= Z * W;
+
+    mTmpMatrix[0]  = 1 - 2 * ( yy + zz );
+    mTmpMatrix[1]  =     2 * ( xy - zw );
+    mTmpMatrix[2]  =     2 * ( xz + yw );
+    mTmpMatrix[4]  =     2 * ( xy + zw );
+    mTmpMatrix[5]  = 1 - 2 * ( xx + zz );
+    mTmpMatrix[6]  =     2 * ( yz - xw );
+    mTmpMatrix[8]  =     2 * ( xz - yw );
+    mTmpMatrix[9]  =     2 * ( yz + xw );
+    mTmpMatrix[10] = 1 - 2 * ( xx + yy );
+    mTmpMatrix[3]  = mTmpMatrix[7] = mTmpMatrix[11] = mTmpMatrix[12] = mTmpMatrix[13] = mTmpMatrix[14] = 0;
+    mTmpMatrix[15] = 1;
+    
+    Matrix.multiplyMM(mMVPMatrix, 0, matrix, 0, mTmpMatrix, 0);  
+    for(int j=0; j<16; j++) matrix[j] = mMVPMatrix[j];   
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only max Byte.MAX_VALUE concurrent effects per bitmap.
+// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
+  
+  static boolean setMax(int m)
+    {
+    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[INDEX] )
+      {
+           if( m<0              ) m = 0;
+      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
+      
+      mMax[INDEX] = m;
+      return true;
+      }
+   
+    return false;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static int getMax()
+    {
+    return mMax[INDEX];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mBmpDH     = GLES20.glGetUniformLocation(mProgramH, "u_bmpD");
+    mDepthH    = GLES20.glGetUniformLocation(mProgramH, "u_Depth");
+    mMVPMatrixH= GLES20.glGetUniformLocation(mProgramH, "u_MVPMatrix");
+    mMVMatrixH = GLES20.glGetUniformLocation(mProgramH, "u_MVMatrix"); 
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void compute(long currTime) 
+    {
+    if( currTime==mTime ) return;
+    if( mTime==0 ) mTime = currTime;
+    long step = (currTime-mTime);
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mInterI[i]==null ) continue;    
+           
+      if( mInterP[i]!=null ) 
+        {
+        mInterP[i].interpolateMain(mUniforms, NUM_UNIFORMS*i, mCurrentDuration[i]);
+        }
+        
+      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i+3, mCurrentDuration[i], step) )      
+        {   
+        for(int j=0; j<mNumListeners; j++)   
+          EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                          EffectMessage.EFFECT_FINISHED, 
+                                         (mID[i]<<EffectTypes.LENGTH)+EffectTypes.MATRIX.type,
+                                          mType[i], 
+                                          mBitmapID); 
+       
+        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i+3) )
+          {  
+          remove(i);
+          i--;
+          continue;
+          }
+        }
+    
+      mCurrentDuration[i] += step;
+      }
+     
+    mTime = currTime;  
+    }  
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected void moveEffect(int index)
+    {
+    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
+    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
+    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];
+    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];
+    mUniforms[NUM_UNIFORMS*index+4] = mUniforms[NUM_UNIFORMS*(index+1)+4];
+    mUniforms[NUM_UNIFORMS*index+5] = mUniforms[NUM_UNIFORMS*(index+1)+5];
+    mUniforms[NUM_UNIFORMS*index+6] = mUniforms[NUM_UNIFORMS*(index+1)+6];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// here construct the ModelView Matrix
+
+  synchronized void send(float[] viewMatrix, DistortedProjection dp) 
+    {
+    Matrix.setIdentityM(viewMatrix, 0);
+    Matrix.translateM(viewMatrix, 0, -dp.width/2, dp.height/2, -dp.distance);
+    
+    float x,y,z, sx,sy,sz=1.0f;
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if (mType[i] == EffectNames.ROTATE.ordinal() )
+        {
+        x = mUniforms[NUM_UNIFORMS*i  ];
+        y = mUniforms[NUM_UNIFORMS*i+1];
+        z = mUniforms[NUM_UNIFORMS*i+2];
+     
+        Matrix.translateM(viewMatrix, 0, x,-y, z); 
+        Matrix.rotateM( viewMatrix, 0, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);  
+        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
+        }
+      else if(mType[i] == EffectNames.QUATERNION.ordinal() )
+        {
+        x = mUniforms[NUM_UNIFORMS*i  ];
+        y = mUniforms[NUM_UNIFORMS*i+1];
+        z = mUniforms[NUM_UNIFORMS*i+2];
+     	
+        Matrix.translateM(viewMatrix, 0, x,-y, z); 
+        multiplyByQuat(viewMatrix, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);
+        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
+        }
+      else if(mType[i] == EffectNames.MOVE.ordinal() )
+        {
+        sx = mUniforms[NUM_UNIFORMS*i+3];   
+        sy = mUniforms[NUM_UNIFORMS*i+4];   
+        sz = mUniforms[NUM_UNIFORMS*i+5];   
+        
+        Matrix.translateM(viewMatrix, 0, sx,-sy, sz);   
+        }
+      else if(mType[i] == EffectNames.SCALE.ordinal() )
+        {
+        sx = mUniforms[NUM_UNIFORMS*i+3];   
+        sy = mUniforms[NUM_UNIFORMS*i+4];   
+        sz = mUniforms[NUM_UNIFORMS*i+5];   
+
+        Matrix.scaleM(viewMatrix, 0, sx, sy, sz);  
+        }
+      else if(mType[i] == EffectNames.SHEAR.ordinal() )
+        {
+        x  = mUniforms[NUM_UNIFORMS*i  ];
+        y  = mUniforms[NUM_UNIFORMS*i+1];
+        z  = mUniforms[NUM_UNIFORMS*i+2];
+        
+        sx = mUniforms[NUM_UNIFORMS*i+3];   
+        sy = mUniforms[NUM_UNIFORMS*i+4];   
+        sz = mUniforms[NUM_UNIFORMS*i+5];   
+        
+        Matrix.translateM(viewMatrix, 0, x,-y, z); 
+      
+        viewMatrix[4] += sx*viewMatrix[0]; // Multiply viewMatrix by 1 x 0 0 , i.e. X-shear. TODO: change this so it is symmetric w respect to all the axis.
+        viewMatrix[5] += sx*viewMatrix[1]; //                        0 1 0 0 
+        viewMatrix[6] += sx*viewMatrix[2]; //                        0 0 1 0
+        viewMatrix[7] += sx*viewMatrix[3]; //                        0 0 0 1
+      
+        viewMatrix[0] += sy*viewMatrix[4]; // Multiply viewMatrix by 1 0 0 0 , i.e. Y-shear. TODO: change this so it is symmetric w respect to all the axis.
+        viewMatrix[1] += sy*viewMatrix[5]; //                        y 1 0 0
+        viewMatrix[2] += sy*viewMatrix[6]; //                        0 0 1 0
+        viewMatrix[3] += sy*viewMatrix[7]; //                        0 0 0 1      
+      
+        // TODO: implement Z-shear.
+        
+        Matrix.translateM(viewMatrix, 0,-x, y, -z);
+        }
+      }
+   
+    Matrix.translateM(viewMatrix, 0, mObjHalfX,-mObjHalfY, -mObjHalfZ);
+    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, viewMatrix, 0);
+    
+    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
+    GLES20.glUniform1f( mDepthH, dp.depth);   
+    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, viewMatrix, 0);
+    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// here construct the ModelView Matrix, but without any effects
+
+  synchronized void sendNoEffects(DistortedProjection dp) 
+    {
+    Matrix.setIdentityM(mTmpMatrix, 0);
+    Matrix.translateM(mTmpMatrix, 0, mObjHalfX-dp.width/2, dp.height/2-mObjHalfY, mObjHalfZ-dp.distance);
+    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, mTmpMatrix, 0);
+    
+    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
+    GLES20.glUniform1f( mDepthH, dp.depth);  
+    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, mTmpMatrix, 0);
+    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator i)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = p;
+      mInterI[mNumEffects] = i;
+      
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator i)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = null;
+      mInterI[mNumEffects] = i;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
+            
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator1D i, float aX, float aY, float aZ)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = null;
+      mInterI[mNumEffects] = i;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
+      
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator1D i, float aX, float aY, float aZ)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = p;
+      mInterI[mNumEffects] = i;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
+      
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float x, float y, float z, float aA, float aX, float aY, float aZ)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = null; 
+      mInterI[mNumEffects] = null;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] =  x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] =  y;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] =  z;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = aA;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
+      
+      return addBase(eln);   
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  }
diff --git a/src/main/java/org/distorted/library/EffectQueueOther.java b/src/main/java/org/distorted/library/EffectQueueOther.java
new file mode 100644
index 0000000..8b36ff4
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectQueueOther.java
@@ -0,0 +1,98 @@
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import java.util.Vector;
+
+/**
+ * Do NOT base this on EffectQueue - this is an entirely different animal than the first 3 EffectLists.
+ * The Effects in here will be executed after all the shaders have been executed - thus there are no
+ * uniforms to send, no real queues to maintain.
+ * <p>
+ * Only 2 effects here ATM:
+ * - save current Surface to a PNG file
+ * - save current animation to a .MP4 file
+ *
+ * In contrast to the other EffectLists, only one instance of each allowed at any given moment - thus
+ * this is not even a real EffectQueue, it is named so only for consistency with the others.
+ */
+public class EffectQueueOther
+  {
+  private Vector<EffectListener> mListeners =null;
+  private int mNumListeners=0;  // ==mListeners.length(), but we only create mListeners if the first one gets added
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public EffectQueueOther(DistortedObject obj)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void send()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized int abortAll()
+    {
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void addListener(EffectListener el)
+    {
+    if( mListeners==null ) mListeners = new Vector<>(2,2);
+
+    mListeners.add(el);
+    mNumListeners++;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeListener(EffectListener el)
+    {
+    if( mNumListeners>0 )
+      {
+      mListeners.remove(el);
+      mNumListeners--;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized boolean removeByID(long id)
+    {
+    //....
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized boolean removeByType(EffectNames effect)
+    {
+    // ...
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected boolean printByID(long id)
+    {
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(EffectNames eln, String filename)
+    {
+    return 0;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/EffectQueueVertex.java b/src/main/java/org/distorted/library/EffectQueueVertex.java
new file mode 100644
index 0000000..f9987e3
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectQueueVertex.java
@@ -0,0 +1,236 @@
+package org.distorted.library;
+
+import android.opengl.GLES20;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueueVertex extends EffectQueue
+  { 
+  private static final int NUM_UNIFORMS = 9;
+  private static final int INDEX = EffectTypes.VERTEX.ordinal();
+  private static int mNumEffectsH;
+  private static int mTypeH;
+  private static int mUniformsH;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectQueueVertex(DistortedObject obj)
+    { 
+    super(obj,NUM_UNIFORMS,INDEX);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only max Byte.MAX_VALUE concurrent effects per bitmap.
+// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
+  
+  static boolean setMax(int m)
+    {
+    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[INDEX] )
+      {
+           if( m<0              ) m = 0;
+      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
+      
+      mMax[INDEX] = m;
+      return true;
+      }
+   
+    return false;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static int getMax()
+    {
+    return mMax[INDEX];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mNumEffectsH= GLES20.glGetUniformLocation( mProgramH, "vNumEffects");
+    mTypeH      = GLES20.glGetUniformLocation( mProgramH, "vType");
+    mUniformsH  = GLES20.glGetUniformLocation( mProgramH, "vUniforms");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void compute(long currTime) 
+    {
+    if( currTime==mTime ) return;
+    if( mTime==0 ) mTime = currTime;
+    long step = (currTime-mTime);
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mInterI[i]==null ) continue;    
+      
+      if( mInterP[i]!=null ) 
+        {
+        mInterP[i].interpolateMain(mUniforms, NUM_UNIFORMS*i+7, mCurrentDuration[i]);
+      
+        mUniforms[NUM_UNIFORMS*i+7] = mUniforms[NUM_UNIFORMS*i+7]-mObjHalfX;
+        mUniforms[NUM_UNIFORMS*i+8] =-mUniforms[NUM_UNIFORMS*i+8]+mObjHalfY;
+        }
+        
+      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i, mCurrentDuration[i], step) )      
+        {
+        for(int j=0; j<mNumListeners; j++)   
+          EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                          EffectMessage.EFFECT_FINISHED, 
+                                         (mID[i]<<EffectTypes.LENGTH)+EffectTypes.VERTEX.type,
+                                          mType[i], 
+                                          mBitmapID); 
+      
+        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i) )
+          {
+          remove(i);
+          i--;
+          continue;
+          }
+        }
+     
+      mCurrentDuration[i] += step;
+      }
+     
+    mTime = currTime;  
+    }  
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected void moveEffect(int index)
+    {
+    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
+    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
+    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];
+    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];
+    mUniforms[NUM_UNIFORMS*index+4] = mUniforms[NUM_UNIFORMS*(index+1)+4];
+    mUniforms[NUM_UNIFORMS*index+5] = mUniforms[NUM_UNIFORMS*(index+1)+5];
+    mUniforms[NUM_UNIFORMS*index+6] = mUniforms[NUM_UNIFORMS*(index+1)+6];
+    mUniforms[NUM_UNIFORMS*index+7] = mUniforms[NUM_UNIFORMS*(index+1)+7];
+    mUniforms[NUM_UNIFORMS*index+8] = mUniforms[NUM_UNIFORMS*(index+1)+8];
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void send() 
+    {
+    GLES20.glUniform1i( mNumEffectsH, mNumEffects);
+      
+    if( mNumEffects>0 )
+      {     
+      GLES20.glUniform1iv( mTypeH    ,  mNumEffects, mType    ,0);
+      GLES20.glUniform3fv( mUniformsH,3*mNumEffects, mUniforms,0);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void sendZero() 
+    {
+    GLES20.glUniform1i( mNumEffectsH, 0);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Do various post-processing on already computed effects.
+// 1) here unlike in the fragment queue, we don't have to multiply the points by ModelView matrix because that gets done in the shader.
+// 2) in case of swirl, pre-compute the sine and cosine of its rotation angle
+  
+  void postprocess()
+    {
+    double d;  
+     
+    for(int i=0; i<mNumEffects; i++)
+      {      
+      if( mType[i]==EffectNames.SWIRL.ordinal() )
+        {
+        d = Math.PI*mUniforms[NUM_UNIFORMS*i]/180;  
+        mUniforms[NUM_UNIFORMS*i+1] = (float)Math.sin(d);
+        mUniforms[NUM_UNIFORMS*i+2] = (float)Math.cos(d);
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, Interpolator2D point)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects);    
+      
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = point;
+
+      return addPriv(eln,region);
+      }
+      
+    return -1;
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, float x, float y)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects);    
+      
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = null;
+      mUniforms[NUM_UNIFORMS*mNumEffects+7] = x-mObjHalfX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+8] =-y+mObjHalfY;  
+     
+      return addPriv(eln,region);
+      }
+      
+    return -1;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float v1, float v2, float v3, Float4D region, float x, float y)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects); 
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] = v1;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = v2;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = v3;  
+     
+      mInterI[mNumEffects] = null;
+      mInterP[mNumEffects] = null;
+      mUniforms[NUM_UNIFORMS*mNumEffects+7] = x-mObjHalfX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+8] =-y+mObjHalfY;  
+      
+      return addPriv(eln,region);    
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private long addPriv(EffectNames eln, Float4D region)
+    {    
+    if( region!=null )
+      {
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = region.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] =-region.y;   // invert y already
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = region.z<=0.0f ? 1000*mObjHalfX : region.z;
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = region.w;
+      }
+    else
+      {
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = 0.0f;
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = 0.0f;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = 1000*mObjHalfX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = 0.0f;
+      }
+    
+    return addBase(eln);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of VertexEffect  
+  }
diff --git a/src/main/java/org/distorted/library/EffectTypes.java b/src/main/java/org/distorted/library/EffectTypes.java
index e9ab8cd..b29af21 100644
--- a/src/main/java/org/distorted/library/EffectTypes.java
+++ b/src/main/java/org/distorted/library/EffectTypes.java
@@ -22,13 +22,13 @@ public enum EffectTypes
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// called from EffectList
+// called from EffectQueue
 
   static void reset(int[] maxtable)
     {
     maxtable[0] = 5;  // By default, there can be a maximum 5 MATRIX effects acting on a single
                       // DistortedObject at any given time. This can be changed with a call to
-                      // EffectListMatrix.setMax(int)
+                      // EffectQueueMatrix.setMax(int)
 
     maxtable[1] = 5;  // Max 5 VERTEX Effects
     maxtable[2] = 5;  // Max 5 FRAGMENT Effects
