commit 310e14fb8e25868c8f73d36fb47e11585cf574d2
Author: leszek <leszek@koltunski.pl>
Date:   Wed Jun 7 00:49:29 2017 +0100

    Some progress with Effect classes.
    
    Big mess - nothing compiles now; classes moved around.

diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
index 87c8bd8..86afec7 100644
--- a/src/main/java/org/distorted/library/Distorted.java
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -23,6 +23,10 @@ import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ConfigurationInfo;
 import android.content.res.Resources;
+
+import org.distorted.library.effect.EffectMessageSender;
+import org.distorted.library.effect.EffectQueue;
+import org.distorted.library.effect.EffectQueuePostprocess;
 import org.distorted.library.program.*;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -84,8 +88,11 @@ public class Distorted
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static boolean isInitialized()
+/**
+ * Have we called onCreate yet, ie have we initialized the library?
+ * @return <code>true</code> if the library is initilized and ready for action.
+ */
+  public static boolean isInitialized()
     {
     return mInitialized;
     }
diff --git a/src/main/java/org/distorted/library/DistortedEffects.java b/src/main/java/org/distorted/library/DistortedEffects.java
index 8fc1ac6..4854804 100644
--- a/src/main/java/org/distorted/library/DistortedEffects.java
+++ b/src/main/java/org/distorted/library/DistortedEffects.java
@@ -22,6 +22,10 @@ package org.distorted.library;
 import android.content.res.Resources;
 import android.opengl.GLES30;
 
+import org.distorted.library.effect.EffectQueue;
+import org.distorted.library.effect.EffectQueueFragment;
+import org.distorted.library.effect.EffectQueueMatrix;
+import org.distorted.library.effect.EffectQueueVertex;
 import org.distorted.library.message.EffectListener;
 import org.distorted.library.program.DistortedProgram;
 import org.distorted.library.program.FragmentCompilationException;
@@ -92,9 +96,9 @@ public class DistortedEffects
   private static long mNextID =0;
   private long mID;
 
-  private EffectQueueMatrix   mM;
+  private EffectQueueMatrix mM;
   private EffectQueueFragment mF;
-  private EffectQueueVertex   mV;
+  private EffectQueueVertex mV;
 
   private boolean matrixCloned, vertexCloned, fragmentCloned;
 
diff --git a/src/main/java/org/distorted/library/DistortedEffectsPostprocess.java b/src/main/java/org/distorted/library/DistortedEffectsPostprocess.java
index c652622..9368bff 100644
--- a/src/main/java/org/distorted/library/DistortedEffectsPostprocess.java
+++ b/src/main/java/org/distorted/library/DistortedEffectsPostprocess.java
@@ -19,6 +19,8 @@
 
 package org.distorted.library;
 
+import org.distorted.library.effect.EffectQueue;
+import org.distorted.library.effect.EffectQueuePostprocess;
 import org.distorted.library.message.EffectListener;
 import org.distorted.library.type.Data1D;
 import org.distorted.library.type.Data4D;
diff --git a/src/main/java/org/distorted/library/DistortedOutputSurface.java b/src/main/java/org/distorted/library/DistortedOutputSurface.java
index c015d84..ab1f611 100644
--- a/src/main/java/org/distorted/library/DistortedOutputSurface.java
+++ b/src/main/java/org/distorted/library/DistortedOutputSurface.java
@@ -26,7 +26,7 @@ import java.util.ArrayList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-abstract class DistortedOutputSurface extends DistortedSurface implements DistortedSlave
+public abstract class DistortedOutputSurface extends DistortedSurface implements DistortedSlave
 {
 //////////// DEBUG FLAGS /////////////////////////////////////////////
 /**
diff --git a/src/main/java/org/distorted/library/DistortedRenderState.java b/src/main/java/org/distorted/library/DistortedRenderState.java
index 28df641..4d6639d 100644
--- a/src/main/java/org/distorted/library/DistortedRenderState.java
+++ b/src/main/java/org/distorted/library/DistortedRenderState.java
@@ -28,7 +28,7 @@ import android.opengl.GLES30;
  * This is a member of DistortedNode. Remembers the OpenGL state we want to set just before rendering
  * the Node.
  */
-class DistortedRenderState
+public class DistortedRenderState
 {
   // TODO: figure this out dynamically; this assumes 8 bit stencil buffer.
   private static final int STENCIL_MASK = (1<<8)-1;
diff --git a/src/main/java/org/distorted/library/EffectMessageSender.java b/src/main/java/org/distorted/library/EffectMessageSender.java
deleted file mode 100644
index 8f8bda6..0000000
--- a/src/main/java/org/distorted/library/EffectMessageSender.java
+++ /dev/null
@@ -1,143 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-import org.distorted.library.message.EffectListener;
-import org.distorted.library.message.EffectMessage;
-
-import java.util.Vector;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-final class EffectMessageSender extends Thread
-  {
-  private class Message
-    {
-    EffectListener mListener;
-    EffectMessage mMessage;
-    long mEffectID;
-    int mEffectName;
-    long mBitmapID;
-
-    Message(EffectListener l, EffectMessage m, long id, int name, long bmpID)
-      {
-      mListener   = l;
-      mMessage    = m;
-      mEffectID   = id;
-      mEffectName = name;
-      mBitmapID   = bmpID;
-      }
-    }
-  
-  private static Vector<Message> mList =null;
-  private static EffectMessageSender mThis=null;
-  private static volatile boolean mNotify  = false;
-  private static volatile boolean mRunning = false;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  private EffectMessageSender() 
-    {
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void startSending()
-    {
-    mRunning = true;
-
-    if( mThis==null )
-      {
-      mList = new Vector<>();
-      mThis = new EffectMessageSender();
-      mThis.start();
-      }
-    else  
-      {  
-      synchronized(mThis)
-        {
-        mThis.notify();
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  static void stopSending()
-    {
-    mRunning = false;
-
-    if( mThis!=null )
-      {
-      synchronized(mThis)
-        {
-        mThis.notify();
-        }
-      }
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  public void run()
-    {
-    Message tmp;  
-     
-    while(mRunning)
-      {
-      //android.util.Log.i("SENDER", "sender thread running...");
-
-      while( mList.size()>0 )
-        {
-        tmp = mList.remove(0);
-        tmp.mListener.effectMessage(tmp.mMessage, tmp.mEffectID, EffectNames.getName(tmp.mEffectName),tmp.mBitmapID);
-        }
-
-      synchronized(mThis)
-        {
-        if (!mNotify)
-          {
-          try  { mThis.wait(); }
-          catch(InterruptedException ex) { }
-          }
-        mNotify = false;
-        }
-      }
-
-    mThis = null;
-    mList.clear();
-
-    //android.util.Log.i("SENDER", "sender thread finished...");
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-        
-  static void newMessage(EffectListener l, EffectMessage m, long id, int name, long bmpID)
-    {
-    Message msg = mThis.new Message(l,m,id,name,bmpID);
-    mList.add(msg);
-
-    synchronized(mThis)
-      {
-      mNotify = true;
-      mThis.notify();
-      }
-    }
-  }
-///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectNames.java b/src/main/java/org/distorted/library/EffectNames.java
deleted file mode 100644
index 0e9f3ce..0000000
--- a/src/main/java/org/distorted/library/EffectNames.java
+++ /dev/null
@@ -1,403 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Names of Effects one can add to the DistortedEffects queues.
- * <p>
- * Effect's 'Type' is one of the constants defined in {@link EffectTypes}.
- * </p>
- * <p>
- * Effect's 'Uniforms' are a vector of 7 (matrix effects) 12 (vertex) or 8 (fragment) floats, which
- * together form full information how to compute a given effect.
- * Typically, some of those values will be Interpolated in CPU (by one of the 'EffectQueueX.compute()'
- * methods) and the effect of such Interpolation sent to the Shaders.
- * </p>
- * <p>
- * Effect's 'Unity' is such a particular vector of its 'interpolated values' which makes the
- * effect NULL. For example, if the effect is 'MOVE' by a 3-dimensional vector, then a 'NULL
- * MOVE' is a MOVE by vector (0,0,0), thus (0,0,0) is the unity of the MOVE effect.
- * This is used by the EffectQueue classes to decide if the final form of the Effect is NULL - and
- * thus if it can safely be removed from Effect Queues without affecting the visual in any way.
- * </p>
- */
-public enum EffectNames
-  {
-  // EFFECT NAME /////// EFFECT TYPE ////////////// UNITY /////////////// DIM // REGION // CENTER
-
-  /////////////////////////////////////////////////////////////////////////////////
-  // MATRIX EFFECTS.
-  // Always 7 Uniforms: 4 per-effect interpolated values + 3 dimensional center.
- /**
-   * Rotate the whole Object around a center point (in angle-axis notation).
-   * <p>
-   * Uniforms: (angle,axisX,axisY,axisZ,centerX,centerY,centerZ)
-   * <p>
-   * Unity: angle==0
-   */
-  ROTATE           ( EffectTypes.MATRIX  ,   new float[] {0.0f}           , 4, false, true ),
- /**
-   * Rotate the whole Object around a center point (in quaternion notation).
-   * <p>
-   * Uniforms: (quatX,quatY,quatZ,quatW,centerX,centerY,centerZ)
-   * <p>
-   * Unity: (quatX,quatY,quatZ) = (0,0,0)
-   */
-  QUATERNION       ( EffectTypes.MATRIX  ,   new float[] {0.0f,0.0f,0.0f} , 4, false, true ),
- /**
-   * Move the whole Object by a vector.
-   * <p>
-   * Uniforms: (vectorX,vectorY,vectorZ,UNUSED,UNUSED,UNUSED,UNUSED)
-   * <p>
-   * Unity: (vectorX,vectorY,vectorZ) = (0,0,0)
-   */
-  MOVE             ( EffectTypes.MATRIX  ,   new float[] {0.0f,0.0f,0.0f} , 3, false, false ),
- /**
-   * Scale the whole Object independently in all 3 dimensions.
-   * <p>
-   * Uniforms: (scaleX,scaleY,scaleZ,UNUSED,UNUSED,UNUSED,UNUSED)
-   * <p>
-   * Unity: (scaleX,scaleY,scaleZ) = (1,1,1)
-   */
-  SCALE            ( EffectTypes.MATRIX  ,   new float[] {1.0f,1.0f,1.0f} , 3, false, false ),
- /**
-   * Shear the whole Object in 3 dimensions around a center point.
-   * <p>
-   * Uniforms: (shearX,shearY,shearZ,UNUSED,centerX,centerY,centerZ)
-   * <p>
-   * Unity:  (shearX,shearY,shearZ) = (0,0,0)
-   */
-  SHEAR            ( EffectTypes.MATRIX  ,   new float[] {0.0f,0.0f,0.0f} , 3, false, true ),
-  // add new Matrix effects here...
-
- /////////////////////////////////////////////////////////////////////////////////
- // VERTEX EFFECTS
- // Always 12 Uniforms: 5 per-effect interpolated values, 3-dimensional center of
- // the effect, 4-dimensional Region
- /**
-   * Apply a 3D vector of force to area around a point on the surface of the Object.
-   * <p>
-   * Uniforms: (forceX,forceY,forceZ,UNUSED,
-   *            UNUSED,centerX,centerY,centerZ,
-   *            regionX,regionY,regionRX,regionRY)
-   * <p>
-   * Unity: (forceX,forceY,forceZ) = (0,0,0)
-   */
-  DISTORT          ( EffectTypes.VERTEX  ,   new float[] {0.0f,0.0f,0.0f} , 3, true, true ),
- /**
-   * Deform the whole Object by applying a 3D vector of force to a center point.
-   * <p>
-   * Uniforms: (forceX,forceY,forceZ,UNUSED,
-   *            UNUSED,centerX,centerY,centerZ,
-   *            regionX,regionY,regionRX,regionRY)
-   * <p>
-   * Unity: (forceX,forceY,forceZ) = (0,0,0)
-   */
-  DEFORM           ( EffectTypes.VERTEX  ,   new float[] {0.0f,0.0f,0.0f} , 3, true, true ),
- /**
-   * Pull (or push away) all points around a center point to (from) it.
-   * <p>
-   * Uniforms: (sinkFactor,UNUSED,UNUSED,UNUSED,
-   *            UNUSED,centerX,centerY,centerZ,
-   *            regionX,regionY,regionRX,regionRY)
-   * <p>
-   * Unity: sinkFactor = 1
-   */
-  SINK             ( EffectTypes.VERTEX  ,   new float[] {1.0f}           , 1, true, true ),
-  /**
-   * Pull (or push away) all points around a line to (from) it.
-   * <p>
-   * Uniforms: (pinchFactor,lineAngle,UNUSED,UNUSED,
-   *            UNUSED,centerX,centerY,centerZ,
-   *            regionX,regionY,regionRX,regionRY)
-   * <p>
-   * Unity: sinkFactor = 1
-   */
-  PINCH            ( EffectTypes.VERTEX  ,   new float[] {1.0f}           , 2, true, true ),
- /**
-   * Smoothly rotate a limited area around a center point.
-   * <p>
-   * Uniforms: (swirlAngle,UNUSED,UNUSED,UNUSED,
-   *            UNUSED, centerX,centerY,centerZ,
-   *            regionX,regionY,regionRX,regionRY)
-   * <p>
-   * Unity: swirlAngle = 0
-   */
-  SWIRL            ( EffectTypes.VERTEX  ,   new float[] {0.0f}           , 1, true, true ),
-  /**
-   * Directional sinusoidal wave effect. The direction of the wave is given by the 'angle'
-   * parameters. Details: {@link DistortedEffects#wave(org.distorted.library.type.Data5D,org.distorted.library.type.Data3D)}
-   * <p>
-   * Uniforms: (amplitude,length,phase,angleAlpha,
-   *            angleBeta, centerX,centerY,centerZ,
-   *            regionX,regionY,regionRX,regionRY)
-   * <p>
-   * Unity: amplitude  = 0
-   */
-  WAVE             ( EffectTypes.VERTEX  ,   new float[] {0.0f}           , 5, true, true ),
-  // add new Vertex Effects here...
-
- /////////////////////////////////////////////////////////////////////////////////
- // FRAGMENT EFFECTS
- // Always 8 Uniforms: 4-per effect interpolated values, 4 dimensional Region.
- /**
-   * Make a given Region (partially) transparent.
-   * <p>
-   * Uniforms: (transparencyLevel,UNUSED,UNUSED,UNUSED, regionX, regionY, regionRX, regionRY)
-   * <p>
-   * Unity: transparencyLevel = 1
-   */
-  ALPHA            ( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false ),
- /**
-   * Make a given Region (partially) transparent.
-   * Effect smoothly fades towards the edges of the region.
-   * <p>
-   * Uniforms: (transparencyLevel,UNUSED,UNUSED,UNUSED, regionX, regionY, regionRX, regionRY)
-   * Unity: transparencyLevel = 1
-   */
-  SMOOTH_ALPHA     ( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false ),
- /**
-   * Blend current color in the texture with a given color.
-   * <p>
-   * Uniforms: (blendLevel,colorR,colorG,colorB, regionX, regionY, regionRX, regionRY)
-   * <p>
-   * Unity: blendLevel = 0
-   */
-  CHROMA           ( EffectTypes.FRAGMENT,   new float[] {0.0f}           , 4, true, false ),
- /**
-   * Smoothly blend current color in the texture with a given color.
-   * <p>
-   * Uniforms: (blendLevel,colorR,colorG,colorB, regionX, regionY, regionRX, regionRY)
-   * Unity: blendLevel = 0
-   */
-  SMOOTH_CHROMA    ( EffectTypes.FRAGMENT,   new float[] {0.0f}           , 4, true, false ),
- /**
-   * Change brightness level of a given Region.
-   * <p>
-   * Uniforms: (brightnessLevel,UNUSED,UNUSED,UNUSED, regionX, regionY, regionRX, regionRY)
-   * <p>
-   * Unity: brightnessLevel = 1
-   */
-  BRIGHTNESS       ( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false ),
- /**
-   * Smoothly change brightness level of a given Region.
-   * <p>
-   * Uniforms: (brightnessLevel,UNUSED,UNUSED,UNUSED, regionX, regionY, regionRX, regionRY)
-   * <p>
-   * Unity: brightnessLevel = 1
-   */
-  SMOOTH_BRIGHTNESS( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false ),
- /**
-   * Change saturation level of a given Region.
-   * <p>
-   * Uniforms: (saturationLevel,UNUSED,UNUSED,UNUSED, regionX, regionY, regionRX, regionRY)
-   * <p>
-   * Unity: saturationLevel = 1
-   */
-  SATURATION       ( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false ),
- /**
-   * Smoothly change saturation level of a given Region.
-   * <p>
-   * Uniforms: (saturationLevel,UNUSED,UNUSED,UNUSED, regionX, regionY, regionRX, regionRY)
-   * <p>
-   * Unity: saturationLevel = 1
-   */
-  SMOOTH_SATURATION( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false ),
- /**
-   * Change contrast level of a given Region.
-   * <p>
-   * Uniforms: (contrastLevel,UNUSED,UNUSED,UNUSED, regionX, regionY, regionRX, regionRY)
-   * <p>
-   * Unity: contrastLevel = 1
-   */
-  CONTRAST         ( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false ),
- /**
-   * Smoothly change contrast level of a given Region.
-   * <p>
-   * Uniforms: (contrastLevel,UNUSED,UNUSED,UNUSED, regionX, regionY, regionRX, regionRY)
-   * <p>
-   * Unity: contrastLevel = 1
-   */
-  SMOOTH_CONTRAST  ( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false ),
-  // add new Fragment effects here...
-
-  /////////////////////////////////////////////////////////////////////////////////
-  // POSTPROCESSING EFFECTS.
-  // Always 5 Uniforms: 5 per-effect interpolated values.
- /**
-   * Blur the area around the center.
-   * <p>
-   * Uniforms: (radius,UNUSED,UNUSED,UNUSED,UNUSED)
-   * <p>
-   * Unity: radius = 0
-   */
-  BLUR             ( EffectTypes.POSTPROCESS,new float[] {0.0f}          , 1, false, false ),
-/**
-   * Make the object Glow with a certain color and configurable radius of the halo around it.
-   * <p>
-   * Uniforms: (A,R,G,B,radius)
-   * <p>
-   * Unity: radius = 0
-   */
-  GLOW             ( EffectTypes.POSTPROCESS,new float[] {0.0f}          , 5, false, false );
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  private static final int MAXDIM = 4;  // maximum supported dimension of the Unity of an Effect
-                                        // (not to be confused with dimension of the Effect itself!)
-  private final EffectTypes type;
-  private final float[] unity;
-  private final int dimension;
-  private final boolean supportsR;
-  private final boolean supportsC;
-
-  private static final float[] unities;
-  private static final int[] unityDimensions;
-  private static final int[] dimensions;
-  private static final boolean[] supportsRegion;
-  private static final boolean[] supportsCenter;
-  private static final EffectNames[] names;
-
-  static
-    {
-    int len = values().length;
-    int i=0;
-    
-    unityDimensions = new int[len];
-    dimensions      = new int[len];
-    supportsRegion  = new boolean[len];
-    supportsCenter  = new boolean[len];
-    unities         = new float[MAXDIM*len];
-    names           = new EffectNames[len];
-
-    for(EffectNames name: EffectNames.values())
-      {
-      unityDimensions[i] = (name.unity==null ? 0 : name.unity.length);
-      dimensions[i]      = name.dimension;
-      supportsRegion[i]  = name.supportsR;
-      supportsCenter[i]  = name.supportsC;
-      names[i]           = name;
-
-      switch(unityDimensions[i])
-        {
-        case 4: unities[MAXDIM*i+3] = name.unity[3];
-        case 3: unities[MAXDIM*i+2] = name.unity[2];
-        case 2: unities[MAXDIM*i+1] = name.unity[1];
-        case 1: unities[MAXDIM*i  ] = name.unity[0];
-        case 0: break;
-        }
-      
-      i++;  
-      }
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  EffectNames(EffectTypes type, float[] unity, int dimension, boolean supportsR, boolean supportsC)
-    {
-    this.type      = type;
-    this.unity     = unity;
-    this.dimension = dimension;
-    this.supportsR = supportsR;
-    this.supportsC = supportsC;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static EffectTypes getType(int ordinal)
-    {
-    return names[ordinal].type;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static EffectNames getName(int ordinal)
-    {
-    return names[ordinal];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  static boolean isUnity(int ordinal, float[] buffer, int index)
-    {
-    switch(unityDimensions[ordinal])
-      {
-      case 0: return true;
-      case 1: return buffer[index  ]==unities[MAXDIM*ordinal  ];
-      case 2: return buffer[index  ]==unities[MAXDIM*ordinal  ] &&
-                     buffer[index+1]==unities[MAXDIM*ordinal+1];
-      case 3: return buffer[index  ]==unities[MAXDIM*ordinal  ] &&
-                     buffer[index+1]==unities[MAXDIM*ordinal+1] && 
-                     buffer[index+2]==unities[MAXDIM*ordinal+2];
-      case 4: return buffer[index  ]==unities[MAXDIM*ordinal  ] &&
-                     buffer[index+1]==unities[MAXDIM*ordinal+1] && 
-                     buffer[index+2]==unities[MAXDIM*ordinal+2] &&
-                     buffer[index+3]==unities[MAXDIM*ordinal+3];
-      }
-   
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static int size()
-    {
-    return values().length;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the Type of an individual Effect. For example, EffectNames.ROTATION.getType() will
- * return EffectTypes.MATRIX.
- * @return type of the effect.
- */
-  public EffectTypes getType()
-    {
-    return type;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Returns the dimension of an Effect (in other words, the number of interpolated values).
- * @return dimension of the Effect.
- */
-  public int getDimension() { return dimensions[ordinal()]; }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Do we support being masked by a Region?
- * @return true if the Effect supports being masked with a Region.
- */
-  public boolean supportsRegion() { return supportsRegion[ordinal()]; }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Does this Effect have a center?
- * @return true if the Effect has a center.
- */
-  public boolean supportsCenter() { return supportsCenter[ordinal()]; }
-
-  }
diff --git a/src/main/java/org/distorted/library/EffectQueue.java b/src/main/java/org/distorted/library/EffectQueue.java
deleted file mode 100644
index f131950..0000000
--- a/src/main/java/org/distorted/library/EffectQueue.java
+++ /dev/null
@@ -1,356 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-import org.distorted.library.message.EffectListener;
-import org.distorted.library.message.EffectMessage;
-import org.distorted.library.type.Dynamic;
-
-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[] mName;
-  protected float[] mUniforms;
-  protected float[] mCache;
-  protected Dynamic[][] mInter;
-  protected long[] mCurrentDuration;
-  protected byte[] mFreeIndexes;
-  protected byte[] mIDIndex;
-  protected long[] mID;
-  protected long mTime=0;
-  protected static int[] mMax = new int[EffectTypes.LENGTH];
-  protected int mMaxIndex;
-  protected Vector<EffectListener> mListeners =null;
-  protected int mNumListeners=0;  // ==mListeners.length(), but we only create mListeners if the first one gets added
-  protected long mObjectID;
-
-  private static boolean mCreated;
-
-  static
-    {
-    onDestroy();
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  EffectQueue(long id, int numUniforms, int numCache, int index)
-    {
-    mNumEffects   = 0;
-    mTotalEffects = 0;
-    mMaxIndex     = index;
-    mObjectID     = id;
-
-    int max = mMax[mMaxIndex];
-
-    if( max>0 )
-      {
-      mName            = new int[max];
-      mUniforms        = new float[numUniforms*max];
-      mInter           = new Dynamic[3][max];
-      mCurrentDuration = new long[max];
-      mID              = new long[max];
-      mIDIndex         = new byte[max];
-      mFreeIndexes     = new byte[max];
-     
-      for(byte i=0; i<max; i++) mFreeIndexes[i] = i;
-
-      if( numCache>0 )
-        {
-        mCache = new float[numCache*max];
-        }
-      }
-   
-    mCreated = true;  
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @SuppressWarnings("unused")
-  int getNumEffects()
-    {
-    return mNumEffects;  
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Only max Byte.MAX_VALUE concurrent effects per DistortedEffects object.
-// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
-// (although probably this many uniforms will not fit in the shaders anyway!)
-
-  static boolean setMax(int index, int m)
-    {
-    if( (!mCreated && !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(int index)
-    {
-    return mMax[index];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void registerForMessages(EffectListener el)
-    {
-    if( mListeners==null ) mListeners = new Vector<>(2,2);
-
-    if( !mListeners.contains(el) )
-      {
-      mListeners.add(el);
-      mNumListeners++;
-      }
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void deregisterForMessages(EffectListener el)
-    {
-    if( mListeners.remove(el) )
-      {
-      mNumListeners--;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onDestroy()
-    {
-    EffectTypes.reset(mMax);
-    mCreated = false;  
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized int removeByID(long id)
-    {
-    int i = getEffectIndex(id);
-   
-    if( i>=0 ) 
-      {
-      remove(i);
-      return 1;
-      }
-   
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized int removeByType(EffectNames effect)
-    {
-    int ret = 0;
-    int ord = effect.ordinal();  
-     
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mName[i]==ord )
-        {
-        remove(i);
-        i--;
-        ret++;
-        }
-      }
-   
-    return ret;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  private synchronized int getEffectIndex(long id)
-    {
-    int index = mIDIndex[(int)(id%mMax[mMaxIndex])];
-    return (index<mNumEffects && mID[index]==id ? index : -1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// we do want to notify Listeners if they called 'abortAll' themselves but don't want to notify
-// them if it is the library itself which is releasing resources.
-
-  synchronized int abortAll(boolean notify)
-    {
-    int ret = mNumEffects;
-    long removedID;
-    int removedName;
-
-    for(int i=0; i<ret; i++ )
-      {
-      mInter[0][i] = null;
-      mInter[1][i] = null;
-      mInter[2][i] = null;
-
-      if( notify )
-        {
-        removedID = mID[i];
-        removedName= mName[i];
-
-        for(int j=0; j<mNumListeners; j++)
-          EffectMessageSender.newMessage( mListeners.elementAt(j),
-                                          EffectMessage.EFFECT_REMOVED,
-                                          (removedID<<EffectTypes.LENGTH)+EffectNames.getType(removedName).type,
-                                          removedName,
-                                          mObjectID);
-        }
-      }
-
-    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 removedName= mName[effect];
-    
-    for(int j=0; j<mMax[mMaxIndex]; j++)
-      {
-      if( mIDIndex[j] > removedPosition ) mIDIndex[j]--; 
-      }
-         
-    for(int j=effect; j<mNumEffects; j++ ) 
-      {
-      mName[j]            = mName[j+1];
-      mInter[0][j]        = mInter[0][j+1];
-      mInter[1][j]        = mInter[1][j+1];
-      mInter[2][j]        = mInter[2][j+1];
-      mCurrentDuration[j] = mCurrentDuration[j+1];
-      mID[j]              = mID[j+1];
-    
-      moveEffect(j);
-      }
-   
-    mInter[0][mNumEffects] = null;
-    mInter[1][mNumEffects] = null;
-    mInter[2][mNumEffects] = null;
-
-    for(int i=0; i<mNumListeners; i++) 
-      EffectMessageSender.newMessage( mListeners.elementAt(i),
-                                      EffectMessage.EFFECT_REMOVED,
-                                      (removedID<<EffectTypes.LENGTH)+EffectNames.getType(removedName).type,
-                                      removedName,
-                                      mObjectID);
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  protected long addBase(EffectNames name)
-    {    
-    mName[mNumEffects]  = name.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)+name.getType().type;
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// used only for debugging
-
-  @SuppressWarnings("unused")
-  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 inter0 = mInter[0][index]==null;
-      boolean inter1 = mInter[1][index]==null;
-      boolean inter2 = mInter[2][index]==null;
-
-      android.util.Log.e("EffectQueue", "numEffects="+mNumEffects+" effect id="+id+" index="+index+
-                         " duration="+mCurrentDuration[index]+" inter[0] null="+inter0+" inter[1] null="+inter1+" inter[2] null="+inter2);
-
-      if( !inter0 ) android.util.Log.e("EffectQueue","inter[0]: "+mInter[0][index].print());
-      if( !inter1 ) android.util.Log.e("EffectQueue","inter[1]: "+mInter[1][index].print());
-      if( !inter2 ) android.util.Log.e("EffectQueue","inter[2]: "+mInter[2][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
deleted file mode 100644
index e65677e..0000000
--- a/src/main/java/org/distorted/library/EffectQueueFragment.java
+++ /dev/null
@@ -1,299 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-import android.opengl.GLES30;
-
-import org.distorted.library.message.EffectMessage;
-import org.distorted.library.type.Data1D;
-import org.distorted.library.type.Data3D;
-import org.distorted.library.type.Data4D;
-import org.distorted.library.type.Dynamic1D;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Dynamic4D;
-import org.distorted.library.type.Static1D;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectQueueFragment extends EffectQueue
-  {
-  private static final int NUM_UNIFORMS = 8;
-  private static final int NUM_CACHE    = 4;
-  private static final int INDEX = EffectTypes.FRAGMENT.ordinal();
-  private static int mNumEffectsH;
-  private static int mTypeH;
-  private static int mUniformsH;
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  EffectQueueFragment(long id)
-    { 
-    super(id,NUM_UNIFORMS,NUM_CACHE,INDEX);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void getUniforms(int mProgramH)
-    {
-    mNumEffectsH= GLES30.glGetUniformLocation( mProgramH, "fNumEffects");
-    mTypeH      = GLES30.glGetUniformLocation( mProgramH, "fType");
-    mUniformsH  = GLES30.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++)
-      {
-      mCurrentDuration[i] += step;
-
-      if( mInter[0][i]!=null && mInter[0][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,
-                                          mName[i],
-                                          mObjectID);
-      
-        if( EffectNames.isUnity(mName[i], mUniforms, NUM_UNIFORMS*i) )
-          {
-          remove(i);
-          i--;
-          continue;
-          }
-        else mInter[0][i] = null;
-        }
-
-      if( mInter[1][i]!=null ) mInter[1][i].interpolateMain( mCache   , NUM_CACHE*i     , mCurrentDuration[i], step);
-      if( mInter[2][i]!=null ) mInter[2][i].interpolateMain( mUniforms, NUM_UNIFORMS*i+1, 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];
-
-    mCache[NUM_CACHE*index  ] = mCache[NUM_CACHE*(index+1)  ];
-    mCache[NUM_CACHE*index+1] = mCache[NUM_CACHE*(index+1)+1];
-    mCache[NUM_CACHE*index+2] = mCache[NUM_CACHE*(index+1)+2];
-    mCache[NUM_CACHE*index+3] = mCache[NUM_CACHE*(index+1)+3];
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized void send(float halfX, float halfY)
-    {
-    GLES30.glUniform1i( mNumEffectsH, mNumEffects);
-
-    if( mNumEffects>0 )
-      {
-      for(int i=0; i<mNumEffects; i++)
-        {
-        mUniforms[NUM_UNIFORMS*i+4] = mCache[NUM_CACHE*i  ]-halfX;
-        mUniforms[NUM_UNIFORMS*i+5] =-mCache[NUM_CACHE*i+1]+halfY;
-        mUniforms[NUM_UNIFORMS*i+6] = mCache[NUM_CACHE*i+2];
-        mUniforms[NUM_UNIFORMS*i+7] = mCache[NUM_CACHE*i+3];
-        }
-
-      GLES30.glUniform1iv( mTypeH    ,                 mNumEffects, mName    ,0);
-      GLES30.glUniform4fv( mUniformsH,(NUM_UNIFORMS/4)*mNumEffects, mUniforms,0);
-      }  
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// alpha, brightness, contrast, saturation
-
-  synchronized long add(EffectNames eln, Data1D data)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( data instanceof Dynamic1D)
-        {
-        mInter[0][mNumEffects] = (Dynamic1D)data;
-        }
-      else if( data instanceof Static1D )
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)data).getX();
-        }
-      else return -1;
-
-      mInter[1][mNumEffects] = null;
-      mCache[NUM_CACHE*mNumEffects+2] = Float.MAX_VALUE;
-      mCache[NUM_CACHE*mNumEffects+3] = Float.MAX_VALUE;
-
-      mInter[2][mNumEffects] = null;
-
-      return addBase(eln); 
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// alpha, brightness, contrast, saturation
-
-  synchronized long add(EffectNames eln, Data1D data, Data4D region)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( data instanceof Dynamic1D)
-        {
-        mInter[0][mNumEffects] = (Dynamic1D)data;
-        }
-      else if( data instanceof Static1D )
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)data).getX();
-        }
-      else return -1;
-
-      if( region instanceof Dynamic4D)
-        {
-        mInter[1][mNumEffects] = (Dynamic4D)region;
-        }
-      else if( region instanceof Static4D )
-        {
-        mInter[1][mNumEffects]  = null;
-        mCache[NUM_CACHE*mNumEffects  ] = ((Static4D)region).getX();
-        mCache[NUM_CACHE*mNumEffects+1] = ((Static4D)region).getY();
-        mCache[NUM_CACHE*mNumEffects+2] = ((Static4D)region).getZ();
-        mCache[NUM_CACHE*mNumEffects+3] = ((Static4D)region).getW();
-        }
-      else return -1;
-
-      mInter[2][mNumEffects] = null;
-
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// chroma
-
-  synchronized long add(EffectNames eln, Data1D level, Data3D color, Data4D region)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( level instanceof Dynamic1D)
-        {
-        mInter[0][mNumEffects] = (Dynamic1D)level;
-        }
-      else if( level instanceof Static1D )
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)level).getX();
-        }
-      else return -1;
-
-      if( color instanceof Dynamic3D)
-        {
-        mInter[2][mNumEffects] = (Dynamic3D)color;
-        }
-      else if( color instanceof Static3D )
-        {
-        mInter[2][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = ((Static3D)color).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+2] = ((Static3D)color).getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+3] = ((Static3D)color).getZ();
-        }
-      else return -1;
-
-      if( region instanceof Dynamic4D)
-        {
-        mInter[1][mNumEffects] = (Dynamic4D)region;
-        }
-      else if( region instanceof Static4D )
-        {
-        mInter[1][mNumEffects]  = null;
-        mCache[NUM_CACHE*mNumEffects  ] = ((Static4D)region).getX();
-        mCache[NUM_CACHE*mNumEffects+1] = ((Static4D)region).getY();
-        mCache[NUM_CACHE*mNumEffects+2] = ((Static4D)region).getZ();
-        mCache[NUM_CACHE*mNumEffects+3] = ((Static4D)region).getW();
-        }
-      else return -1;
-
-      return addBase(eln); 
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// chroma
-
-  synchronized long add(EffectNames eln, Data1D level, Data3D color)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( level instanceof Dynamic1D)
-        {
-        mInter[0][mNumEffects] = (Dynamic1D)level;
-        }
-      else if( level instanceof Static1D )
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)level).getX();
-        }
-      else return -1;
-
-      if( color instanceof Dynamic3D)
-        {
-        mInter[2][mNumEffects] = (Dynamic3D)color;
-        }
-      else if( color instanceof Static3D )
-        {
-        mInter[2][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = ((Static3D)color).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+2] = ((Static3D)color).getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+3] = ((Static3D)color).getZ();
-        }
-      else return -1;
-
-      mInter[1][mNumEffects]  = null;
-      mCache[NUM_CACHE*mNumEffects+2] = Float.MAX_VALUE;
-      mCache[NUM_CACHE*mNumEffects+3] = Float.MAX_VALUE;
-
-      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
deleted file mode 100644
index cc7a92f..0000000
--- a/src/main/java/org/distorted/library/EffectQueueMatrix.java
+++ /dev/null
@@ -1,524 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-import android.opengl.GLES30;
-import android.opengl.Matrix;
-
-import org.distorted.library.message.EffectMessage;
-import org.distorted.library.type.Data1D;
-import org.distorted.library.type.Data3D;
-import org.distorted.library.type.Data4D;
-import org.distorted.library.type.Dynamic1D;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Dynamic4D;
-import org.distorted.library.type.DynamicQuat;
-import org.distorted.library.type.Static1D;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectQueueMatrix extends EffectQueue
-  {   
-  private static final int NUM_UNIFORMS = 7;
-  private static final int NUM_CACHE    = 0;
-  private static final int INDEX = EffectTypes.MATRIX.ordinal();
-
-  private static float[] mMVPMatrix = new float[16];
-  private static float[] mTmpMatrix = new float[16];
-  private static float[] mViewMatrix= new float[16];
-
-  private static int mObjDH;      // This is a handle to half a Object dimensions
-  private static int mMVPMatrixH; // the transformation matrix
-  private static int mMVMatrixH;  // the modelview matrix.
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  EffectQueueMatrix(long id)
-    { 
-    super(id,NUM_UNIFORMS,NUM_CACHE,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);  
-
-    matrix[ 0] = mMVPMatrix[ 0];
-    matrix[ 1] = mMVPMatrix[ 1];
-    matrix[ 2] = mMVPMatrix[ 2];
-    matrix[ 3] = mMVPMatrix[ 3];
-    matrix[ 4] = mMVPMatrix[ 4];
-    matrix[ 5] = mMVPMatrix[ 5];
-    matrix[ 6] = mMVPMatrix[ 6];
-    matrix[ 7] = mMVPMatrix[ 7];
-    matrix[ 8] = mMVPMatrix[ 8];
-    matrix[ 9] = mMVPMatrix[ 9];
-    matrix[10] = mMVPMatrix[10];
-    matrix[11] = mMVPMatrix[11];
-    matrix[12] = mMVPMatrix[12];
-    matrix[13] = mMVPMatrix[13];
-    matrix[14] = mMVPMatrix[14];
-    matrix[15] = mMVPMatrix[15];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void magnify(DistortedOutputSurface projection, float halfX, float halfY, float halfZ, float marginInPixels)
-    {
-    float scale, nx, ny;
-    float[] result= new float[4];
-    float[] point = new float[4];
-    float[] matrix= new float[16];
-    float minx = Integer.MAX_VALUE;
-    float maxx = Integer.MIN_VALUE;
-    float miny = Integer.MAX_VALUE;
-    float maxy = Integer.MIN_VALUE;
-
-    point[3] = 1.0f;
-
-    Matrix.multiplyMM(matrix, 0, projection.mProjectionMatrix, 0, mViewMatrix, 0);
-
-    point[0] = +halfX; point[1] = +halfY; point[2] = +halfZ;
-    Matrix.multiplyMV(result,0,matrix,0,point,0);
-    nx = result[0]/result[3];
-    ny = result[1]/result[3];
-    if( nx<minx ) minx = nx;
-    if( nx>maxx ) maxx = nx;
-    if( ny<miny ) miny = ny;
-    if( ny>maxy ) maxy = ny;
-
-    point[0] = +halfX; point[1] = +halfY; point[2] = -halfZ;
-    Matrix.multiplyMV(result,0,matrix,0,point,0);
-    nx = result[0]/result[3];
-    ny = result[1]/result[3];
-    if( nx<minx ) minx = nx;
-    if( nx>maxx ) maxx = nx;
-    if( ny<miny ) miny = ny;
-    if( ny>maxy ) maxy = ny;
-
-    point[0] = +halfX; point[1] = -halfY; point[2] = +halfZ;
-    Matrix.multiplyMV(result,0,matrix,0,point,0);
-    nx = result[0]/result[3];
-    ny = result[1]/result[3];
-    if( nx<minx ) minx = nx;
-    if( nx>maxx ) maxx = nx;
-    if( ny<miny ) miny = ny;
-    if( ny>maxy ) maxy = ny;
-
-    point[0] = +halfX; point[1] = -halfY; point[2] = -halfZ;
-    Matrix.multiplyMV(result,0,matrix,0,point,0);
-    nx = result[0]/result[3];
-    ny = result[1]/result[3];
-    if( nx<minx ) minx = nx;
-    if( nx>maxx ) maxx = nx;
-    if( ny<miny ) miny = ny;
-    if( ny>maxy ) maxy = ny;
-
-    point[0] = -halfX; point[1] = +halfY; point[2] = +halfZ;
-    Matrix.multiplyMV(result,0,matrix,0,point,0);
-    nx = result[0]/result[3];
-    ny = result[1]/result[3];
-    if( nx<minx ) minx = nx;
-    if( nx>maxx ) maxx = nx;
-    if( ny<miny ) miny = ny;
-    if( ny>maxy ) maxy = ny;
-
-    point[0] = -halfX; point[1] = +halfY; point[2] = -halfZ;
-    Matrix.multiplyMV(result,0,matrix,0,point,0);
-    nx = result[0]/result[3];
-    ny = result[1]/result[3];
-    if( nx<minx ) minx = nx;
-    if( nx>maxx ) maxx = nx;
-    if( ny<miny ) miny = ny;
-    if( ny>maxy ) maxy = ny;
-
-    point[0] = -halfX; point[1] = -halfY; point[2] = +halfZ;
-    Matrix.multiplyMV(result,0,matrix,0,point,0);
-    nx = result[0]/result[3];
-    ny = result[1]/result[3];
-    if( nx<minx ) minx = nx;
-    if( nx>maxx ) maxx = nx;
-    if( ny<miny ) miny = ny;
-    if( ny>maxy ) maxy = ny;
-
-    point[0] = -halfX; point[1] = -halfY; point[2] = -halfZ;
-    Matrix.multiplyMV(result,0,matrix,0,point,0);
-    nx = result[0]/result[3];
-    ny = result[1]/result[3];
-    if( nx<minx ) minx = nx;
-    if( nx>maxx ) maxx = nx;
-    if( ny<miny ) miny = ny;
-    if( ny>maxy ) maxy = ny;
-
-    float xlen = projection.mWidth *(maxx-minx)/2;
-    float ylen = projection.mHeight*(maxy-miny)/2;
-
-    scale = 1.0f + marginInPixels/( xlen>ylen ? ylen:xlen );
-
-    //android.util.Log.d("scale", ""+marginInPixels+" scale= "+scale+" xlen="+xlen+" ylen="+ylen);
-
-    Matrix.scaleM(mViewMatrix, 0, scale, scale, scale);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// here construct the ModelView and the ModelViewProjection Matrices
-
-  private void constructMatrices(DistortedOutputSurface projection, float halfX, float halfY, float halfZ, float marginInPixels)
-    {
-    Matrix.setIdentityM(mViewMatrix, 0);
-    Matrix.translateM(mViewMatrix, 0, -projection.mWidth/2, projection.mHeight/2, -projection.mDistance);
-
-    float x,y,z, sx,sy,sz;
-    float mipmap = projection.mMipmap;
-
-    if( mipmap!=1 ) Matrix.scaleM(mViewMatrix, 0, mipmap, mipmap, mipmap);
-
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if (mName[i] == EffectNames.ROTATE.ordinal() )
-        {
-        x = mUniforms[NUM_UNIFORMS*i+4];
-        y = mUniforms[NUM_UNIFORMS*i+5];
-        z = mUniforms[NUM_UNIFORMS*i+6];
-
-        Matrix.translateM(mViewMatrix, 0, x,-y, z);
-        Matrix.rotateM( mViewMatrix, 0, mUniforms[NUM_UNIFORMS*i], mUniforms[NUM_UNIFORMS*i+1], mUniforms[NUM_UNIFORMS*i+2], mUniforms[NUM_UNIFORMS*i+3]);
-        Matrix.translateM(mViewMatrix, 0,-x, y,-z);
-        }
-      else if(mName[i] == EffectNames.QUATERNION.ordinal() )
-        {
-        x = mUniforms[NUM_UNIFORMS*i+4];
-        y = mUniforms[NUM_UNIFORMS*i+5];
-        z = mUniforms[NUM_UNIFORMS*i+6];
-
-        Matrix.translateM(mViewMatrix, 0, x,-y, z);
-        multiplyByQuat(mViewMatrix, mUniforms[NUM_UNIFORMS*i], mUniforms[NUM_UNIFORMS*i+1], mUniforms[NUM_UNIFORMS*i+2], mUniforms[NUM_UNIFORMS*i+3]);
-        Matrix.translateM(mViewMatrix, 0,-x, y,-z);
-        }
-      else if(mName[i] == EffectNames.MOVE.ordinal() )
-        {
-        sx = mUniforms[NUM_UNIFORMS*i  ];
-        sy = mUniforms[NUM_UNIFORMS*i+1];
-        sz = mUniforms[NUM_UNIFORMS*i+2];
-
-        Matrix.translateM(mViewMatrix, 0, sx,-sy, sz);
-        }
-      else if(mName[i] == EffectNames.SCALE.ordinal() )
-        {
-        sx = mUniforms[NUM_UNIFORMS*i  ];
-        sy = mUniforms[NUM_UNIFORMS*i+1];
-        sz = mUniforms[NUM_UNIFORMS*i+2];
-
-        Matrix.scaleM(mViewMatrix, 0, sx, sy, sz);
-        }
-      else if(mName[i] == EffectNames.SHEAR.ordinal() )
-        {
-        sx = mUniforms[NUM_UNIFORMS*i  ];
-        sy = mUniforms[NUM_UNIFORMS*i+1];
-        sz = mUniforms[NUM_UNIFORMS*i+2];
-
-        x  = mUniforms[NUM_UNIFORMS*i+4];
-        y  = mUniforms[NUM_UNIFORMS*i+5];
-        z  = mUniforms[NUM_UNIFORMS*i+6];
-
-        Matrix.translateM(mViewMatrix, 0, x,-y, z);
-
-        mViewMatrix[4] += sx*mViewMatrix[0]; // Multiply viewMatrix by 1 x 0 0 , i.e. X-shear.
-        mViewMatrix[5] += sx*mViewMatrix[1]; //                        0 1 0 0
-        mViewMatrix[6] += sx*mViewMatrix[2]; //                        0 0 1 0
-        mViewMatrix[7] += sx*mViewMatrix[3]; //                        0 0 0 1
-
-        mViewMatrix[0] += sy*mViewMatrix[4]; // Multiply viewMatrix by 1 0 0 0 , i.e. Y-shear.
-        mViewMatrix[1] += sy*mViewMatrix[5]; //                        y 1 0 0
-        mViewMatrix[2] += sy*mViewMatrix[6]; //                        0 0 1 0
-        mViewMatrix[3] += sy*mViewMatrix[7]; //                        0 0 0 1
-
-        mViewMatrix[4] += sz*mViewMatrix[8]; // Multiply viewMatrix by 1 0 0 0 , i.e. Z-shear.
-        mViewMatrix[5] += sz*mViewMatrix[9]; //                        0 1 0 0
-        mViewMatrix[6] += sz*mViewMatrix[10];//                        0 z 1 0
-        mViewMatrix[7] += sz*mViewMatrix[11];//                        0 0 0 1
-
-        Matrix.translateM(mViewMatrix, 0,-x, y, -z);
-        }
-      }
-
-    Matrix.translateM(mViewMatrix, 0, halfX,-halfY,-halfZ);
-
-    if( marginInPixels!=0 ) magnify(projection,halfX,halfY,halfZ, marginInPixels);
-
-    Matrix.multiplyMM(mMVPMatrix, 0, projection.mProjectionMatrix, 0, mViewMatrix, 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void getUniforms(int mProgramH)
-    {
-    mObjDH     = GLES30.glGetUniformLocation(mProgramH, "u_objD");
-    mMVPMatrixH= GLES30.glGetUniformLocation(mProgramH, "u_MVPMatrix");
-    mMVMatrixH = GLES30.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++)
-      {
-      mCurrentDuration[i] += step;
-
-      if( mInter[0][i]!=null && mInter[0][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.MATRIX.type,
-                                          mName[i],
-                                          mObjectID);
-
-        if( EffectNames.isUnity(mName[i], mUniforms, NUM_UNIFORMS*i) )
-          {
-          remove(i);
-          i--;
-          continue;
-          }
-        else mInter[0][i] = null;
-        }
-
-      if( mInter[1][i]!=null )
-        {
-        mInter[1][i].interpolateMain(mUniforms, NUM_UNIFORMS*i+4, 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];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  float[] getMVP()
-    {
-    return mMVPMatrix;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized void send(DistortedOutputSurface projection, float halfX, float halfY, float halfZ, float marginInPixels)
-    {
-    constructMatrices(projection,halfX,halfY,halfZ, marginInPixels);
-
-    GLES30.glUniform3f( mObjDH , halfX, halfY, halfZ);
-    GLES30.glUniformMatrix4fv(mMVMatrixH , 1, false, mViewMatrix, 0);
-    GLES30.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix , 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// move, scale
-
-  synchronized long add(EffectNames eln, Data3D vector)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      mInter[1][mNumEffects] = null;
-
-      if( vector instanceof Dynamic3D)
-        {
-        mInter[0][mNumEffects] = (Dynamic3D)vector;
-        }
-      else if( vector instanceof Static3D )
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects  ] = ((Static3D)vector).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = ((Static3D)vector).getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+2] = ((Static3D)vector).getZ();
-        }
-      else return -1;
-
-      return addBase(eln);
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// rotate - static axis
-
-  synchronized long add(EffectNames eln, Data1D angle, Static3D axis, Data3D center)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( angle instanceof Dynamic1D)
-        {
-        mInter[0][mNumEffects] = (Dynamic1D)angle;
-        }
-      else if( angle instanceof Static1D)
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)angle).getX();
-        }
-      else return -1;
-
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = axis.getX();
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = axis.getY();
-      mUniforms[NUM_UNIFORMS*mNumEffects+3] = axis.getZ();
-
-      if( center instanceof Dynamic3D)
-        {
-        mInter[1][mNumEffects] = (Dynamic3D)center;
-        }
-      else if( center instanceof Static3D )
-        {
-        mInter[1][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects+4] = ((Static3D)center).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+5] = ((Static3D)center).getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+6] = ((Static3D)center).getZ();
-        }
-      else return -1;
-
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// quaternion or rotate - dynamic axis
-
-  synchronized long add(EffectNames eln, Data4D data, Data3D center)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( data instanceof Dynamic4D  )
-        {
-        mInter[0][mNumEffects] = (Dynamic4D)data;
-        }
-      else if( data instanceof DynamicQuat)
-        {
-        mInter[0][mNumEffects] = (DynamicQuat)data;
-        }
-      else if( data instanceof Static4D   )
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects  ] = ((Static4D)data).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = ((Static4D)data).getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+2] = ((Static4D)data).getZ();
-        mUniforms[NUM_UNIFORMS*mNumEffects+3] = ((Static4D)data).getW();
-        }
-      else return -1;
-
-      if( center instanceof Dynamic3D)
-        {
-        mInter[1][mNumEffects] = (Dynamic3D)center;
-        }
-      else if( center instanceof Static3D )
-        {
-        mInter[1][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects+4] = ((Static3D)center).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+5] = ((Static3D)center).getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+6] = ((Static3D)center).getZ();
-        }
-      else return -1;
-
-      return addBase(eln);
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// shear
-
-  synchronized long add(EffectNames eln, Data3D shear, Data3D center)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( shear instanceof Dynamic3D)
-        {
-        mInter[0][mNumEffects] = (Dynamic3D)shear;
-        }
-      else if( shear instanceof Static3D )
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects  ] = ((Static3D)shear).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = ((Static3D)shear).getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+2] = ((Static3D)shear).getZ();
-        }
-      else return -1;
-
-      if( center instanceof Dynamic3D)
-        {
-        mInter[1][mNumEffects] = (Dynamic3D)center;
-        }
-      else if( center instanceof Static3D )
-        {
-        mInter[1][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects+4] = ((Static3D)center).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+5] = ((Static3D)center).getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+6] = ((Static3D)center).getZ();
-        }
-      else return -1;
-
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-  }
diff --git a/src/main/java/org/distorted/library/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/EffectQueuePostprocess.java
deleted file mode 100644
index dd1898a..0000000
--- a/src/main/java/org/distorted/library/EffectQueuePostprocess.java
+++ /dev/null
@@ -1,451 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-import android.content.res.Resources;
-import android.opengl.GLES30;
-
-import org.distorted.library.message.EffectMessage;
-import org.distorted.library.program.DistortedProgram;
-import org.distorted.library.program.FragmentCompilationException;
-import org.distorted.library.program.FragmentUniformsException;
-import org.distorted.library.program.LinkingException;
-import org.distorted.library.program.VertexCompilationException;
-import org.distorted.library.program.VertexUniformsException;
-import org.distorted.library.type.Data1D;
-import org.distorted.library.type.Data4D;
-import org.distorted.library.type.Dynamic1D;
-import org.distorted.library.type.Dynamic4D;
-import org.distorted.library.type.Static1D;
-import org.distorted.library.type.Static4D;
-
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectQueuePostprocess extends EffectQueue
-  {
-  private static final int MAX_HALO = 50;    // Support effects creating up to MAX_HALO pixels wide 'halo' around the object.
-
-  private static final int POS_DATA_SIZE= 2; // Post Program: size of the position data in elements
-  private static final int TEX_DATA_SIZE= 2; // Post Program: size of the texture coordinate data in elements.
-
-  private static final int NUM_UNIFORMS = 5;
-  private static final int NUM_CACHE    = 0;
-  private static final int INDEX = EffectTypes.POSTPROCESS.ordinal();
-
-  private static final FloatBuffer mQuadPositions, mQuadTexture, mQuadTextureInv;
-
-  static
-    {
-    int dataLength      = 4;
-    int bytes_per_float = 4;
-
-    float[] position  = { -0.5f, -0.5f,  -0.5f, 0.5f,  0.5f,-0.5f,  0.5f, 0.5f };
-    float[] textureNor= {  0.0f,  0.0f,   0.0f, 1.0f,  1.0f, 0.0f,  1.0f, 1.0f };
-    float[] textureInv= {  0.0f,  0.0f,   1.0f, 0.0f,  0.0f, 1.0f,  1.0f, 1.0f };
-
-    mQuadPositions = ByteBuffer.allocateDirect(POS_DATA_SIZE*dataLength*bytes_per_float).order(ByteOrder.nativeOrder()).asFloatBuffer();
-    mQuadPositions.put(position).position(0);
-    mQuadTexture= ByteBuffer.allocateDirect(TEX_DATA_SIZE*dataLength*bytes_per_float).order(ByteOrder.nativeOrder()).asFloatBuffer();
-    mQuadTexture.put(textureNor).position(0);
-    mQuadTextureInv= ByteBuffer.allocateDirect(TEX_DATA_SIZE*dataLength*bytes_per_float).order(ByteOrder.nativeOrder()).asFloatBuffer();
-    mQuadTextureInv.put(textureInv).position(0);
-    }
-
-  int mQualityLevel;
-  float mQualityScale;
-  private int mHalo;
-
-  /////////////////////////////////////////////////////////////////////////////////
-  // BLUR effect
-  private static final float GAUSSIAN[] =   // G(0.00), G(0.03), G(0.06), ..., G(3.00), 0
-    {                                       // where G(x)= (1/(sqrt(2*PI))) * e^(-(x^2)/2). The last 0 terminates.
-    0.398948f, 0.398769f, 0.398231f, 0.397336f, 0.396086f, 0.394485f, 0.392537f, 0.390247f, 0.387622f, 0.384668f,
-    0.381393f, 0.377806f, 0.373916f, 0.369733f, 0.365268f, 0.360532f, 0.355538f, 0.350297f, 0.344823f, 0.339129f,
-    0.333229f, 0.327138f, 0.320868f, 0.314436f, 0.307856f, 0.301142f, 0.294309f, 0.287373f, 0.280348f, 0.273248f,
-    0.266089f, 0.258884f, 0.251648f, 0.244394f, 0.237135f, 0.229886f, 0.222657f, 0.215461f, 0.208311f, 0.201217f,
-    0.194189f, 0.187238f, 0.180374f, 0.173605f, 0.166940f, 0.160386f, 0.153951f, 0.147641f, 0.141462f, 0.135420f,
-    0.129520f, 0.123765f, 0.118159f, 0.112706f, 0.107408f, 0.102266f, 0.097284f, 0.092461f, 0.087797f, 0.083294f,
-    0.078951f, 0.074767f, 0.070741f, 0.066872f, 0.063158f, 0.059596f, 0.056184f, 0.052920f, 0.049801f, 0.046823f,
-    0.043984f, 0.041280f, 0.038707f, 0.036262f, 0.033941f, 0.031740f, 0.029655f, 0.027682f, 0.025817f, 0.024056f,
-    0.022395f, 0.020830f, 0.019357f, 0.017971f, 0.016670f, 0.015450f, 0.014305f, 0.013234f, 0.012232f, 0.011295f,
-    0.010421f, 0.009606f, 0.008847f, 0.008140f, 0.007483f, 0.006873f, 0.006307f, 0.005782f, 0.005296f, 0.004847f,
-    0.004432f, 0.000000f
-    };
-  private static final int NUM_GAUSSIAN = GAUSSIAN.length-2;
-
-  // The (fixed-function-sampled) Gaussian Blur kernels are of the size k0=1, k1=2, k2=2, k3=3, k4=3, k5=4, k6=4,...
-  // i.e. k(i)=floor((i+3)/2).  (the 'i' in k(i) means 'blur taking into account the present pixel and 'i' pixels
-  // in all 4 directions)
-  // We need room for MAX_BLUR of them, and sum(i=0...N, floor((i+3)/2)) = N + floor(N*N/4)
-  private static float[] weightsCache = new float[MAX_HALO + MAX_HALO*MAX_HALO/4];
-  private static float[] offsetsCache = new float[MAX_HALO + MAX_HALO*MAX_HALO/4];
-
-  private static DistortedProgram mBlur1Program, mBlur2Program;
-  private static int mRadius1H,mOffsets1H,mWeights1H,mDepth1H, mColorTexture1H;
-  private static int mRadius2H,mOffsets2H,mWeights2H,mDepth2H, mColorTexture2H;
-  private static float[] mWeights = new float[MAX_HALO];
-  private static float[] mOffsets = new float[MAX_HALO];
-  /////////////////////////////////////////////////////////////////////////////////
-  // GLOW effect
-
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  EffectQueuePostprocess(long id)
-    { 
-    super(id,NUM_UNIFORMS,NUM_CACHE,INDEX );
-
-    mQualityLevel = 0;
-    mQualityScale = 1.0f;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void createProgram(Resources resources)
-  throws FragmentCompilationException,VertexCompilationException,VertexUniformsException,FragmentUniformsException,LinkingException
-    {
-    final InputStream blur1VertexStream   = resources.openRawResource(R.raw.blur_vertex_shader);
-    final InputStream blur1FragmentStream = resources.openRawResource(R.raw.blur1_fragment_shader);
-
-    try
-      {
-      mBlur1Program = new DistortedProgram(blur1VertexStream,blur1FragmentStream,
-                                          Distorted.GLSL_VERSION,
-                                          Distorted.GLSL_VERSION + "#define MAX_BLUR "+MAX_HALO, Distorted.GLSL);
-      }
-    catch(Exception e)
-      {
-      android.util.Log.e("EFFECTS", "exception trying to compile BLUR1 program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int blur1ProgramH = mBlur1Program.getProgramHandle();
-    mRadius1H       = GLES30.glGetUniformLocation( blur1ProgramH, "u_Radius");
-    mOffsets1H      = GLES30.glGetUniformLocation( blur1ProgramH, "u_Offsets");
-    mWeights1H      = GLES30.glGetUniformLocation( blur1ProgramH, "u_Weights");
-    mDepth1H        = GLES30.glGetUniformLocation( blur1ProgramH, "u_Depth");
-    mColorTexture1H = GLES30.glGetUniformLocation( blur1ProgramH, "u_ColorTexture");
-
-    final InputStream blur2VertexStream   = resources.openRawResource(R.raw.blur_vertex_shader);
-    final InputStream blur2FragmentStream = resources.openRawResource(R.raw.blur2_fragment_shader);
-
-    try
-      {
-      mBlur2Program = new DistortedProgram(blur2VertexStream,blur2FragmentStream,
-                                          Distorted.GLSL_VERSION,
-                                          Distorted.GLSL_VERSION + "#define MAX_BLUR "+MAX_HALO, Distorted.GLSL);
-      }
-    catch(Exception e)
-      {
-      android.util.Log.e("EFFECTS", "exception trying to compile BLUR2 program: "+e.getMessage());
-      // run anyway as compiling Blur2 WILL fail on OpenGL 2.0 contexts
-      mBlur2Program = mBlur1Program;
-      }
-
-    int blur2ProgramH = mBlur2Program.getProgramHandle();
-    mRadius2H       = GLES30.glGetUniformLocation( blur2ProgramH, "u_Radius");
-    mOffsets2H      = GLES30.glGetUniformLocation( blur2ProgramH, "u_Offsets");
-    mWeights2H      = GLES30.glGetUniformLocation( blur2ProgramH, "u_Weights");
-    mDepth2H        = GLES30.glGetUniformLocation( blur2ProgramH, "u_Depth");
-    mColorTexture2H = GLES30.glGetUniformLocation( blur2ProgramH, "u_ColorTexture");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean compute(long currTime)
-    {
-    if( currTime==mTime ) return false;
-    if( mTime==0 ) mTime = currTime;
-    long step = (currTime-mTime);
-
-    mHalo = 0;
-    int halo;
-
-    for(int i=0; i<mNumEffects; i++)
-      {
-      mCurrentDuration[i] += step;
-
-      if( mInter[0][i]!=null && mInter[0][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.POSTPROCESS.type,
-                                          mName[i],
-                                          mObjectID);
-
-        if( EffectNames.isUnity(mName[i], mUniforms, NUM_UNIFORMS*i) )
-          {
-          remove(i);
-          i--;
-          continue;
-          }
-        else mInter[0][i] = null;
-        }
-
-      if( mInter[1][i]!=null ) mInter[1][i].interpolateMain( mUniforms, NUM_UNIFORMS*i+1, mCurrentDuration[i], step);
-
-      halo = (int)mUniforms[NUM_UNIFORMS*i];
-      if( halo>mHalo ) mHalo = halo;
-      }
-
-    mTime = currTime;
-
-    return true;
-    }  
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  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];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// This implements the 'Better separable implementation using GPU fixed function sampling' from
-// https://software.intel.com/en-us/blogs/2014/07/15/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms
-
-  private void computeGaussianKernel(int radius)
-    {
-    int offset = radius + radius*radius/4;
-
-    if( weightsCache[offset]==0.0f )
-      {
-      float z, x= 0.0f, P= (float)NUM_GAUSSIAN / (radius>3 ? radius:3);
-      mWeights[0] = GAUSSIAN[0];
-      float sum   = GAUSSIAN[0];
-      int j;
-
-      for(int i=1; i<=radius; i++)
-        {
-        x += P;
-        j = (int)x;
-        z = x-j;
-
-        mWeights[i] = (1-z)*GAUSSIAN[j] + z*GAUSSIAN[j+1];
-        sum += 2*mWeights[i];
-        }
-
-      for(int i=0; i<=radius; i++) mWeights[i] /= sum;
-
-      int numloops = radius/2;
-      weightsCache[offset] = mWeights[0];
-      offsetsCache[offset] = 0.0f;
-
-      for(int i=0; i<numloops; i++)
-        {
-        offsetsCache[offset+i+1] = mWeights[2*i+1]*(2*i+1) + mWeights[2*i+2]*(2*i+2);
-        weightsCache[offset+i+1] = mWeights[2*i+1] + mWeights[2*i+2];
-        offsetsCache[offset+i+1]/= weightsCache[offset+i+1];
-        }
-
-      if( radius%2 == 1 )
-        {
-        int index = offset + radius/2 +1;
-        offsetsCache[index]=radius;
-        weightsCache[index]=mWeights[radius];
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getHalo()
-    {
-    return mNumEffects>0 ? mHalo : 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int postprocess(long time, DistortedOutputSurface surface)
-    {
-    int numRenders = 0;
-
-    if( mNumEffects>0 )
-      {
-      compute(time);
-
-      for(int i=0; i<mNumEffects; i++)
-        {
-        if (mName[i] == EffectNames.BLUR.ordinal() )
-          {
-          blur(NUM_UNIFORMS*i,surface);
-          numRenders += 2;
-          }
-        else if (mName[i] == EffectNames.GLOW.ordinal() )
-          {
-          glow(NUM_UNIFORMS*i,surface);
-          numRenders += 2;
-          }
-        }
-      }
-
-    return numRenders;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void blur(int index, DistortedOutputSurface surface)
-    {
-    DistortedFramebuffer buffer = surface.mBuffer[mQualityLevel];
-    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, buffer.mFBOH[0]);
-
-    float w1 = buffer.mWidth;
-    float h1 = buffer.mHeight;
-
-    int radius = (int)(mUniforms[index]*mQualityScale);
-    if( radius>=MAX_HALO ) radius = MAX_HALO-1;
-    computeGaussianKernel(radius);
-
-    int offset = radius + radius*radius/4;
-    radius = (radius+1)/2;
-
-    // horizontal blur
-    GLES30.glViewport(0, 0, (int)w1, (int)h1);
-    mBlur1Program.useProgram();
-    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, buffer.mColorH[1], 0);
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mColorH[0]);
-
-    GLES30.glUniform1fv( mWeights1H, radius+1, weightsCache,offset);
-    GLES30.glUniform1i( mRadius1H, radius);
-    GLES30.glUniform1f( mDepth1H , 1.0f-surface.mNear);
-    GLES30.glUniform1i( mColorTexture1H , 0 );
-    for(int i=0; i<=radius; i++) mOffsets[i] = offsetsCache[offset+i]/h1;
-    GLES30.glUniform1fv( mOffsets1H ,radius+1, mOffsets,0);
-    GLES30.glVertexAttribPointer(mBlur1Program.mAttribute[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mQuadPositions);
-    GLES30.glVertexAttribPointer(mBlur1Program.mAttribute[1], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mQuadTexture);
-
-    DistortedRenderState.useStencilMark();
-    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
-    DistortedRenderState.unuseStencilMark();
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-
-    // vertical blur
-    mBlur2Program.useProgram();
-    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, buffer.mColorH[0], 0);
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mColorH[1]);
-
-    GLES30.glColorMask(true,true,true,true);
-    GLES30.glClearColor(0.0f,0.0f,0.0f,0.0f);
-    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
-
-    GLES30.glUniform1fv( mWeights2H, radius+1, weightsCache,offset);
-    GLES30.glUniform1i( mRadius2H, radius);
-    GLES30.glUniform1f( mDepth2H , 1.0f-surface.mNear);
-    GLES30.glUniform1i( mColorTexture2H , 0 );
-    for(int i=0; i<=radius; i++) mOffsets[i] = offsetsCache[offset+i]/w1;
-    GLES30.glUniform1fv( mOffsets2H ,radius+1, mOffsets,0);
-    GLES30.glVertexAttribPointer(mBlur2Program.mAttribute[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mQuadPositions);
-    GLES30.glVertexAttribPointer(mBlur2Program.mAttribute[1], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mQuadTexture);
-
-    DistortedRenderState.useStencilMark();
-    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
-    DistortedRenderState.unuseStencilMark();
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void glow(int index, DistortedOutputSurface surface)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// blur
-
-  synchronized long add(EffectNames eln, Data1D degree)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( degree instanceof Dynamic1D)
-        {
-        mInter[0][mNumEffects] = (Dynamic1D)degree;
-        }
-      else if( degree instanceof Static1D)
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)degree).getX();
-        }
-      else return -1;
-
-      mInter[1][mNumEffects] = null;
-      mInter[2][mNumEffects] = null;
-
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// glow
-
-  synchronized long add(EffectNames eln, Data1D degree, Data4D color)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( degree instanceof Dynamic1D)
-        {
-        mInter[0][mNumEffects] = (Dynamic1D)degree;
-        }
-      else if( degree instanceof Static1D)
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)degree).getX();
-        }
-      else return -1;
-
-      if( color instanceof Dynamic4D)
-        {
-        mInter[1][mNumEffects] = (Dynamic4D)color;
-        }
-      else if( color instanceof Static4D)
-        {
-        mInter[1][mNumEffects] = null;
-        Static4D tmp = (Static4D)color;
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = tmp.getW();  //
-        mUniforms[NUM_UNIFORMS*mNumEffects+2] = tmp.getX();  // Invert: RGBA sent
-        mUniforms[NUM_UNIFORMS*mNumEffects+3] = tmp.getY();  // in, ARGB inside
-        mUniforms[NUM_UNIFORMS*mNumEffects+4] = tmp.getZ();  //
-        }
-      else return -1;
-
-      mInter[2][mNumEffects] = null;
-
-      return addBase(eln);
-      }
-
-    return -1;
-    }
-  }
diff --git a/src/main/java/org/distorted/library/EffectQueueVertex.java b/src/main/java/org/distorted/library/EffectQueueVertex.java
deleted file mode 100644
index 3bbd17e..0000000
--- a/src/main/java/org/distorted/library/EffectQueueVertex.java
+++ /dev/null
@@ -1,387 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-import android.opengl.GLES30;
-
-import org.distorted.library.message.EffectMessage;
-import org.distorted.library.type.Data1D;
-import org.distorted.library.type.Data2D;
-import org.distorted.library.type.Data3D;
-import org.distorted.library.type.Data4D;
-import org.distorted.library.type.Data5D;
-import org.distorted.library.type.Dynamic1D;
-import org.distorted.library.type.Dynamic2D;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Dynamic4D;
-import org.distorted.library.type.Dynamic5D;
-import org.distorted.library.type.Static1D;
-import org.distorted.library.type.Static2D;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-import org.distorted.library.type.Static5D;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectQueueVertex extends EffectQueue
-  { 
-  private static final int NUM_UNIFORMS = 12;
-  private static final int NUM_CACHE    =  3;
-  private static final int INDEX = EffectTypes.VERTEX.ordinal();
-  private static int mNumEffectsH;
-  private static int mTypeH;
-  private static int mUniformsH;
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  EffectQueueVertex(long id)
-    { 
-    super(id,NUM_UNIFORMS,NUM_CACHE,INDEX);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void getUniforms(int mProgramH)
-    {
-    mNumEffectsH= GLES30.glGetUniformLocation( mProgramH, "vNumEffects");
-    mTypeH      = GLES30.glGetUniformLocation( mProgramH, "vType");
-    mUniformsH  = GLES30.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++)
-      {
-      mCurrentDuration[i] += step;
-
-      if( mInter[0][i]!=null )
-        {
-        if( mInter[0][i].interpolateMain(mUniforms ,NUM_UNIFORMS*i, mCurrentDuration[i], step) )
-          {
-          postprocess(i);
-
-          for(int j=0; j<mNumListeners; j++)
-            EffectMessageSender.newMessage( mListeners.elementAt(j),
-                                            EffectMessage.EFFECT_FINISHED,
-                                           (mID[i]<<EffectTypes.LENGTH)+EffectTypes.VERTEX.type,
-                                            mName[i],
-                                            mObjectID);
-
-          if( EffectNames.isUnity(mName[i], mUniforms, NUM_UNIFORMS*i) )
-            {
-            remove(i);
-            i--;
-            continue;
-            }
-          else mInter[0][i] = null;
-          }
-        else
-          {
-          postprocess(i);
-          }
-        }
-
-      if( mInter[1][i]!=null ) mInter[1][i].interpolateMain(mUniforms, NUM_UNIFORMS*i+8, mCurrentDuration[i], step);
-      if( mInter[2][i]!=null ) mInter[2][i].interpolateMain(mCache   , NUM_CACHE*i     , 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];
-
-    mCache[NUM_CACHE*index  ] = mCache[NUM_CACHE*(index+1)  ];
-    mCache[NUM_CACHE*index+1] = mCache[NUM_CACHE*(index+1)+1];
-    mCache[NUM_CACHE*index+2] = mCache[NUM_CACHE*(index+1)+2];
-
-    mUniforms[NUM_UNIFORMS*index+ 8] = mUniforms[NUM_UNIFORMS*(index+1)+ 8];
-    mUniforms[NUM_UNIFORMS*index+ 9] = mUniforms[NUM_UNIFORMS*(index+1)+ 9];
-    mUniforms[NUM_UNIFORMS*index+10] = mUniforms[NUM_UNIFORMS*(index+1)+10];
-    mUniforms[NUM_UNIFORMS*index+11] = mUniforms[NUM_UNIFORMS*(index+1)+11];
-    }
-   
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  synchronized void send(float halfX, float halfY, float halfZ)
-    {
-    GLES30.glUniform1i( mNumEffectsH, mNumEffects);
-      
-    if( mNumEffects>0 )
-      {
-      for(int i=0; i<mNumEffects; i++)
-        {
-        mUniforms[NUM_UNIFORMS*i+5] = mCache[NUM_CACHE*i  ]-halfX;
-        mUniforms[NUM_UNIFORMS*i+6] =-mCache[NUM_CACHE*i+1]+halfY;
-        mUniforms[NUM_UNIFORMS*i+7] = mCache[NUM_CACHE*i+2]-halfZ;
-        }
-
-      GLES30.glUniform1iv( mTypeH    ,                 mNumEffects, mName    ,0);
-      GLES30.glUniform4fv( mUniformsH,(NUM_UNIFORMS/4)*mNumEffects, mUniforms,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, switch the angles from degrees to radians
-// 3) likewise in case of WAVE and PINCH
-// 4) In case of DISTORT, invert the Y-axis
-  
-  private void postprocess(int effect)
-    {
-    if( mName[effect]==EffectNames.SWIRL.ordinal() )
-      {
-      mUniforms[NUM_UNIFORMS*effect  ] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect  ]/180);
-      }
-    if( mName[effect]==EffectNames.PINCH.ordinal() )
-      {
-      mUniforms[NUM_UNIFORMS*effect+1] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect+1]/180);
-      }
-    if( mName[effect]==EffectNames.WAVE.ordinal() )
-      {
-      mUniforms[NUM_UNIFORMS*effect+2] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect+2]/180);
-      mUniforms[NUM_UNIFORMS*effect+3] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect+3]/180);
-      mUniforms[NUM_UNIFORMS*effect+4] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect+4]/180);
-      }
-    if( mName[effect]==EffectNames.DISTORT.ordinal() )
-      {
-      mUniforms[NUM_UNIFORMS*effect+1] =-mUniforms[NUM_UNIFORMS*effect+1];
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// wave
-
-  synchronized long add(EffectNames eln, Data5D data, Data3D center, Data4D region)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( data instanceof Dynamic5D)
-        {
-        mInter[0][mNumEffects] = (Dynamic5D)data;
-        }
-      else if( data instanceof Static5D)
-        {
-        Static5D tmp = (Static5D)data;
-
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects  ] = tmp.getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = tmp.getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+2] = tmp.getZ();
-        mUniforms[NUM_UNIFORMS*mNumEffects+3] = tmp.getW();
-        mUniforms[NUM_UNIFORMS*mNumEffects+4] = tmp.getV();
-        }
-
-      return addPriv(eln,center,region);
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// deform,distort
-
-  synchronized long add(EffectNames eln, Data3D data, Data3D center, Data4D region)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( data instanceof Dynamic3D)
-        {
-        mInter[0][mNumEffects] = (Dynamic3D)data;
-        }
-      else if( data instanceof Static3D)
-        {
-        Static3D tmp = (Static3D)data;
-
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects  ] = tmp.getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = tmp.getY();
-        mUniforms[NUM_UNIFORMS*mNumEffects+2] = tmp.getZ();
-        }
-
-      return addPriv(eln,center,region);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// sink, swirl
-
-  synchronized long add(EffectNames eln, Data1D data, Data3D center, Data4D region)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( data instanceof Dynamic1D)
-        {
-        mInter[0][mNumEffects] = (Dynamic1D)data;
-        }
-      else if( data instanceof Static1D)
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)data).getX();
-        }
-
-      return addPriv(eln,center,region);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// sink, swirl
-
-  synchronized long add(EffectNames eln, Data1D data, Data3D center)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( data instanceof Dynamic1D)
-        {
-        mInter[0][mNumEffects] = (Dynamic1D)data;
-        }
-      else if( data instanceof Static1D)
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)data).getX();
-        }
-
-      return addPriv(eln,center,null);
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// pinch
-
-  synchronized long add(EffectNames eln, Data2D data, Data3D center, Data4D region)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( data instanceof Dynamic2D)
-        {
-        mInter[0][mNumEffects] = (Dynamic2D)data;
-        }
-      else if( data instanceof Static2D)
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects  ] = ((Static2D)data).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = ((Static2D)data).getY();
-        }
-
-      return addPriv(eln,center,region);
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// pinch
-
-  synchronized long add(EffectNames eln, Data2D data, Data3D center)
-    {
-    if( mMax[INDEX]>mNumEffects )
-      {
-      if( data instanceof Dynamic2D)
-        {
-        mInter[0][mNumEffects] = (Dynamic2D)data;
-        }
-      else if( data instanceof Static2D)
-        {
-        mInter[0][mNumEffects] = null;
-        mUniforms[NUM_UNIFORMS*mNumEffects  ] = ((Static2D)data).getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+1] = ((Static2D)data).getY();
-        }
-
-      return addPriv(eln,center,null);
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  private long addPriv(EffectNames eln, Data3D center, Data4D region)
-    {
-    if( region!=null )
-      {
-      if( region instanceof Dynamic4D)
-        {
-        mInter[1][mNumEffects] = (Dynamic4D)region;
-        }
-      else if ( region instanceof Static4D)
-        {
-        Static4D tmp = (Static4D)region;
-
-        float z = tmp.getZ();
-
-        mUniforms[NUM_UNIFORMS*mNumEffects+ 8] = tmp.getX();
-        mUniforms[NUM_UNIFORMS*mNumEffects+ 9] =-tmp.getY();   // invert y already
-        mUniforms[NUM_UNIFORMS*mNumEffects+10] = z<=0.0f ? Float.MAX_VALUE : z;
-        mUniforms[NUM_UNIFORMS*mNumEffects+11] = tmp.getW();
-        mInter[1][mNumEffects] = null;
-        }
-      else return -1;
-      }
-    else
-      {
-      mUniforms[NUM_UNIFORMS*mNumEffects+ 8] = 0.0f;
-      mUniforms[NUM_UNIFORMS*mNumEffects+ 9] = 0.0f;
-      mUniforms[NUM_UNIFORMS*mNumEffects+10] = Float.MAX_VALUE;
-      mUniforms[NUM_UNIFORMS*mNumEffects+11] = 0.0f;
-      mInter[1][mNumEffects] = null;
-      }
-
-    if( center instanceof Dynamic3D)
-      {
-      mInter[2][mNumEffects] = (Dynamic3D)center;
-      }
-    else if( center instanceof Static3D)
-      {
-      mInter[2][mNumEffects] = null;
-      mCache[NUM_CACHE*mNumEffects  ] = ((Static3D)center).getX();
-      mCache[NUM_CACHE*mNumEffects+1] = ((Static3D)center).getY();
-      mCache[NUM_CACHE*mNumEffects+2] = ((Static3D)center).getZ();
-      }
-
-    long ret= addBase(eln);
-
-    postprocess(mNumEffects-1); //addBase just incremented mNumEffects
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// end of VertexEffect  
-  }
diff --git a/src/main/java/org/distorted/library/EffectTypes.java b/src/main/java/org/distorted/library/EffectTypes.java
deleted file mode 100644
index d584554..0000000
--- a/src/main/java/org/distorted/library/EffectTypes.java
+++ /dev/null
@@ -1,75 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Types of Effects one can add to the DistortedEffects queues.
- * <p>
- * Each effect type goes to an independent queue; the queues get executed one-by-one
- * and are each a class descendant from EffectQueue.
- */
-public enum EffectTypes
-  {
-  /**
-   * Effects that change the ModelView matrix: Rotations, Moves, Shears, Scales.
-   */
-  MATRIX      ( 0x1 ),  // we will need to perform bitwise operations on those - so keep the values 1,2,4,8...
-  /**
-   * Effects that get executed in the Vertex shader: various distortions of the vertices.
-   */
-  VERTEX      ( 0x2 ),
-  /**
-   * Effects executed in the Fragment shader: changes of color, hue, transparency levels, etc.
-   */
-  FRAGMENT    ( 0x4 ),
-  /**
-   * Postprocessing done to the texture the first stage fragment shader created (if this queue is
-   * empty, the first stage skips this intermediate texture)
-   */
-  POSTPROCESS ( 0x8 );
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  final static int LENGTH = values().length;  // The number of effect types.
-  final static int MASK= (1<<LENGTH)-1;       // Needed when we do bitwise operations on Effect Types.
-
-  final int type;
-
-  EffectTypes(int type)
-    {
-    this.type = type;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// called from EffectQueue
-
-  static void reset(int[] maxtable)
-    {
-    maxtable[0] =10;  // By default, there can be a maximum 10 MATRIX effects in a single
-                      // EffectQueueMatrix at any given time. This can be changed with a call
-                      // to EffectQueueMatrix.setMax(int)
-
-    maxtable[1] = 5;  // Max 5 VERTEX Effects
-    maxtable[2] = 5;  // Max 5 FRAGMENT Effects
-    maxtable[3] = 5;  // Max 5 POSTPROCESSING Effects
-    }
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  }
diff --git a/src/main/java/org/distorted/library/effect/EffectMessageSender.java b/src/main/java/org/distorted/library/effect/EffectMessageSender.java
new file mode 100644
index 0000000..111b76c
--- /dev/null
+++ b/src/main/java/org/distorted/library/effect/EffectMessageSender.java
@@ -0,0 +1,143 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effect;
+
+import org.distorted.library.message.EffectListener;
+import org.distorted.library.message.EffectMessage;
+
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+final class EffectMessageSender extends Thread
+  {
+  private class Message
+    {
+    EffectListener mListener;
+    EffectMessage mMessage;
+    long mEffectID;
+    int mEffectName;
+    long mBitmapID;
+
+    Message(EffectListener l, EffectMessage m, long id, int name, long bmpID)
+      {
+      mListener   = l;
+      mMessage    = m;
+      mEffectID   = id;
+      mEffectName = name;
+      mBitmapID   = bmpID;
+      }
+    }
+  
+  private static Vector<Message> mList =null;
+  private static EffectMessageSender mThis=null;
+  private static volatile boolean mNotify  = false;
+  private static volatile boolean mRunning = false;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  private EffectMessageSender() 
+    {
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void startSending()
+    {
+    mRunning = true;
+
+    if( mThis==null )
+      {
+      mList = new Vector<>();
+      mThis = new EffectMessageSender();
+      mThis.start();
+      }
+    else  
+      {  
+      synchronized(mThis)
+        {
+        mThis.notify();
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  static void stopSending()
+    {
+    mRunning = false;
+
+    if( mThis!=null )
+      {
+      synchronized(mThis)
+        {
+        mThis.notify();
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  public void run()
+    {
+    Message tmp;  
+     
+    while(mRunning)
+      {
+      //android.util.Log.i("SENDER", "sender thread running...");
+
+      while( mList.size()>0 )
+        {
+        tmp = mList.remove(0);
+        tmp.mListener.effectMessage(tmp.mMessage, tmp.mEffectID, tmp.mEffectName,tmp.mBitmapID);
+        }
+
+      synchronized(mThis)
+        {
+        if (!mNotify)
+          {
+          try  { mThis.wait(); }
+          catch(InterruptedException ex) { }
+          }
+        mNotify = false;
+        }
+      }
+
+    mThis = null;
+    mList.clear();
+
+    //android.util.Log.i("SENDER", "sender thread finished...");
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+        
+  static void newMessage(EffectListener l, EffectMessage m, long id, int name, long bmpID)
+    {
+    Message msg = mThis.new Message(l,m,id,name,bmpID);
+    mList.add(msg);
+
+    synchronized(mThis)
+      {
+      mNotify = true;
+      mThis.notify();
+      }
+    }
+  }
+///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/effect/EffectQueue.java b/src/main/java/org/distorted/library/effect/EffectQueue.java
new file mode 100644
index 0000000..6bc1d38
--- /dev/null
+++ b/src/main/java/org/distorted/library/effect/EffectQueue.java
@@ -0,0 +1,366 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effect;
+
+import org.distorted.library.Distorted;
+import org.distorted.library.message.EffectListener;
+import org.distorted.library.message.EffectMessage;
+import org.distorted.library.type.Dynamic;
+
+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[] mName;
+  protected int[] mType;
+  protected float[] mUniforms;
+  protected float[] mCache;
+  protected Dynamic[][] mInter;
+  protected long[] mCurrentDuration;
+  protected byte[] mFreeIndexes;
+  protected byte[] mIDIndex;
+  protected long[] mID;
+  protected long mTime=0;
+  protected static int[] mMax = new int[Effect.LENGTH];
+  protected int mMaxIndex;
+  protected Vector<EffectListener> mListeners =null;
+  protected int mNumListeners=0;  // ==mListeners.length(), but we only create mListeners if the first one gets added
+  protected long mObjectID;
+
+  private static boolean mCreated;
+
+  static
+    {
+    onDestroy();
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  EffectQueue(long id, int numUniforms, int numCache, int index)
+    {
+    mNumEffects   = 0;
+    mTotalEffects = 0;
+    mMaxIndex     = index;
+    mObjectID     = id;
+
+    int max = mMax[mMaxIndex];
+
+    if( max>0 )
+      {
+      mName            = new int[max];
+      mType            = new int[max];
+      mUniforms        = new float[numUniforms*max];
+      mInter           = new Dynamic[3][max];
+      mCurrentDuration = new long[max];
+      mID              = new long[max];
+      mIDIndex         = new byte[max];
+      mFreeIndexes     = new byte[max];
+     
+      for(byte i=0; i<max; i++) mFreeIndexes[i] = i;
+
+      if( numCache>0 )
+        {
+        mCache = new float[numCache*max];
+        }
+      }
+   
+    mCreated = true;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @SuppressWarnings("unused")
+  int getNumEffects()
+    {
+    return mNumEffects;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only max Byte.MAX_VALUE concurrent effects per DistortedEffects object.
+// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
+// (although probably this many uniforms will not fit in the shaders anyway!)
+
+  static boolean setMax(int index, int m)
+    {
+    if( (!mCreated && !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(int index)
+    {
+    return mMax[index];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void registerForMessages(EffectListener el)
+    {
+    if( mListeners==null ) mListeners = new Vector<>(2,2);
+
+    if( !mListeners.contains(el) )
+      {
+      mListeners.add(el);
+      mNumListeners++;
+      }
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void deregisterForMessages(EffectListener el)
+    {
+    if( mListeners.remove(el) )
+      {
+      mNumListeners--;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void onDestroy()
+    {
+    Effect.reset(mMax);
+    mCreated = false;  
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized int removeByID(long id)
+    {
+    int i = getEffectIndex(id);
+   
+    if( i>=0 ) 
+      {
+      remove(i);
+      return 1;
+      }
+   
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized int removeByType(Effect effect)
+    {
+    int ret  = 0;
+    int name = effect.getName();
+    int type = effect.getType();
+
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mName[i]==name && mType[i]==type )
+        {
+        remove(i);
+        i--;
+        ret++;
+        }
+      }
+   
+    return ret;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private synchronized int getEffectIndex(long id)
+    {
+    int index = mIDIndex[(int)(id%mMax[mMaxIndex])];
+    return (index<mNumEffects && mID[index]==id ? index : -1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// we do want to notify Listeners if they called 'abortAll' themselves but don't want to notify
+// them if it is the library itself which is releasing resources.
+
+  synchronized int abortAll(boolean notify)
+    {
+    int ret = mNumEffects;
+    long removedID;
+    int removedName, removedType;
+
+    for(int i=0; i<ret; i++ )
+      {
+      mInter[0][i] = null;
+      mInter[1][i] = null;
+      mInter[2][i] = null;
+
+      if( notify )
+        {
+        removedID = mID[i];
+        removedName= mName[i];
+        removedType= mType[i];
+
+        for(int j=0; j<mNumListeners; j++)
+          EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                          EffectMessage.EFFECT_REMOVED,
+                                          (removedID<<Effect.LENGTH)+removedType,
+                                          removedName,
+                                          mObjectID);
+        }
+      }
+
+    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 removedName= mName[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++ ) 
+      {
+      mName[j]            = mName[j+1];
+      mType[j]            = mType[j+1];
+      mInter[0][j]        = mInter[0][j+1];
+      mInter[1][j]        = mInter[1][j+1];
+      mInter[2][j]        = mInter[2][j+1];
+      mCurrentDuration[j] = mCurrentDuration[j+1];
+      mID[j]              = mID[j+1];
+    
+      moveEffect(j);
+      }
+   
+    mInter[0][mNumEffects] = null;
+    mInter[1][mNumEffects] = null;
+    mInter[2][mNumEffects] = null;
+
+    for(int i=0; i<mNumListeners; i++) 
+      EffectMessageSender.newMessage( mListeners.elementAt(i),
+                                      EffectMessage.EFFECT_REMOVED,
+                                      (removedID<<Effect.LENGTH)+removedType,
+                                      removedName,
+                                      mObjectID);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  protected long addBase(Effect effect)
+    {
+    int type = effect.getType();
+
+    mName[mNumEffects] = effect.getName();
+    mType[mNumEffects] = type;
+    mCurrentDuration[mNumEffects] = 0;
+    
+    int index = mFreeIndexes[mNumEffects];
+    long id = mTotalEffects*mMax[mMaxIndex] + index;
+    mID[mNumEffects] = id;
+    mIDIndex[index] = mNumEffects;  
+   
+    mNumEffects++; 
+    mTotalEffects++;
+   
+    return (id<<Effect.LENGTH)+type;
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// used only for debugging
+
+  @SuppressWarnings("unused")
+  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 inter0 = mInter[0][index]==null;
+      boolean inter1 = mInter[1][index]==null;
+      boolean inter2 = mInter[2][index]==null;
+
+      android.util.Log.e("EffectQueue", "numEffects="+mNumEffects+" effect id="+id+" index="+index+
+                         " duration="+mCurrentDuration[index]+" inter[0] null="+inter0+" inter[1] null="+inter1+" inter[2] null="+inter2);
+
+      if( !inter0 ) android.util.Log.e("EffectQueue","inter[0]: "+mInter[0][index].print());
+      if( !inter1 ) android.util.Log.e("EffectQueue","inter[1]: "+mInter[1][index].print());
+      if( !inter2 ) android.util.Log.e("EffectQueue","inter[2]: "+mInter[2][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/effect/EffectQueueFragment.java b/src/main/java/org/distorted/library/effect/EffectQueueFragment.java
new file mode 100644
index 0000000..5114bcb
--- /dev/null
+++ b/src/main/java/org/distorted/library/effect/EffectQueueFragment.java
@@ -0,0 +1,211 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effect;
+
+import android.opengl.GLES30;
+
+import org.distorted.library.message.EffectMessage;
+import org.distorted.library.type.Dynamic4D;
+import org.distorted.library.type.Static;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static2D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.library.type.Static5D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueueFragment extends EffectQueue
+  {
+  private static final int NUM_UNIFORMS = 8;
+  private static final int NUM_CACHE    = 4;
+  private static final int INDEX = Effect.FRAGMENT;
+  private static int mNumEffectsH;
+  private static int mTypeH;
+  private static int mUniformsH;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  EffectQueueFragment(long id)
+    { 
+    super(id,NUM_UNIFORMS,NUM_CACHE,INDEX);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mNumEffectsH= GLES30.glGetUniformLocation( mProgramH, "fNumEffects");
+    mTypeH      = GLES30.glGetUniformLocation( mProgramH, "fType");
+    mUniformsH  = GLES30.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++)
+      {
+      mCurrentDuration[i] += step;
+
+      if( mInter[0][i]!=null && mInter[0][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]<<Effect.LENGTH)+Effect.FRAGMENT,
+                                          mName[i],
+                                          mObjectID);
+      
+        if( FragmentEffect.isUnity(mName[i], mUniforms, NUM_UNIFORMS*i) )
+          {
+          remove(i);
+          i--;
+          continue;
+          }
+        else mInter[0][i] = null;
+        }
+
+      if( mInter[1][i]!=null ) mInter[2][i].interpolateMain( mUniforms, NUM_UNIFORMS*i+1, mCurrentDuration[i], step);
+      if( mInter[2][i]!=null ) mInter[1][i].interpolateMain( mCache   , NUM_CACHE*i     , 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];
+
+    mCache[NUM_CACHE*index  ] = mCache[NUM_CACHE*(index+1)  ];
+    mCache[NUM_CACHE*index+1] = mCache[NUM_CACHE*(index+1)+1];
+    mCache[NUM_CACHE*index+2] = mCache[NUM_CACHE*(index+1)+2];
+    mCache[NUM_CACHE*index+3] = mCache[NUM_CACHE*(index+1)+3];
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void send(float halfX, float halfY)
+    {
+    GLES30.glUniform1i( mNumEffectsH, mNumEffects);
+
+    if( mNumEffects>0 )
+      {
+      for(int i=0; i<mNumEffects; i++)
+        {
+        mUniforms[NUM_UNIFORMS*i+4] = mCache[NUM_CACHE*i  ]-halfX;
+        mUniforms[NUM_UNIFORMS*i+5] =-mCache[NUM_CACHE*i+1]+halfY;
+        mUniforms[NUM_UNIFORMS*i+6] = mCache[NUM_CACHE*i+2];
+        mUniforms[NUM_UNIFORMS*i+7] = mCache[NUM_CACHE*i+3];
+        }
+
+      GLES30.glUniform1iv( mTypeH    ,                 mNumEffects, mName    ,0);
+      GLES30.glUniform4fv( mUniformsH,(NUM_UNIFORMS/4)*mNumEffects, mUniforms,0);
+      }  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(FragmentEffect fe)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      int dim0 = 0;
+
+      if( fe.mDynamic0 != null )
+        {
+        mInter[0][mNumEffects] = fe.mDynamic0;
+        dim0 = fe.mDynamic0.getDimension();
+        }
+      else
+        {
+        mInter[0][mNumEffects] = null;
+
+        if( fe.mStatic0 != null )
+          {
+          Static s = fe.mStatic0;
+          dim0 = s.getDimension();
+
+          switch( dim0 )
+            {
+            case 5 : mUniforms[NUM_UNIFORMS*mNumEffects + 4] = ((Static5D)s).getV();
+            case 4 : mUniforms[NUM_UNIFORMS*mNumEffects + 3] = ((Static4D)s).getW();
+            case 3 : mUniforms[NUM_UNIFORMS*mNumEffects + 2] = ((Static3D)s).getZ();
+            case 2 : mUniforms[NUM_UNIFORMS*mNumEffects + 1] = ((Static2D)s).getY();
+            case 1 : mUniforms[NUM_UNIFORMS*mNumEffects    ] = ((Static1D)s).getX();
+            }
+          }
+        }
+
+      if( fe.mDynamic1 != null )
+        {
+        mInter[1][mNumEffects] = fe.mDynamic1;
+        }
+      else
+        {
+        mInter[1][mNumEffects] = null;
+
+        if( fe.mStatic1 != null )
+          {
+          Static s = fe.mStatic1;
+          int dim1 = s.getDimension();
+
+          switch( dim1 )
+            {
+            case 5 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 4] = ((Static5D)s).getV();
+            case 4 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 3] = ((Static4D)s).getW();
+            case 3 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 2] = ((Static3D)s).getZ();
+            case 2 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 1] = ((Static2D)s).getY();
+            case 1 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0    ] = ((Static1D)s).getX();
+            }
+          }
+        }
+
+      if( fe.mRegion instanceof Dynamic4D)
+        {
+        mInter[2][mNumEffects] = (Dynamic4D)fe.mRegion;
+        }
+      else if( fe.mRegion instanceof Static4D )
+        {
+        mInter[2][mNumEffects]  = null;
+
+        Static4D s = (Static4D)fe.mRegion;
+        mCache[NUM_CACHE*mNumEffects  ] = s.getX();
+        mCache[NUM_CACHE*mNumEffects+1] = s.getY();
+        mCache[NUM_CACHE*mNumEffects+2] = s.getZ();
+        mCache[NUM_CACHE*mNumEffects+3] = s.getW();
+        }
+      else return -1;
+
+      return addBase(fe);
+      }
+      
+    return -1;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/effect/EffectQueueMatrix.java b/src/main/java/org/distorted/library/effect/EffectQueueMatrix.java
new file mode 100644
index 0000000..fb43315
--- /dev/null
+++ b/src/main/java/org/distorted/library/effect/EffectQueueMatrix.java
@@ -0,0 +1,441 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effect;
+
+import android.opengl.GLES30;
+import android.opengl.Matrix;
+
+import org.distorted.library.DistortedOutputSurface;
+import org.distorted.library.message.EffectMessage;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Static;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static2D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.library.type.Static5D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueueMatrix extends EffectQueue
+  {   
+  private static final int NUM_UNIFORMS = 7;
+  private static final int NUM_CACHE    = 0;
+  private static final int INDEX = Effect.MATRIX;
+
+  private static float[] mMVPMatrix = new float[16];
+  private static float[] mTmpMatrix = new float[16];
+  private static float[] mViewMatrix= new float[16];
+
+  private static int mObjDH;      // This is a handle to half a Object dimensions
+  private static int mMVPMatrixH; // the transformation matrix
+  private static int mMVMatrixH;  // the modelview matrix.
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  EffectQueueMatrix(long id)
+    { 
+    super(id,NUM_UNIFORMS,NUM_CACHE,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);  
+
+    matrix[ 0] = mMVPMatrix[ 0];
+    matrix[ 1] = mMVPMatrix[ 1];
+    matrix[ 2] = mMVPMatrix[ 2];
+    matrix[ 3] = mMVPMatrix[ 3];
+    matrix[ 4] = mMVPMatrix[ 4];
+    matrix[ 5] = mMVPMatrix[ 5];
+    matrix[ 6] = mMVPMatrix[ 6];
+    matrix[ 7] = mMVPMatrix[ 7];
+    matrix[ 8] = mMVPMatrix[ 8];
+    matrix[ 9] = mMVPMatrix[ 9];
+    matrix[10] = mMVPMatrix[10];
+    matrix[11] = mMVPMatrix[11];
+    matrix[12] = mMVPMatrix[12];
+    matrix[13] = mMVPMatrix[13];
+    matrix[14] = mMVPMatrix[14];
+    matrix[15] = mMVPMatrix[15];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void magnify(DistortedOutputSurface projection, float halfX, float halfY, float halfZ, float marginInPixels)
+    {
+    float scale, nx, ny;
+    float[] result= new float[4];
+    float[] point = new float[4];
+    float[] matrix= new float[16];
+    float minx = Integer.MAX_VALUE;
+    float maxx = Integer.MIN_VALUE;
+    float miny = Integer.MAX_VALUE;
+    float maxy = Integer.MIN_VALUE;
+
+    point[3] = 1.0f;
+
+    Matrix.multiplyMM(matrix, 0, projection.mProjectionMatrix, 0, mViewMatrix, 0);
+
+    point[0] = +halfX; point[1] = +halfY; point[2] = +halfZ;
+    Matrix.multiplyMV(result,0,matrix,0,point,0);
+    nx = result[0]/result[3];
+    ny = result[1]/result[3];
+    if( nx<minx ) minx = nx;
+    if( nx>maxx ) maxx = nx;
+    if( ny<miny ) miny = ny;
+    if( ny>maxy ) maxy = ny;
+
+    point[0] = +halfX; point[1] = +halfY; point[2] = -halfZ;
+    Matrix.multiplyMV(result,0,matrix,0,point,0);
+    nx = result[0]/result[3];
+    ny = result[1]/result[3];
+    if( nx<minx ) minx = nx;
+    if( nx>maxx ) maxx = nx;
+    if( ny<miny ) miny = ny;
+    if( ny>maxy ) maxy = ny;
+
+    point[0] = +halfX; point[1] = -halfY; point[2] = +halfZ;
+    Matrix.multiplyMV(result,0,matrix,0,point,0);
+    nx = result[0]/result[3];
+    ny = result[1]/result[3];
+    if( nx<minx ) minx = nx;
+    if( nx>maxx ) maxx = nx;
+    if( ny<miny ) miny = ny;
+    if( ny>maxy ) maxy = ny;
+
+    point[0] = +halfX; point[1] = -halfY; point[2] = -halfZ;
+    Matrix.multiplyMV(result,0,matrix,0,point,0);
+    nx = result[0]/result[3];
+    ny = result[1]/result[3];
+    if( nx<minx ) minx = nx;
+    if( nx>maxx ) maxx = nx;
+    if( ny<miny ) miny = ny;
+    if( ny>maxy ) maxy = ny;
+
+    point[0] = -halfX; point[1] = +halfY; point[2] = +halfZ;
+    Matrix.multiplyMV(result,0,matrix,0,point,0);
+    nx = result[0]/result[3];
+    ny = result[1]/result[3];
+    if( nx<minx ) minx = nx;
+    if( nx>maxx ) maxx = nx;
+    if( ny<miny ) miny = ny;
+    if( ny>maxy ) maxy = ny;
+
+    point[0] = -halfX; point[1] = +halfY; point[2] = -halfZ;
+    Matrix.multiplyMV(result,0,matrix,0,point,0);
+    nx = result[0]/result[3];
+    ny = result[1]/result[3];
+    if( nx<minx ) minx = nx;
+    if( nx>maxx ) maxx = nx;
+    if( ny<miny ) miny = ny;
+    if( ny>maxy ) maxy = ny;
+
+    point[0] = -halfX; point[1] = -halfY; point[2] = +halfZ;
+    Matrix.multiplyMV(result,0,matrix,0,point,0);
+    nx = result[0]/result[3];
+    ny = result[1]/result[3];
+    if( nx<minx ) minx = nx;
+    if( nx>maxx ) maxx = nx;
+    if( ny<miny ) miny = ny;
+    if( ny>maxy ) maxy = ny;
+
+    point[0] = -halfX; point[1] = -halfY; point[2] = -halfZ;
+    Matrix.multiplyMV(result,0,matrix,0,point,0);
+    nx = result[0]/result[3];
+    ny = result[1]/result[3];
+    if( nx<minx ) minx = nx;
+    if( nx>maxx ) maxx = nx;
+    if( ny<miny ) miny = ny;
+    if( ny>maxy ) maxy = ny;
+
+    float xlen = projection.mWidth *(maxx-minx)/2;
+    float ylen = projection.mHeight*(maxy-miny)/2;
+
+    scale = 1.0f + marginInPixels/( xlen>ylen ? ylen:xlen );
+
+    //android.util.Log.d("scale", ""+marginInPixels+" scale= "+scale+" xlen="+xlen+" ylen="+ylen);
+
+    Matrix.scaleM(mViewMatrix, 0, scale, scale, scale);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// here construct the ModelView and the ModelViewProjection Matrices
+
+  private void constructMatrices(DistortedOutputSurface projection, float halfX, float halfY, float halfZ, float marginInPixels)
+    {
+    Matrix.setIdentityM(mViewMatrix, 0);
+    Matrix.translateM(mViewMatrix, 0, -projection.mWidth/2, projection.mHeight/2, -projection.mDistance);
+
+    float x,y,z, sx,sy,sz;
+    float mipmap = projection.mMipmap;
+
+    if( mipmap!=1 ) Matrix.scaleM(mViewMatrix, 0, mipmap, mipmap, mipmap);
+
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if (mName[i] == MatrixEffect.ROTATE )
+        {
+        x = mUniforms[NUM_UNIFORMS*i+4];
+        y = mUniforms[NUM_UNIFORMS*i+5];
+        z = mUniforms[NUM_UNIFORMS*i+6];
+
+        Matrix.translateM(mViewMatrix, 0, x,-y, z);
+        Matrix.rotateM( mViewMatrix, 0, mUniforms[NUM_UNIFORMS*i], mUniforms[NUM_UNIFORMS*i+1], mUniforms[NUM_UNIFORMS*i+2], mUniforms[NUM_UNIFORMS*i+3]);
+        Matrix.translateM(mViewMatrix, 0,-x, y,-z);
+        }
+      else if(mName[i] == MatrixEffect.QUATERNION )
+        {
+        x = mUniforms[NUM_UNIFORMS*i+4];
+        y = mUniforms[NUM_UNIFORMS*i+5];
+        z = mUniforms[NUM_UNIFORMS*i+6];
+
+        Matrix.translateM(mViewMatrix, 0, x,-y, z);
+        multiplyByQuat(mViewMatrix, mUniforms[NUM_UNIFORMS*i], mUniforms[NUM_UNIFORMS*i+1], mUniforms[NUM_UNIFORMS*i+2], mUniforms[NUM_UNIFORMS*i+3]);
+        Matrix.translateM(mViewMatrix, 0,-x, y,-z);
+        }
+      else if(mName[i] == MatrixEffect.MOVE )
+        {
+        sx = mUniforms[NUM_UNIFORMS*i  ];
+        sy = mUniforms[NUM_UNIFORMS*i+1];
+        sz = mUniforms[NUM_UNIFORMS*i+2];
+
+        Matrix.translateM(mViewMatrix, 0, sx,-sy, sz);
+        }
+      else if(mName[i] == MatrixEffect.SCALE )
+        {
+        sx = mUniforms[NUM_UNIFORMS*i  ];
+        sy = mUniforms[NUM_UNIFORMS*i+1];
+        sz = mUniforms[NUM_UNIFORMS*i+2];
+
+        Matrix.scaleM(mViewMatrix, 0, sx, sy, sz);
+        }
+      else if(mName[i] == MatrixEffect.SHEAR )
+        {
+        sx = mUniforms[NUM_UNIFORMS*i  ];
+        sy = mUniforms[NUM_UNIFORMS*i+1];
+        sz = mUniforms[NUM_UNIFORMS*i+2];
+
+        x  = mUniforms[NUM_UNIFORMS*i+4];
+        y  = mUniforms[NUM_UNIFORMS*i+5];
+        z  = mUniforms[NUM_UNIFORMS*i+6];
+
+        Matrix.translateM(mViewMatrix, 0, x,-y, z);
+
+        mViewMatrix[4] += sx*mViewMatrix[0]; // Multiply viewMatrix by 1 x 0 0 , i.e. X-shear.
+        mViewMatrix[5] += sx*mViewMatrix[1]; //                        0 1 0 0
+        mViewMatrix[6] += sx*mViewMatrix[2]; //                        0 0 1 0
+        mViewMatrix[7] += sx*mViewMatrix[3]; //                        0 0 0 1
+
+        mViewMatrix[0] += sy*mViewMatrix[4]; // Multiply viewMatrix by 1 0 0 0 , i.e. Y-shear.
+        mViewMatrix[1] += sy*mViewMatrix[5]; //                        y 1 0 0
+        mViewMatrix[2] += sy*mViewMatrix[6]; //                        0 0 1 0
+        mViewMatrix[3] += sy*mViewMatrix[7]; //                        0 0 0 1
+
+        mViewMatrix[4] += sz*mViewMatrix[8]; // Multiply viewMatrix by 1 0 0 0 , i.e. Z-shear.
+        mViewMatrix[5] += sz*mViewMatrix[9]; //                        0 1 0 0
+        mViewMatrix[6] += sz*mViewMatrix[10];//                        0 z 1 0
+        mViewMatrix[7] += sz*mViewMatrix[11];//                        0 0 0 1
+
+        Matrix.translateM(mViewMatrix, 0,-x, y, -z);
+        }
+      }
+
+    Matrix.translateM(mViewMatrix, 0, halfX,-halfY,-halfZ);
+
+    if( marginInPixels!=0 ) magnify(projection,halfX,halfY,halfZ, marginInPixels);
+
+    Matrix.multiplyMM(mMVPMatrix, 0, projection.mProjectionMatrix, 0, mViewMatrix, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mObjDH     = GLES30.glGetUniformLocation(mProgramH, "u_objD");
+    mMVPMatrixH= GLES30.glGetUniformLocation(mProgramH, "u_MVPMatrix");
+    mMVMatrixH = GLES30.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++)
+      {
+      mCurrentDuration[i] += step;
+
+      if( mInter[0][i]!=null && mInter[0][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]<<Effect.LENGTH)+Effect.MATRIX,
+                                          mName[i],
+                                          mObjectID);
+
+        if( MatrixEffect.isUnity(mName[i], mUniforms, NUM_UNIFORMS*i) )
+          {
+          remove(i);
+          i--;
+          continue;
+          }
+        else mInter[0][i] = null;
+        }
+
+      if( mInter[2][i]!=null )
+        {
+        mInter[2][i].interpolateMain(mUniforms, NUM_UNIFORMS*i+4, 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];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[] getMVP()
+    {
+    return mMVPMatrix;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void send(DistortedOutputSurface projection, float halfX, float halfY, float halfZ, float marginInPixels)
+    {
+    constructMatrices(projection,halfX,halfY,halfZ, marginInPixels);
+
+    GLES30.glUniform3f( mObjDH , halfX, halfY, halfZ);
+    GLES30.glUniformMatrix4fv(mMVMatrixH , 1, false, mViewMatrix, 0);
+    GLES30.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix , 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(MatrixEffect me)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      int dim0 = 0;
+
+      if( me.mDynamic0 != null )
+        {
+        mInter[0][mNumEffects] = me.mDynamic0;
+        dim0 = me.mDynamic0.getDimension();
+        }
+      else
+        {
+        mInter[0][mNumEffects] = null;
+
+        if( me.mStatic0 != null )
+          {
+          Static s = me.mStatic0;
+          dim0 = s.getDimension();
+
+          switch( dim0 )
+            {
+            case 5 : mUniforms[NUM_UNIFORMS*mNumEffects + 4] = ((Static5D)s).getV();
+            case 4 : mUniforms[NUM_UNIFORMS*mNumEffects + 3] = ((Static4D)s).getW();
+            case 3 : mUniforms[NUM_UNIFORMS*mNumEffects + 2] = ((Static3D)s).getZ();
+            case 2 : mUniforms[NUM_UNIFORMS*mNumEffects + 1] = ((Static2D)s).getY();
+            case 1 : mUniforms[NUM_UNIFORMS*mNumEffects    ] = ((Static1D)s).getX();
+            }
+          }
+        }
+
+      mInter[1][mNumEffects] = null;
+
+      if( me.mStatic1 != null )
+        {
+        Static s = me.mStatic1;
+        int dim1 = s.getDimension();
+
+        switch( dim1 )
+          {
+          case 5 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 4] = ((Static5D)s).getV();
+          case 4 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 3] = ((Static4D)s).getW();
+          case 3 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 2] = ((Static3D)s).getZ();
+          case 2 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 1] = ((Static2D)s).getY();
+          case 1 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0    ] = ((Static1D)s).getX();
+          }
+        }
+
+      if( me.mCenter instanceof Dynamic3D)
+        {
+        mInter[2][mNumEffects] = (Dynamic3D)me.mCenter;
+        }
+      else if( me.mCenter instanceof Static3D )
+        {
+        mInter[2][mNumEffects] = null;
+
+        Static3D s = (Static3D)me.mCenter;
+        mUniforms[NUM_UNIFORMS*mNumEffects+4] = s.getX();
+        mUniforms[NUM_UNIFORMS*mNumEffects+5] = s.getY();
+        mUniforms[NUM_UNIFORMS*mNumEffects+6] = s.getZ();
+        }
+      else return -1;
+
+      return addBase(me);
+      }
+      
+    return -1;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/effect/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/effect/EffectQueuePostprocess.java
new file mode 100644
index 0000000..7b995f7
--- /dev/null
+++ b/src/main/java/org/distorted/library/effect/EffectQueuePostprocess.java
@@ -0,0 +1,444 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effect;
+
+import android.content.res.Resources;
+import android.opengl.GLES30;
+
+import org.distorted.library.Distorted;
+import org.distorted.library.DistortedFramebuffer;
+import org.distorted.library.DistortedOutputSurface;
+import org.distorted.library.DistortedRenderState;
+import org.distorted.library.R;
+import org.distorted.library.message.EffectMessage;
+import org.distorted.library.program.DistortedProgram;
+import org.distorted.library.program.FragmentCompilationException;
+import org.distorted.library.program.FragmentUniformsException;
+import org.distorted.library.program.LinkingException;
+import org.distorted.library.program.VertexCompilationException;
+import org.distorted.library.program.VertexUniformsException;
+import org.distorted.library.type.Static;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static2D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.library.type.Static5D;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueuePostprocess extends EffectQueue
+  {
+  private static final int MAX_HALO = 50;    // Support effects creating up to MAX_HALO pixels wide 'halo' around the object.
+
+  private static final int POS_DATA_SIZE= 2; // Post Program: size of the position data in elements
+  private static final int TEX_DATA_SIZE= 2; // Post Program: size of the texture coordinate data in elements.
+
+  private static final int NUM_UNIFORMS = 5;
+  private static final int NUM_CACHE    = 0;
+  private static final int INDEX = Effect.POSTPROCESS;
+
+  private static final FloatBuffer mQuadPositions, mQuadTexture, mQuadTextureInv;
+
+  static
+    {
+    int dataLength      = 4;
+    int bytes_per_float = 4;
+
+    float[] position  = { -0.5f, -0.5f,  -0.5f, 0.5f,  0.5f,-0.5f,  0.5f, 0.5f };
+    float[] textureNor= {  0.0f,  0.0f,   0.0f, 1.0f,  1.0f, 0.0f,  1.0f, 1.0f };
+    float[] textureInv= {  0.0f,  0.0f,   1.0f, 0.0f,  0.0f, 1.0f,  1.0f, 1.0f };
+
+    mQuadPositions = ByteBuffer.allocateDirect(POS_DATA_SIZE*dataLength*bytes_per_float).order(ByteOrder.nativeOrder()).asFloatBuffer();
+    mQuadPositions.put(position).position(0);
+    mQuadTexture= ByteBuffer.allocateDirect(TEX_DATA_SIZE*dataLength*bytes_per_float).order(ByteOrder.nativeOrder()).asFloatBuffer();
+    mQuadTexture.put(textureNor).position(0);
+    mQuadTextureInv= ByteBuffer.allocateDirect(TEX_DATA_SIZE*dataLength*bytes_per_float).order(ByteOrder.nativeOrder()).asFloatBuffer();
+    mQuadTextureInv.put(textureInv).position(0);
+    }
+
+  int mQualityLevel;
+  float mQualityScale;
+  private int mHalo;
+
+  /////////////////////////////////////////////////////////////////////////////////
+  // BLUR effect
+  private static final float GAUSSIAN[] =   // G(0.00), G(0.03), G(0.06), ..., G(3.00), 0
+    {                                       // where G(x)= (1/(sqrt(2*PI))) * e^(-(x^2)/2). The last 0 terminates.
+    0.398948f, 0.398769f, 0.398231f, 0.397336f, 0.396086f, 0.394485f, 0.392537f, 0.390247f, 0.387622f, 0.384668f,
+    0.381393f, 0.377806f, 0.373916f, 0.369733f, 0.365268f, 0.360532f, 0.355538f, 0.350297f, 0.344823f, 0.339129f,
+    0.333229f, 0.327138f, 0.320868f, 0.314436f, 0.307856f, 0.301142f, 0.294309f, 0.287373f, 0.280348f, 0.273248f,
+    0.266089f, 0.258884f, 0.251648f, 0.244394f, 0.237135f, 0.229886f, 0.222657f, 0.215461f, 0.208311f, 0.201217f,
+    0.194189f, 0.187238f, 0.180374f, 0.173605f, 0.166940f, 0.160386f, 0.153951f, 0.147641f, 0.141462f, 0.135420f,
+    0.129520f, 0.123765f, 0.118159f, 0.112706f, 0.107408f, 0.102266f, 0.097284f, 0.092461f, 0.087797f, 0.083294f,
+    0.078951f, 0.074767f, 0.070741f, 0.066872f, 0.063158f, 0.059596f, 0.056184f, 0.052920f, 0.049801f, 0.046823f,
+    0.043984f, 0.041280f, 0.038707f, 0.036262f, 0.033941f, 0.031740f, 0.029655f, 0.027682f, 0.025817f, 0.024056f,
+    0.022395f, 0.020830f, 0.019357f, 0.017971f, 0.016670f, 0.015450f, 0.014305f, 0.013234f, 0.012232f, 0.011295f,
+    0.010421f, 0.009606f, 0.008847f, 0.008140f, 0.007483f, 0.006873f, 0.006307f, 0.005782f, 0.005296f, 0.004847f,
+    0.004432f, 0.000000f
+    };
+  private static final int NUM_GAUSSIAN = GAUSSIAN.length-2;
+
+  // The (fixed-function-sampled) Gaussian Blur kernels are of the size k0=1, k1=2, k2=2, k3=3, k4=3, k5=4, k6=4,...
+  // i.e. k(i)=floor((i+3)/2).  (the 'i' in k(i) means 'blur taking into account the present pixel and 'i' pixels
+  // in all 4 directions)
+  // We need room for MAX_BLUR of them, and sum(i=0...N, floor((i+3)/2)) = N + floor(N*N/4)
+  private static float[] weightsCache = new float[MAX_HALO + MAX_HALO*MAX_HALO/4];
+  private static float[] offsetsCache = new float[MAX_HALO + MAX_HALO*MAX_HALO/4];
+
+  private static DistortedProgram mBlur1Program, mBlur2Program;
+  private static int mRadius1H,mOffsets1H,mWeights1H,mDepth1H, mColorTexture1H;
+  private static int mRadius2H,mOffsets2H,mWeights2H,mDepth2H, mColorTexture2H;
+  private static float[] mWeights = new float[MAX_HALO];
+  private static float[] mOffsets = new float[MAX_HALO];
+  /////////////////////////////////////////////////////////////////////////////////
+  // GLOW effect
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  EffectQueuePostprocess(long id)
+    { 
+    super(id,NUM_UNIFORMS,NUM_CACHE,INDEX );
+
+    mQualityLevel = 0;
+    mQualityScale = 1.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void createProgram(Resources resources)
+  throws FragmentCompilationException,VertexCompilationException,VertexUniformsException,FragmentUniformsException,LinkingException
+    {
+    final InputStream blur1VertexStream   = resources.openRawResource(R.raw.blur_vertex_shader);
+    final InputStream blur1FragmentStream = resources.openRawResource(R.raw.blur1_fragment_shader);
+
+    try
+      {
+      mBlur1Program = new DistortedProgram(blur1VertexStream,blur1FragmentStream,
+                                          Distorted.GLSL_VERSION,
+                                          Distorted.GLSL_VERSION + "#define MAX_BLUR "+MAX_HALO, Distorted.GLSL);
+      }
+    catch(Exception e)
+      {
+      android.util.Log.e("EFFECTS", "exception trying to compile BLUR1 program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int blur1ProgramH = mBlur1Program.getProgramHandle();
+    mRadius1H       = GLES30.glGetUniformLocation( blur1ProgramH, "u_Radius");
+    mOffsets1H      = GLES30.glGetUniformLocation( blur1ProgramH, "u_Offsets");
+    mWeights1H      = GLES30.glGetUniformLocation( blur1ProgramH, "u_Weights");
+    mDepth1H        = GLES30.glGetUniformLocation( blur1ProgramH, "u_Depth");
+    mColorTexture1H = GLES30.glGetUniformLocation( blur1ProgramH, "u_ColorTexture");
+
+    final InputStream blur2VertexStream   = resources.openRawResource(R.raw.blur_vertex_shader);
+    final InputStream blur2FragmentStream = resources.openRawResource(R.raw.blur2_fragment_shader);
+
+    try
+      {
+      mBlur2Program = new DistortedProgram(blur2VertexStream,blur2FragmentStream,
+                                          Distorted.GLSL_VERSION,
+                                          Distorted.GLSL_VERSION + "#define MAX_BLUR "+MAX_HALO, Distorted.GLSL);
+      }
+    catch(Exception e)
+      {
+      android.util.Log.e("EFFECTS", "exception trying to compile BLUR2 program: "+e.getMessage());
+      // run anyway as compiling Blur2 WILL fail on OpenGL 2.0 contexts
+      mBlur2Program = mBlur1Program;
+      }
+
+    int blur2ProgramH = mBlur2Program.getProgramHandle();
+    mRadius2H       = GLES30.glGetUniformLocation( blur2ProgramH, "u_Radius");
+    mOffsets2H      = GLES30.glGetUniformLocation( blur2ProgramH, "u_Offsets");
+    mWeights2H      = GLES30.glGetUniformLocation( blur2ProgramH, "u_Weights");
+    mDepth2H        = GLES30.glGetUniformLocation( blur2ProgramH, "u_Depth");
+    mColorTexture2H = GLES30.glGetUniformLocation( blur2ProgramH, "u_ColorTexture");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean compute(long currTime)
+    {
+    if( currTime==mTime ) return false;
+    if( mTime==0 ) mTime = currTime;
+    long step = (currTime-mTime);
+
+    mHalo = 0;
+    int halo;
+
+    for(int i=0; i<mNumEffects; i++)
+      {
+      mCurrentDuration[i] += step;
+
+      if( mInter[0][i]!=null && mInter[0][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]<<Effect.LENGTH)+Effect.POSTPROCESS,
+                                          mName[i],
+                                          mObjectID);
+
+        if( PostprocessEffect.isUnity(mName[i], mUniforms, NUM_UNIFORMS*i) )
+          {
+          remove(i);
+          i--;
+          continue;
+          }
+        else mInter[0][i] = null;
+        }
+
+      if( mInter[1][i]!=null ) mInter[1][i].interpolateMain( mUniforms, NUM_UNIFORMS*i+1, mCurrentDuration[i], step);
+
+      halo = (int)mUniforms[NUM_UNIFORMS*i];
+      if( halo>mHalo ) mHalo = halo;
+      }
+
+    mTime = currTime;
+
+    return true;
+    }  
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// This implements the 'Better separable implementation using GPU fixed function sampling' from
+// https://software.intel.com/en-us/blogs/2014/07/15/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms
+
+  private void computeGaussianKernel(int radius)
+    {
+    int offset = radius + radius*radius/4;
+
+    if( weightsCache[offset]==0.0f )
+      {
+      float z, x= 0.0f, P= (float)NUM_GAUSSIAN / (radius>3 ? radius:3);
+      mWeights[0] = GAUSSIAN[0];
+      float sum   = GAUSSIAN[0];
+      int j;
+
+      for(int i=1; i<=radius; i++)
+        {
+        x += P;
+        j = (int)x;
+        z = x-j;
+
+        mWeights[i] = (1-z)*GAUSSIAN[j] + z*GAUSSIAN[j+1];
+        sum += 2*mWeights[i];
+        }
+
+      for(int i=0; i<=radius; i++) mWeights[i] /= sum;
+
+      int numloops = radius/2;
+      weightsCache[offset] = mWeights[0];
+      offsetsCache[offset] = 0.0f;
+
+      for(int i=0; i<numloops; i++)
+        {
+        offsetsCache[offset+i+1] = mWeights[2*i+1]*(2*i+1) + mWeights[2*i+2]*(2*i+2);
+        weightsCache[offset+i+1] = mWeights[2*i+1] + mWeights[2*i+2];
+        offsetsCache[offset+i+1]/= weightsCache[offset+i+1];
+        }
+
+      if( radius%2 == 1 )
+        {
+        int index = offset + radius/2 +1;
+        offsetsCache[index]=radius;
+        weightsCache[index]=mWeights[radius];
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getHalo()
+    {
+    return mNumEffects>0 ? mHalo : 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int postprocess(long time, DistortedOutputSurface surface)
+    {
+    int numRenders = 0;
+
+    if( mNumEffects>0 )
+      {
+      compute(time);
+
+      for(int i=0; i<mNumEffects; i++)
+        {
+        if (mName[i] == PostprocessEffect.BLUR )
+          {
+          blur(NUM_UNIFORMS*i,surface);
+          numRenders += 2;
+          }
+        else if (mName[i] == PostprocessEffect.GLOW )
+          {
+          glow(NUM_UNIFORMS*i,surface);
+          numRenders += 2;
+          }
+        }
+      }
+
+    return numRenders;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void blur(int index, DistortedOutputSurface surface)
+    {
+    DistortedFramebuffer buffer = surface.mBuffer[mQualityLevel];
+    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, buffer.mFBOH[0]);
+
+    float w1 = buffer.mWidth;
+    float h1 = buffer.mHeight;
+
+    int radius = (int)(mUniforms[index]*mQualityScale);
+    if( radius>=MAX_HALO ) radius = MAX_HALO-1;
+    computeGaussianKernel(radius);
+
+    int offset = radius + radius*radius/4;
+    radius = (radius+1)/2;
+
+    // horizontal blur
+    GLES30.glViewport(0, 0, (int)w1, (int)h1);
+    mBlur1Program.useProgram();
+    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, buffer.mColorH[1], 0);
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mColorH[0]);
+
+    GLES30.glUniform1fv( mWeights1H, radius+1, weightsCache,offset);
+    GLES30.glUniform1i( mRadius1H, radius);
+    GLES30.glUniform1f( mDepth1H , 1.0f-surface.mNear);
+    GLES30.glUniform1i( mColorTexture1H , 0 );
+    for(int i=0; i<=radius; i++) mOffsets[i] = offsetsCache[offset+i]/h1;
+    GLES30.glUniform1fv( mOffsets1H ,radius+1, mOffsets,0);
+    GLES30.glVertexAttribPointer(mBlur1Program.mAttribute[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mQuadPositions);
+    GLES30.glVertexAttribPointer(mBlur1Program.mAttribute[1], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mQuadTexture);
+
+    DistortedRenderState.useStencilMark();
+    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
+    DistortedRenderState.unuseStencilMark();
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+
+    // vertical blur
+    mBlur2Program.useProgram();
+    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, buffer.mColorH[0], 0);
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mColorH[1]);
+
+    GLES30.glColorMask(true,true,true,true);
+    GLES30.glClearColor(0.0f,0.0f,0.0f,0.0f);
+    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
+
+    GLES30.glUniform1fv( mWeights2H, radius+1, weightsCache,offset);
+    GLES30.glUniform1i( mRadius2H, radius);
+    GLES30.glUniform1f( mDepth2H , 1.0f-surface.mNear);
+    GLES30.glUniform1i( mColorTexture2H , 0 );
+    for(int i=0; i<=radius; i++) mOffsets[i] = offsetsCache[offset+i]/w1;
+    GLES30.glUniform1fv( mOffsets2H ,radius+1, mOffsets,0);
+    GLES30.glVertexAttribPointer(mBlur2Program.mAttribute[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mQuadPositions);
+    GLES30.glVertexAttribPointer(mBlur2Program.mAttribute[1], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mQuadTexture);
+
+    DistortedRenderState.useStencilMark();
+    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
+    DistortedRenderState.unuseStencilMark();
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void glow(int index, DistortedOutputSurface surface)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(PostprocessEffect pe)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      int dim0 = 0;
+
+      if( pe.mDynamic0 != null )
+        {
+        mInter[0][mNumEffects] = pe.mDynamic0;
+        dim0 = pe.mDynamic0.getDimension();
+        }
+      else
+        {
+        mInter[0][mNumEffects] = null;
+
+        if( pe.mStatic0 != null )
+          {
+          Static s = pe.mStatic0;
+          dim0 = s.getDimension();
+
+          switch( dim0 )
+            {
+            case 5 : mUniforms[NUM_UNIFORMS*mNumEffects + 4] = ((Static5D)s).getV();
+            case 4 : mUniforms[NUM_UNIFORMS*mNumEffects + 3] = ((Static4D)s).getW();
+            case 3 : mUniforms[NUM_UNIFORMS*mNumEffects + 2] = ((Static3D)s).getZ();
+            case 2 : mUniforms[NUM_UNIFORMS*mNumEffects + 1] = ((Static2D)s).getY();
+            case 1 : mUniforms[NUM_UNIFORMS*mNumEffects    ] = ((Static1D)s).getX();
+            }
+          }
+        }
+
+      mInter[1][mNumEffects] = null;
+
+      if( pe.mStatic1 != null )
+        {
+        Static s = pe.mStatic1;
+        int dim1 = s.getDimension();
+
+        switch( dim1 )
+          {
+          case 5 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 4] = ((Static5D)s).getV();
+          case 4 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 3] = ((Static4D)s).getW();
+          case 3 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 2] = ((Static3D)s).getZ();
+          case 2 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0 + 1] = ((Static2D)s).getY();
+          case 1 : mUniforms[NUM_UNIFORMS*mNumEffects + dim0    ] = ((Static1D)s).getX();
+          }
+        }
+
+      return addBase(pe);
+      }
+
+    return -1;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/effect/EffectQueueVertex.java b/src/main/java/org/distorted/library/effect/EffectQueueVertex.java
new file mode 100644
index 0000000..2c6e5c3
--- /dev/null
+++ b/src/main/java/org/distorted/library/effect/EffectQueueVertex.java
@@ -0,0 +1,255 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effect;
+
+import android.opengl.GLES30;
+
+import org.distorted.library.message.EffectMessage;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Dynamic4D;
+import org.distorted.library.type.Static;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static2D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.library.type.Static5D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueueVertex extends EffectQueue
+  { 
+  private static final int NUM_UNIFORMS = 12;
+  private static final int NUM_CACHE    =  3;
+  private static final int INDEX = Effect.VERTEX;
+  private static int mNumEffectsH;
+  private static int mTypeH;
+  private static int mUniformsH;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  EffectQueueVertex(long id)
+    { 
+    super(id,NUM_UNIFORMS,NUM_CACHE,INDEX);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mNumEffectsH= GLES30.glGetUniformLocation( mProgramH, "vNumEffects");
+    mTypeH      = GLES30.glGetUniformLocation( mProgramH, "vType");
+    mUniformsH  = GLES30.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++)
+      {
+      mCurrentDuration[i] += step;
+
+      if( mInter[0][i]!=null )
+        {
+        if( mInter[0][i].interpolateMain(mUniforms ,NUM_UNIFORMS*i, mCurrentDuration[i], step) )
+          {
+          postprocess(i);
+
+          for(int j=0; j<mNumListeners; j++)
+            EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                            EffectMessage.EFFECT_FINISHED,
+                                           (mID[i]<<Effect.LENGTH)+Effect.VERTEX,
+                                            mName[i],
+                                            mObjectID);
+
+          if( VertexEffect.isUnity(mName[i], mUniforms, NUM_UNIFORMS*i) )
+            {
+            remove(i);
+            i--;
+            continue;
+            }
+          else mInter[0][i] = null;
+          }
+        else
+          {
+          postprocess(i);
+          }
+        }
+
+      if( mInter[1][i]!=null ) mInter[1][i].interpolateMain(mUniforms, NUM_UNIFORMS*i+8, mCurrentDuration[i], step);
+      if( mInter[2][i]!=null ) mInter[2][i].interpolateMain(mCache   , NUM_CACHE*i     , 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];
+
+    mCache[NUM_CACHE*index  ] = mCache[NUM_CACHE*(index+1)  ];
+    mCache[NUM_CACHE*index+1] = mCache[NUM_CACHE*(index+1)+1];
+    mCache[NUM_CACHE*index+2] = mCache[NUM_CACHE*(index+1)+2];
+
+    mUniforms[NUM_UNIFORMS*index+ 8] = mUniforms[NUM_UNIFORMS*(index+1)+ 8];
+    mUniforms[NUM_UNIFORMS*index+ 9] = mUniforms[NUM_UNIFORMS*(index+1)+ 9];
+    mUniforms[NUM_UNIFORMS*index+10] = mUniforms[NUM_UNIFORMS*(index+1)+10];
+    mUniforms[NUM_UNIFORMS*index+11] = mUniforms[NUM_UNIFORMS*(index+1)+11];
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void send(float halfX, float halfY, float halfZ)
+    {
+    GLES30.glUniform1i( mNumEffectsH, mNumEffects);
+      
+    if( mNumEffects>0 )
+      {
+      for(int i=0; i<mNumEffects; i++)
+        {
+        mUniforms[NUM_UNIFORMS*i+5] = mCache[NUM_CACHE*i  ]-halfX;
+        mUniforms[NUM_UNIFORMS*i+6] =-mCache[NUM_CACHE*i+1]+halfY;
+        mUniforms[NUM_UNIFORMS*i+7] = mCache[NUM_CACHE*i+2]-halfZ;
+        }
+
+      GLES30.glUniform1iv( mTypeH    ,                 mNumEffects, mName    ,0);
+      GLES30.glUniform4fv( mUniformsH,(NUM_UNIFORMS/4)*mNumEffects, mUniforms,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, switch the angles from degrees to radians
+// 3) likewise in case of WAVE and PINCH
+// 4) In case of DISTORT, invert the Y-axis
+  
+  private void postprocess(int effect)
+    {
+    if( mName[effect]==VertexEffect.SWIRL )
+      {
+      mUniforms[NUM_UNIFORMS*effect  ] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect  ]/180);
+      }
+    if( mName[effect]==VertexEffect.PINCH )
+      {
+      mUniforms[NUM_UNIFORMS*effect+1] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect+1]/180);
+      }
+    if( mName[effect]==VertexEffect.WAVE )
+      {
+      mUniforms[NUM_UNIFORMS*effect+2] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect+2]/180);
+      mUniforms[NUM_UNIFORMS*effect+3] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect+3]/180);
+      mUniforms[NUM_UNIFORMS*effect+4] = (float)(Math.PI*mUniforms[NUM_UNIFORMS*effect+4]/180);
+      }
+    if( mName[effect]==VertexEffect.DISTORT )
+      {
+      mUniforms[NUM_UNIFORMS*effect+1] =-mUniforms[NUM_UNIFORMS*effect+1];
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(VertexEffect ve)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      if( ve.mDynamic0 != null )
+        {
+        mInter[0][mNumEffects] = ve.mDynamic0;
+        }
+      else
+        {
+        mInter[0][mNumEffects] = null;
+
+        if( ve.mStatic0 != null )
+          {
+          Static s = ve.mStatic0;
+
+          switch( s.getDimension() )
+            {
+            case 5 : mUniforms[NUM_UNIFORMS*mNumEffects + 4] = ((Static5D)s).getV();
+            case 4 : mUniforms[NUM_UNIFORMS*mNumEffects + 3] = ((Static4D)s).getW();
+            case 3 : mUniforms[NUM_UNIFORMS*mNumEffects + 2] = ((Static3D)s).getZ();
+            case 2 : mUniforms[NUM_UNIFORMS*mNumEffects + 1] = ((Static2D)s).getY();
+            case 1 : mUniforms[NUM_UNIFORMS*mNumEffects    ] = ((Static1D)s).getX();
+            }
+          }
+        }
+
+      if( ve.mRegion!=null )
+        {
+        if( ve.mRegion instanceof Dynamic4D)
+          {
+          mInter[1][mNumEffects] = (Dynamic4D)ve.mRegion;
+          }
+        else if ( ve.mRegion instanceof Static4D)
+          {
+          Static4D tmp = (Static4D)ve.mRegion;
+
+          float z = tmp.getZ();
+
+          mUniforms[NUM_UNIFORMS*mNumEffects+ 8] = tmp.getX();
+          mUniforms[NUM_UNIFORMS*mNumEffects+ 9] =-tmp.getY();   // invert y already
+          mUniforms[NUM_UNIFORMS*mNumEffects+10] = z<=0.0f ? Float.MAX_VALUE : z;
+          mUniforms[NUM_UNIFORMS*mNumEffects+11] = tmp.getW();
+          mInter[1][mNumEffects] = null;
+          }
+        else return -1;
+        }
+      else
+        {
+        mUniforms[NUM_UNIFORMS*mNumEffects+ 8] = 0.0f;
+        mUniforms[NUM_UNIFORMS*mNumEffects+ 9] = 0.0f;
+        mUniforms[NUM_UNIFORMS*mNumEffects+10] = Float.MAX_VALUE;
+        mUniforms[NUM_UNIFORMS*mNumEffects+11] = 0.0f;
+        mInter[1][mNumEffects] = null;
+        }
+
+      if( ve.mCenter instanceof Dynamic3D)
+        {
+        mInter[2][mNumEffects] = (Dynamic3D)ve.mCenter;
+        }
+      else if( ve.mCenter instanceof Static3D)
+        {
+        mInter[2][mNumEffects] = null;
+        mCache[NUM_CACHE*mNumEffects  ] = ((Static3D)ve.mCenter).getX();
+        mCache[NUM_CACHE*mNumEffects+1] = ((Static3D)ve.mCenter).getY();
+        mCache[NUM_CACHE*mNumEffects+2] = ((Static3D)ve.mCenter).getZ();
+        }
+
+      long ret= addBase(ve);
+
+      postprocess(mNumEffects-1); //addBase just incremented mNumEffects
+
+      return ret;
+      }
+
+    return -1;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/effect/FragmentEffect.java b/src/main/java/org/distorted/library/effect/FragmentEffect.java
index fd3faa2..fbd4784 100644
--- a/src/main/java/org/distorted/library/effect/FragmentEffect.java
+++ b/src/main/java/org/distorted/library/effect/FragmentEffect.java
@@ -24,6 +24,8 @@ import org.distorted.library.type.Dynamic;
 import org.distorted.library.type.Static;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// FRAGMENT EFFECTS
+// 8 Uniforms: 4-per effect interpolated values, 4 dimensional Region.
 
 public abstract class FragmentEffect extends Effect
   {
@@ -65,7 +67,7 @@ public abstract class FragmentEffect extends Effect
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static boolean isUnity(int name, float[] buffer, int index)
+  public static boolean isUnity(int name, float[] buffer, int index)
     {
     switch(mUnityDim[name])
       {
diff --git a/src/main/java/org/distorted/library/effect/MatrixEffect.java b/src/main/java/org/distorted/library/effect/MatrixEffect.java
index 5e68345..a931012 100644
--- a/src/main/java/org/distorted/library/effect/MatrixEffect.java
+++ b/src/main/java/org/distorted/library/effect/MatrixEffect.java
@@ -22,6 +22,8 @@ package org.distorted.library.effect;
 import org.distorted.library.type.*;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// MATRIX EFFECTS.
+// 7 Uniforms: 4 per-effect interpolated values + 3 dimensional center.
 
 public abstract class MatrixEffect extends Effect
   {
@@ -35,8 +37,8 @@ public abstract class MatrixEffect extends Effect
   static final int MAX = 5;
   private static final int MAX_UNITY_DIM = 3;
 
-  Dynamic mDynamic0;
-  Static  mStatic0, mStatic1;
+  Dynamic mDynamic0,mDynamic1;
+  Static  mStatic0 , mStatic1;
   Data3D mCenter;
 
   private final static float[] mUnity= new float[MAX_UNITY_DIM*NUM_EFFECTS];
@@ -58,7 +60,7 @@ public abstract class MatrixEffect extends Effect
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static boolean isUnity(int name, float[] buffer, int index)
+  public static boolean isUnity(int name, float[] buffer, int index)
     {
     switch(mUnityDim[name])
       {
diff --git a/src/main/java/org/distorted/library/effect/PostprocessEffect.java b/src/main/java/org/distorted/library/effect/PostprocessEffect.java
index 1b10d48..707da85 100644
--- a/src/main/java/org/distorted/library/effect/PostprocessEffect.java
+++ b/src/main/java/org/distorted/library/effect/PostprocessEffect.java
@@ -23,6 +23,8 @@ import org.distorted.library.type.Dynamic;
 import org.distorted.library.type.Static;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// POSTPROCESSING EFFECTS.
+// 5 Uniforms: 5 per-effect interpolated values.
 
 public abstract class PostprocessEffect extends Effect
   {
@@ -55,7 +57,7 @@ public abstract class PostprocessEffect extends Effect
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static boolean isUnity(int name, float[] buffer, int index)
+  public static boolean isUnity(int name, float[] buffer, int index)
     {
     switch(mUnityDim[name])
       {
diff --git a/src/main/java/org/distorted/library/effect/VertexEffect.java b/src/main/java/org/distorted/library/effect/VertexEffect.java
index 8f03194..00a2a8e 100644
--- a/src/main/java/org/distorted/library/effect/VertexEffect.java
+++ b/src/main/java/org/distorted/library/effect/VertexEffect.java
@@ -25,6 +25,8 @@ import org.distorted.library.type.Dynamic;
 import org.distorted.library.type.Static;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// VERTEX EFFECTS
+// 12 Uniforms: 5 per-effect interpolated values, 3-dimensional center, 4-dimensional Region
 
 public abstract class VertexEffect extends Effect
   {
@@ -63,7 +65,7 @@ public abstract class VertexEffect extends Effect
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static boolean isUnity(int name, float[] buffer, int index)
+  public static boolean isUnity(int name, float[] buffer, int index)
     {
     switch(mUnityDim[name])
       {
diff --git a/src/main/java/org/distorted/library/message/EffectListener.java b/src/main/java/org/distorted/library/message/EffectListener.java
index 84f5644..2523755 100644
--- a/src/main/java/org/distorted/library/message/EffectListener.java
+++ b/src/main/java/org/distorted/library/message/EffectListener.java
@@ -21,8 +21,6 @@ package org.distorted.library.message;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-import org.distorted.library.EffectNames;
-
 /**
  * This interface lets users of the Distorted library get notified when something happens to one of the effects.
  * To receive the notifications, we first have to register with a call to {@link org.distorted.library.DistortedEffects#registerForMessages(EffectListener)}.
@@ -37,14 +35,13 @@ public interface EffectListener
  * @param eventType  Type of event that happened.
  * @param effectID   ID of the effect the event happened to. This ID must have been previously returned by one
  *                   of the DistortedEffects.{deform,distort,move,...} functions.
- * @param effectName Name of the effect as defined by EffectNames.
+ * @param effectName Name of the effect as defined by final constants from the 4 classes descendant from Effect.
  * @param objectID   the ID of the DistortedEffects object, as returned by {@link org.distorted.library.DistortedEffects#getID()},
  *                   this event happened to.
  * @see EffectMessage
- * @see EffectNames
  */
    
-  void effectMessage(final EffectMessage eventType, final long effectID, final EffectNames effectName, final long objectID);
+  void effectMessage(final EffectMessage eventType, final long effectID, final int effectName, final long objectID);
   }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/type/Dynamic.java b/src/main/java/org/distorted/library/type/Dynamic.java
index 40cd52e..aea7cc7 100644
--- a/src/main/java/org/distorted/library/type/Dynamic.java
+++ b/src/main/java/org/distorted/library/type/Dynamic.java
@@ -81,7 +81,7 @@ public abstract class Dynamic
    */
   public static final int ACCESS_SEQUENTIAL = 1;
 
-  protected final int mDimension;
+  protected int mDimension;
   protected int numPoints;
   protected int mSegment;       // between which pair of points are we currently? (in case of PATH this is a bit complicated!)
   protected boolean cacheDirty; // VectorCache not up to date
@@ -160,7 +160,7 @@ public abstract class Dynamic
   
   protected Dynamic()
     {
-    mDimension = 0;
+
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
