commit 6a06a9124c05ef780773a82aa4511750e95fb70d
Author: Leszek Koltunski <leszek@distortedandroid.org>
Date:   Wed May 25 20:45:23 2016 +0100

    Initial commit

diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..d36a1ce
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 22
+    buildToolsVersion "23.0.3"
+
+    defaultConfig {
+        minSdkVersion 15
+        targetSdkVersion 23
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+        }
+    }
+}
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c4cd868
--- /dev/null
+++ b/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.distorted.library"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="10"
+        android:targetSdkVersion="19" />
+
+</manifest>
diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
new file mode 100644
index 0000000..55db683
--- /dev/null
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -0,0 +1,498 @@
+package org.distorted.library;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import android.opengl.GLSurfaceView;
+import android.opengl.GLES20;
+import android.os.Build;
+import android.util.Log;
+
+import org.distorted.library.R;
+import org.distorted.library.exception.*;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * A singleton class used to control various global settings.
+ */
+public class Distorted 
+{
+  /**
+   * When creating an instance of a DistortedBitmap from another instance, do not clone anything.
+   * Used in the copy constructor.
+   */
+  public static final int CLONE_NOTHING = 0x0;
+  /**
+   * When creating an instance of a DistortedObject from another instance, clone the Bitmap that's
+   * backing up our DistortedObject. 
+   * <p>
+   * This way we can have two DistortedObjects, both backed up by the same Bitmap, to which we can 
+   * apply different effects. Used in the copy constructor.
+   */
+  public static final int CLONE_BITMAP  = 0x1;
+  /**
+   * When creating an instance of a DistortedObject from another instance, clone the Matrix Effects.
+   * <p>
+   * This way we can have two different DistortedObjects with different Bitmaps behind them, both 
+   * always displayed in exactly the same place on the screen. Applying any matrix-based effect to 
+   * one of them automatically applies the effect to the other. Used in the copy constructor.
+   */
+  public static final int CLONE_MATRIX  = 0x2;
+  /**
+   * When creating an instance of a DistortedObject from another instance, clone the Vertex Effects.
+   * <p>
+   * This way we can have two different DistortedObjects with different Bitmaps behind them, both 
+   * always with the same Vertex effects. Applying any vertex-based effect to one of them automatically 
+   * applies the effect to the other. Used in the copy constructor.
+   */
+  public static final int CLONE_VERTEX  = 0x4;
+  /**
+   * When creating an instance of a DistortedObject from another instance, clone the Fragment Effects.
+   * <p>
+   * This way we can have two different DistortedObjects with different Bitmaps behind them, both 
+   * always with the same Fragment effects. Applying any fragment-based effect to one of them automatically 
+   * applies the effect to the other. Used in the copy constructor.
+   */
+  public static final int CLONE_FRAGMENT= 0x8;
+  /**
+   * When creating an instance of a DistortedNode from another instance, clone the children Nodes.
+   * <p>
+   * This is mainly useful for creating many similar sub-trees of Bitmaps and rendering then at different places
+   * on the screen, with (optionally) different effects of the top-level root Bitmap.   
+   */
+  public static final int CLONE_CHILDREN= 0x10;
+  /**
+  * Constant used to represent a Matrix-based effect.
+  */
+  public static final int TYPE_MATR = 0x1;
+  /**
+   * Constant used to represent a Vertex-based effect.
+   */
+  public static final int TYPE_VERT = 0x2;
+  /**
+   * Constant used to represent a Fragment-based effect.
+   */
+  public static final int TYPE_FRAG = 0x4;
+   
+  private static final String TAG = Distorted.class.getSimpleName();
+  private static boolean mInitialized = false;
+  
+  static int mPositionH;      // pass in model position information
+  static int mColorH;         // pass in model color information
+  static int mTextureUniformH;// pass in the texture.
+  static int mNormalH;        // pass in model normal information.
+  static int mTextureCoordH;  // pass in model texture coordinate information.
+  static int mProgramH;       // This is a handle to our shading program.  
+
+  static DistortedProjection mProjection = new DistortedProjection(false);
+  static float mFOV = 60.0f;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private Distorted()
+    {
+    
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private static void sanitizeMaxValues() throws VertexUniformsException,FragmentUniformsException
+    {
+    int maxV,maxF;  
+    int[] param = new int[1];
+    
+    GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_UNIFORM_VECTORS, param, 0);
+    maxV = param[0];
+    GLES20.glGetIntegerv(GLES20.GL_MAX_FRAGMENT_UNIFORM_VECTORS, param, 0);
+    maxF = param[0];
+    
+    Log.d(TAG, "Max vectors in vertex shader: "+maxV);
+    Log.d(TAG, "Max vectors in fragment shader: "+maxF);
+    
+    if( Build.FINGERPRINT.startsWith("generic") == false )
+      {
+      int realMaxV = (maxV-11)/4;   // adjust this in case of changes to the shaders...
+      int realMaxF = (maxF- 2)/4;   //
+    
+      if( EffectListVertex.getMax() > realMaxV )
+        {
+        throw new VertexUniformsException("Too many effects in the vertex shader, max is "+realMaxV, realMaxV);
+        }
+      if( EffectListFragment.getMax() > realMaxF )
+        {
+        throw new FragmentUniformsException("Too many effects in the fragment shader, max is "+realMaxF, realMaxF);
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int compileShader(final int shaderType, final String shaderSource) throws FragmentCompilationException,VertexCompilationException
+    {
+    int shaderHandle = GLES20.glCreateShader(shaderType);
+
+    if (shaderHandle != 0) 
+      {
+      GLES20.glShaderSource(shaderHandle, "#version 100 \n"+ generateShaderHeader(shaderType) + shaderSource);
+      GLES20.glCompileShader(shaderHandle);
+      final int[] compileStatus = new int[1];
+      GLES20.glGetShaderiv(shaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
+
+      if (compileStatus[0] != GLES20.GL_TRUE ) 
+        {
+        GLES20.glDeleteShader(shaderHandle);
+        shaderHandle = 0;
+        }
+      }
+
+    if (shaderHandle == 0)
+      {     
+      String error = GLES20.glGetShaderInfoLog(shaderHandle);
+     
+      switch(shaderType)
+        {
+        case GLES20.GL_VERTEX_SHADER  : throw new VertexCompilationException(error); 
+        case GLES20.GL_FRAGMENT_SHADER: throw new FragmentCompilationException(error);
+        default                       : throw new RuntimeException(error);
+        }
+      }
+
+    return shaderHandle;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int createAndLinkProgram(final int vertexShaderHandle, final int fragmentShaderHandle, final String[] attributes) throws LinkingException
+    {
+    int programHandle = GLES20.glCreateProgram();
+
+    if (programHandle != 0) 
+      {
+      GLES20.glAttachShader(programHandle, vertexShaderHandle);         
+      GLES20.glAttachShader(programHandle, fragmentShaderHandle);
+
+      if (attributes != null)
+        {
+        final int size = attributes.length;
+
+        for (int i = 0; i < size; i++)
+          {
+          GLES20.glBindAttribLocation(programHandle, i, attributes[i]);
+          }                
+        }
+
+      GLES20.glLinkProgram(programHandle);
+
+      final int[] linkStatus = new int[1];
+      GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);
+
+      if (linkStatus[0] != GLES20.GL_TRUE ) 
+        {         
+        String error = GLES20.glGetProgramInfoLog(programHandle);
+        GLES20.glDeleteProgram(programHandle);
+        throw new LinkingException(error);
+        }
+      
+      final int[] numberOfUniforms = new int[1];
+      GLES20.glGetProgramiv(programHandle, GLES20.GL_ACTIVE_UNIFORMS, numberOfUniforms, 0);
+
+      android.util.Log.d(TAG, "number of active uniforms="+numberOfUniforms[0]);
+      }
+ 
+    return programHandle;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private static String generateShaderHeader(final int type)
+    {
+    String header="";
+   
+    switch(type)
+      {
+      case GLES20.GL_VERTEX_SHADER  : header += ("#define NUM_VERTEX "  +EffectListVertex.getMax()+"\n");
+     
+                                      for(EffectNames name: EffectNames.values() )
+                                        {
+                                        if( name.getType()==TYPE_VERT )  
+                                        header += ("#define "+name.name()+" "+name.ordinal()+"\n");  
+                                        }
+                                      break;
+      case GLES20.GL_FRAGMENT_SHADER: header += ("#define NUM_FRAGMENT "+EffectListFragment.getMax()+"\n");
+     
+                                      for(EffectNames name: EffectNames.values() )
+                                        {
+                                        if( name.getType()==TYPE_FRAG )  
+                                        header += ("#define "+name.name()+" "+name.ordinal()+"\n");  
+                                        }
+                                      break;
+     }
+   
+    //Log.d(TAG,""+header);
+    
+    return header;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private static String readTextFileFromRawResource(final GLSurfaceView v, final int resourceId)
+    {
+    final InputStream inputStream = v.getContext().getResources().openRawResource(resourceId);
+    final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
+    final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
+ 
+    String nextLine;
+    final StringBuilder body = new StringBuilder();
+ 
+    try
+      {
+      while ((nextLine = bufferedReader.readLine()) != null)
+        {
+        body.append(nextLine);
+        body.append('\n');
+        }
+      }
+    catch (IOException e)
+      {
+      return null;
+      }
+ 
+    return body.toString();
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+ 
+  private static String getVertexShader(final GLSurfaceView v)
+    {
+    return readTextFileFromRawResource( v, R.raw.main_vertex_shader);
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+ 
+  private static String getFragmentShader(final GLSurfaceView v)
+    {
+    return readTextFileFromRawResource( v, R.raw.main_fragment_shader);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Public API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets Vertical Field of View angle. This changes the Projection Matrix.  
+ *   
+ * @param fov Vertical Field Of View angle, in degrees. If T is the middle of the top edge of the 
+ *            screen, E is the eye point, and B is the middle of the bottom edge of the screen, then 
+ *            fov = angle(TEB)
+ */
+  public static void setFov(float fov)
+    {
+    mFOV = fov;
+   
+    if( mProjection.width>0 && mProjection.height>0 )
+      mProjection.onSurfaceChanged( (int)mProjection.width, (int)mProjection.height);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When OpenGL context gets created, you need to call this method so that the library can initialise its internal data structures.
+ * I.e. best called from GLSurfaceView.onSurfaceCreated().
+ * <p>
+ * Compiles the vertex and fragment shaders, establishes the addresses of all uniforms, and initialises all Bitmaps that have already
+ * been created.
+ *   
+ * @param v The Surface that just got created.
+ * @throws FragmentCompilationException
+ * @throws VertexCompilationException
+ * @throws VertexUniformsException
+ * @throws FragmentUniformsException
+ * @throws LinkingException
+ */
+  public static void onSurfaceCreated(GLSurfaceView v) throws FragmentCompilationException,VertexCompilationException,VertexUniformsException,FragmentUniformsException,LinkingException
+    { 
+    mInitialized = true;  
+     
+// String ver;  
+    
+    final String vertexShader   = Distorted.getVertexShader(v);         
+    final String fragmentShader = Distorted.getFragmentShader(v);       
+/*
+    ver = GLES20.glGetString(GLES20.GL_SHADING_LANGUAGE_VERSION);    
+    Log.d(TAG, "GLSL version: "+(ver==null ? "null" : ver) );
+    ver = GLES20.glGetString(GLES20.GL_VERSION);   
+    Log.d(TAG, "GL version: "+(ver==null ? "null" : ver) );
+    ver = GLES20.glGetString(GLES20.GL_VENDOR);    
+    Log.d(TAG, "GL vendor: "+(ver==null ? "null" : ver) );
+    ver = GLES20.glGetString(GLES20.GL_RENDERER);  
+    Log.d(TAG, "GL renderer: "+(ver==null ? "null" : ver) );
+*/
+    //ver = GLES20.glGetString(GLES20.GL_EXTENSIONS);    
+    //Log.d(TAG, "GL extensions: "+(ver==null ? "null" : ver) );
+    
+    sanitizeMaxValues();
+    
+    final int vertexShaderHandle   = compileShader(GLES20.GL_VERTEX_SHADER  , vertexShader  );     
+    final int fragmentShaderHandle = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);     
+      
+    mProgramH = createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle, new String[] {"a_Position",  "a_Color", "a_Normal", "a_TexCoordinate"});                                                            
+      
+    GLES20.glUseProgram(mProgramH);
+    GLES20.glEnable (GLES20.GL_DEPTH_TEST);
+    GLES20.glDepthFunc(GLES20.GL_LEQUAL);
+    GLES20.glEnable(GLES20.GL_BLEND);
+    GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+    
+    mTextureUniformH = GLES20.glGetUniformLocation(mProgramH, "u_Texture");
+    
+    mPositionH       = GLES20.glGetAttribLocation( mProgramH, "a_Position");
+    mColorH          = GLES20.glGetAttribLocation( mProgramH, "a_Color");
+    mNormalH         = GLES20.glGetAttribLocation( mProgramH, "a_Normal"); 
+    mTextureCoordH   = GLES20.glGetAttribLocation( mProgramH, "a_TexCoordinate");
+    
+    EffectListFragment.getUniforms(mProgramH);
+    EffectListVertex.getUniforms(mProgramH);
+    EffectListMatrix.getUniforms(mProgramH);
+    
+    GLES20.glEnableVertexAttribArray(mPositionH);        
+    GLES20.glEnableVertexAttribArray(mColorH);
+    GLES20.glEnableVertexAttribArray(mNormalH);
+    GLES20.glEnableVertexAttribArray(mTextureCoordH);
+   
+    DistortedObjectList.reset();
+    DistortedNode.reset();
+    EffectMessageSender.startSending();
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this when the physical size of the surface we are rendering to changes.
+ * I.e. must be called from GLSurfaceView.onSurfaceChanged()  
+ *   
+ * @param surfaceWidth  new width of the surface.
+ * @param surfaceHeight new height of the surface.
+ */
+  public static void onSurfaceChanged(int surfaceWidth, int surfaceHeight)
+    {
+    mProjection.onSurfaceChanged(surfaceWidth, surfaceHeight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can release its internal data structures.
+ * Must be called from Activity.onDestroy(). 
+ */
+  public static void onDestroy()
+    {
+    DistortedObjectList.release();
+    
+    EffectListVertex.reset();
+    EffectListFragment.reset();
+    EffectListMatrix.reset();
+    EffectMessageSender.stopSending();
+   
+    mInitialized = false;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the true if onSurfaceCreated has been called already, and thus if the Library's is ready
+ * to accept effect requests.
+ * 
+ * @return <code>true</code> if the Library is ready for action, <code>false</code> otherwise.
+ */
+  public static boolean isInitialized()
+    {
+    return mInitialized;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the maximum number of Matrix effects.
+ *    
+ * @return The maximum number of Matrix effects
+ */
+  public static int getMaxMatrix()
+    {
+    return EffectListMatrix.getMax();  
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the maximum number of Vertex effects.
+ *    
+ * @return The maximum number of Vertex effects
+ */  
+  public static int getMaxVertex()
+    {
+    return EffectListVertex.getMax();  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the maximum number of Fragment effects.
+ *    
+ * @return The maximum number of Fragment effects
+ */  
+  public static int getMaxFragment()
+    {
+    return EffectListFragment.getMax();  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the maximum number of Matrix effects that can be applied to a single DistortedBitmap at one time.
+ * This can fail if the value of 'max' is outside permitted range. 
+ * 
+ * @param max new maximum number of simultaneous Matrix Effects. Has to be a non-negative number not greater
+ *            than Byte.MAX_VALUE 
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+  public static boolean setMaxMatrix(int max)
+    {
+    return EffectListMatrix.setMax(max);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the maximum number of Vertex effects that can be applied to a single DistortedBitmap at one time.
+ * This can fail if:
+ * <ul>
+ * <li>the value of 'max' is outside permitted range
+ * <li>We try to increase the value of 'max' when it is too late to do so already. It needs to be called 
+ *     before the Vertex Shader gets compiled, i.e. before the call to {@link #onSurfaceCreated}. After this
+ *     time only decreasing the value of 'max' is permitted.  
+ * </ul>
+ * 
+ * @param max new maximum number of simultaneous Vertex Effects. Has to be a non-negative number not greater
+ *            than Byte.MAX_VALUE 
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+  public static boolean setMaxVertex(int max)
+    {
+    return EffectListVertex.setMax(max);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the maximum number of Fragment effects that can be applied to a single DistortedBitmap at one time.
+ * This can fail if:
+ * <ul>
+ * <li>the value of 'max' is outside permitted range
+ * <li>We try to increase the value of 'max' when it is too late to do so already. It needs to be called 
+ *     before the Fragment Shader gets compiled, i.e. before the call to {@link #onSurfaceCreated}. After this
+ *     time only decreasing the value of 'max' is permitted.  
+ * </ul>
+ * 
+ * @param max new maximum number of simultaneous Fragment Effects. Has to be a non-negative number not greater
+ *            than Byte.MAX_VALUE 
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+  public static boolean setMaxFragment(int max)
+    {
+    return EffectListFragment.setMax(max);  
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//end of file  
+}
diff --git a/src/main/java/org/distorted/library/DistortedBitmap.java b/src/main/java/org/distorted/library/DistortedBitmap.java
new file mode 100644
index 0000000..b122480
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedBitmap.java
@@ -0,0 +1,125 @@
+package org.distorted.library;
+
+import android.graphics.Bitmap;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Wrapper around the standard Android Bitmap class to which one can apply graphical effects.
+ * <p>
+ * General idea is as follows:
+ * <ul>
+ * <li> Create an instance of DistortedBitmap
+ * <li> Paint something onto the Bitmap that's backing it up
+ * <li> Apply some effects
+ * <li> Draw it!
+ * </ul>
+ * <p>
+ * The effects we can apply fall into three general categories:
+ * <ul>
+ * <li> Matrix Effects, i.e. ones that change the Bitmap's ModelView Matrix (moves, scales, rotations)
+ * <li> Vertex Effects, i.e. effects that are implemented in the Vertex Shader. Those typically change
+ *      the shape of (some sub-Region of) the Bitmap in some way (deforms, distortions, sinks)
+ * <li> Fragment Effects, i.e. effects that change (some of) the pixels of the Bitmap (transparency, macroblock)
+ * </ul>
+ * <p>
+ * 
+ */
+public class DistortedBitmap extends DistortedObject
+   {
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Default constructor: creates a DistortedBitmap (width,height) pixels in size, with the distortion
+ * grid of size 'size' and does not fill it up with any Bitmap data just yet.
+ * <p>
+ * Distortion grid is a grid of rectangles the Bitmap is split to. The vertices of this grid are then 
+ * moved around by the Vertex Shader to create various Vertex Effects.
+ * <p>
+ * Size parameter describes the horizontal size, i.e. the number of rectangles the top (or bottom) edge
+ * is split to. So when size=1, the whole Bitmap is just one giant rectangle. When size=10, the Bitmap
+ * is split into a grid of 10x10 rectangles.
+ * <p>
+ * The higher the size, the better Vertex Effects look; on the other hand too high size will slow things 
+ * down.  
+ *       
+ * @param width  width of the DistortedBitmap, in pixels.
+ * @param height height of the DistortedBitmap, in pixels.
+ * @param size Horizontal size of the distortion grid. 2<=size&lt;256.
+ */
+   public DistortedBitmap(int width, int height, int gridSize)
+     {     
+     int xsize = gridSize;
+     int ysize = xsize*height/width;
+     
+     if( xsize<2   ) xsize=  2;
+     if( xsize>256 ) xsize=256;
+     if( ysize<2   ) ysize=  2;
+     if( ysize>256 ) ysize=256;
+     
+     mSizeX= width;
+     mSizeY= height;
+     mSizeZ= 1;     
+     mGrid = new GridBitmap(xsize,ysize);
+     initializeData(gridSize);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////   
+/**
+ * Creates a DistortedBitmap and immediately fills it up with Bitmap data.
+ * The dimensions of the created DistortedBitmap object are the same like that of the passed Bitmap.
+ *       
+ * @param bmp The android.graphics.Bitmap object to apply effects to and display.
+ * @param size Horizontal size of the distortion grid. 1<=size&lt;256.
+ */
+   public DistortedBitmap(Bitmap bmp, int gridSize)
+     {
+     this(bmp.getWidth(), bmp.getHeight(), gridSize); 
+     setBitmap(bmp);
+     }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor used to create a DistortedBitmap based on various parts of another Bitmap.
+ * <p>
+ * Whatever we do not clone gets created just like in the default constructor.
+ *    
+ * @param db    Source DistortedBitmap to create our object from
+ * @param flags A bitmask of values specifying what to copy.
+ *              For example, CLONE_BITMAP | CLONE_MATRIX.
+ */
+   public DistortedBitmap(DistortedBitmap db, int flags)
+     {
+     initializeEffectLists(db,flags);  
+      
+     mID = DistortedObjectList.add(this);
+        
+     mSizeX = db.mSizeX;
+     mSizeY = db.mSizeY;
+     mSizeZ = db.mSizeZ;
+     mSize  = db.mSize;
+     mGrid  = db.mGrid;
+       
+     if( (flags & Distorted.CLONE_BITMAP) != 0 ) 
+       {
+       mTextureDataH = db.mTextureDataH;
+ 	    mBmp          = db.mBmp;
+ 	    mBitmapSet    = db.mBitmapSet;
+ 	    }
+     else
+       {
+       mTextureDataH   = new int[1];
+       mTextureDataH[0]= 0;
+       mBitmapSet      = new boolean[1];
+       mBitmapSet[0]   = false;
+       mBmp            = new Bitmap[1];
+       mBmp[0]         = null;
+       
+       if( Distorted.isInitialized() ) resetTexture();
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+}
+    
diff --git a/src/main/java/org/distorted/library/DistortedCubes.java b/src/main/java/org/distorted/library/DistortedCubes.java
new file mode 100644
index 0000000..62c82ac
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedCubes.java
@@ -0,0 +1,138 @@
+package org.distorted.library;
+
+import android.graphics.Bitmap;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Instance of this class represents a connected, flat set of cubes optionally textured as a whole.
+ * (a subset of a NxMx1 cuboid build with 1x1x1 cubes, i.e. the MxNx1 cuboid with arbitrary cubes missing)
+ * <p>
+ * General idea is as follows:
+ * <ul>
+ * <li> Create an instance of this class
+ * <li> Optionally texture it
+ * <li> Apply some effects
+ * <li> Draw it!
+ * </ul>
+ * <p>
+ * The effects we can apply fall into three general categories:
+ * <ul>
+ * <li> Matrix Effects, i.e. ones that change the Cuboid's ModelView Matrix (moves, scales, rotations)
+ * <li> Vertex Effects, i.e. effects that are implemented in the Vertex Shader. Those typically change
+ *      the shape of (some sub-Region of) the Cuboid in some way (deforms, distortions, sinks)
+ * <li> Fragment Effects, i.e. effects that change (some of) the pixels of the Texture (transparency, macroblock)
+ * </ul>
+ * <p>
+ * 
+ */
+public class DistortedCubes extends DistortedObject
+   {
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+/////////////////////////////////////////////////////////////////////////////////////////////////// 
+ 
+/**
+ * Creates internal memory representation of a cuboid subset.
+ * 
+ * @param cols Integer helping to parse the next parameter.
+ * @param desc String describing the subset of a MxNx1 cuboid that we want to create.
+ *             Its MxN characters - all 0 or 1 - decide of appropriate field is taken or not.
+ *             
+ *             For example, (cols=2, desc="111010") describes the following shape:
+ *             
+ *             XX
+ *             X
+ *             X
+ *             
+ *             whereas (cols=2,desc="110001") describes
+ *             
+ *             XX
+ *              
+ *              X
+ *              
+ * @param gridSize size, in pixels, of the single 1x1x1 cube our cuboid is built from
+ *  
+ */   
+   public DistortedCubes(int cols, String desc, int gridSize) 
+     {
+     this(cols,desc,gridSize,false);
+     }
+
+/**
+ * @param frontOnly Only create the front wall or side and back as well?
+ */
+   
+   public DistortedCubes(int cols, String desc, int gridSize, boolean frontOnly) 
+     {
+     int Rs = 0;
+     int Cs = 0;
+     
+     if( cols>0 )
+       {
+       int reallen = desc.length();
+       int len = reallen;
+
+       if( (reallen/cols)*cols != reallen )
+         {
+         len = ((reallen/cols)+1)*cols; 
+         for(int i=reallen; i<len; i++) desc += "0";
+         }
+    
+       if( desc.indexOf("1")>=0 )
+         {
+         Cs = cols;
+         Rs = len/cols;
+         }
+       }
+     
+     mSizeX= gridSize*Cs;
+     mSizeY= gridSize*Rs;
+     mSizeZ= frontOnly ? 0 : gridSize;
+     mGrid = new GridCubes(cols,desc, frontOnly);
+     initializeData(gridSize);
+     }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor used to create a DistortedCubes based on various parts of another object.
+ * <p>
+ * Whatever we do not clone gets created just like in the default constructor.
+ *    
+ * @param dc    Source object to create our object from
+ * @param flags A bitmask of values specifying what to copy.
+ *              For example, CLONE_BITMAP | CLONE_MATRIX.
+ */
+   public DistortedCubes(DistortedCubes dc, int flags)
+     {
+     initializeEffectLists(dc,flags);  
+      
+     mID = DistortedObjectList.add(this);
+        
+     mSizeX = dc.mSizeX;
+     mSizeY = dc.mSizeY;
+     mSizeZ = dc.mSizeZ;
+     mSize  = dc.mSize;
+     mGrid  = dc.mGrid;
+       
+     if( (flags & Distorted.CLONE_BITMAP) != 0 ) 
+       {
+       mTextureDataH = dc.mTextureDataH;
+       mBmp          = dc.mBmp;
+       mBitmapSet    = dc.mBitmapSet;
+       }
+     else
+       {
+       mTextureDataH   = new int[1];
+       mTextureDataH[0]= 0;
+       mBitmapSet      = new boolean[1];
+       mBitmapSet[0]   = false;
+       mBmp            = new Bitmap[1];
+       mBmp[0]         = null;
+       
+       if( Distorted.isInitialized() ) resetTexture(); 
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////   
+   }
diff --git a/src/main/java/org/distorted/library/DistortedNode.java b/src/main/java/org/distorted/library/DistortedNode.java
new file mode 100644
index 0000000..5a8aeca
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedNode.java
@@ -0,0 +1,500 @@
+package org.distorted.library;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.opengl.GLES20;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Class which represents a Node in a Tree of DistortedBitmaps.
+ *  
+ * Having organized a set of DistortedBitmaps into a Tree, we can then render any Node to any Framebuffer.
+ * That recursively renders the Bitmap held in the Node and all its children, along with whatever effects
+ * each one of them has. 
+ */
+public class DistortedNode 
+  {
+  private static final int TEXTURE_FAILED_TO_CREATE = -1;   
+  private static final int TEXTURE_NOT_CREATED_YET  = -2;   
+   
+  private DistortedBitmap mBitmap;
+  private NodeData mData;
+  
+  private DistortedNode mParent;
+  private ArrayList<DistortedNode> mChildren;
+  private int[] mNumChildren;  // ==mChildren.length(), but we only create mChildren if the first one gets added
+  
+  private static HashMap<ArrayList<Long>,NodeData> mMapNodeID = new HashMap<ArrayList<Long>,NodeData>();
+  private static long mNextNodeID =0;
+
+  //////////////////////////////////////////////////////////////////
+  
+  private class NodeData
+    {
+    long ID;  
+    int numPointingNodes;
+    int mFramebufferID;
+    int mTextureID;
+    DistortedProjection mProjection;
+    boolean mRendered;
+    
+  //////////////////////////////////////////////////////////////////
+ 
+   public NodeData(long id)
+     {
+     ID = id;
+     numPointingNodes= 1;
+     mFramebufferID = 0;
+     mTextureID = TEXTURE_NOT_CREATED_YET;
+     mProjection = null;
+     mRendered = false;
+     }
+   
+  //////////////////////////////////////////////////////////////////
+
+    boolean createFBO()
+      {  
+      int[] textureIds = new int[1];
+      GLES20.glGenTextures(1, textureIds, 0);
+      mTextureID = textureIds[0];
+      int[] mFBORenderToTexture = new int[1];
+      
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);
+      GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+      GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
+      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
+      GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, mProjection.width, mProjection.height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
+
+      GLES20.glGenFramebuffers(1, mFBORenderToTexture, 0);
+      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFBORenderToTexture[0]);
+      GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, mTextureID, 0);
+      int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
+    
+      if(status != GLES20.GL_FRAMEBUFFER_COMPLETE)
+        {
+        android.util.Log.e("Node", "failed to create framebuffer, error="+status);  
+        
+        GLES20.glDeleteTextures(1, textureIds, 0);
+        GLES20.glDeleteFramebuffers(1, mFBORenderToTexture, 0);
+        mFramebufferID = 0;
+        mTextureID = TEXTURE_FAILED_TO_CREATE;
+        return false;
+        }
+      
+      mFramebufferID = mFBORenderToTexture[0];
+      
+      return true;
+      }
+   
+  //////////////////////////////////////////////////////////////////
+
+    void deleteFBO()
+      {
+      int[] textureIds = new int[1];
+      int[] mFBORenderToTexture = new int[1];
+     
+      textureIds[0] = mTextureID;
+      mFBORenderToTexture[0] = mFramebufferID;
+      
+      GLES20.glDeleteTextures(1, textureIds, 0);
+      GLES20.glDeleteFramebuffers(1, mFBORenderToTexture, 0);
+      
+      mFramebufferID = 0;
+      mTextureID = TEXTURE_NOT_CREATED_YET;
+      }
+   }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void markRecursive()
+    {
+    mData.mRendered = false;
+   
+    synchronized(this)
+      {
+      for(int i=0; i<mNumChildren[0]; i++) mChildren.get(i).markRecursive();
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private void drawRecursive(long currTime, DistortedProjection dp, int mFBO)
+    {
+    if( mNumChildren[0]<=0 )
+      {
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBitmap.mTextureDataH[0]); 
+      
+      if( mData.mProjection!=null )
+        {
+        mData.deleteFBO();
+        mData.mProjection = null;
+        }
+      }
+    else
+      {
+      if( mData.mRendered==false )
+        {
+        mData.mRendered = true;
+       
+        if( mData.mTextureID==TEXTURE_NOT_CREATED_YET ) mData.createFBO();
+       
+        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mData.mFramebufferID);
+        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+        GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+      
+        if( mBitmap.mBitmapSet[0] )
+          {
+          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBitmap.mTextureDataH[0]);        
+          mBitmap.drawNoEffectsPriv(mData.mProjection);
+          }
+      
+        synchronized(this)
+          {
+          for(int i=0; i<mNumChildren[0]; i++)
+            {
+            mChildren.get(i).drawRecursive(currTime, mData.mProjection, mData.mFramebufferID);
+            }
+          }
+        }
+      
+      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFBO);
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mData.mTextureID);   // this is safe because we must have called createFBO() above before.     
+      }
+    
+    mBitmap.drawPriv(currTime, dp);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// tree isomorphism
+  
+  private void RecomputeNodeID(ArrayList<Long> prev)
+    {
+    ArrayList<Long> curr = generateIDList();
+     
+    if( mParent==null )
+      {
+      adjustNodeData(prev,curr);
+      }
+    else
+      {
+      ArrayList<Long> parentPrev = mParent.generateIDList();
+      adjustNodeData(prev,curr);
+      mParent.RecomputeNodeID(parentPrev);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private ArrayList<Long> generateIDList()
+    {
+    ArrayList<Long> ret = new ArrayList<Long>();
+     
+    ret.add( mNumChildren[0]>0 ? mBitmap.getBitmapID() : mBitmap.getID() );
+    DistortedNode node;
+   
+    for(int i=0; i<mNumChildren[0]; i++)
+      {
+      node = mChildren.get(i);
+      ret.add(node.mData.ID);
+      }
+   
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void adjustNodeData(ArrayList<Long> oldList, ArrayList<Long> newList)
+    {
+    if( mData.numPointingNodes>1 ) mData.numPointingNodes--;
+    else                           mMapNodeID.remove(oldList);  
+   
+    NodeData newData = mMapNodeID.get(newList);
+    
+    if( newData==null )
+      {
+      mData.ID = ++mNextNodeID;  
+      mData.numPointingNodes = 1;
+     
+      if( newList.size()>1 && mData.mProjection==null )
+        {     
+        mData.mProjection = new DistortedProjection(true);
+        mData.mProjection.onSurfaceChanged(mBitmap.getWidth(), mBitmap.getHeight());
+        mData.mFramebufferID = 0;
+        mData.mTextureID = TEXTURE_NOT_CREATED_YET;
+        }
+       
+      mMapNodeID.put(newList, mData);
+      }
+    else
+      {  
+      newData.numPointingNodes++;
+      mData = newData;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+// this will be called on startup and every time OpenGL context has been lost
+// also call this from the constructor if the OpenGL context has been created already.
+    
+  static void reset()
+    {
+    NodeData tmp;   
+     
+    for(ArrayList<Long> key: mMapNodeID.keySet())
+      {
+      tmp = mMapNodeID.get(key);
+          
+      if( tmp.mProjection!=null )
+        {
+    	  tmp.mTextureID = TEXTURE_NOT_CREATED_YET;
+        tmp.mRendered  = false;
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructs new Node of the Tree.
+ *     
+ * @param bmp DistortedBitmap to put into the new Node.
+ */
+  public DistortedNode(DistortedBitmap bmp)
+    {
+    mBitmap = bmp;
+    mParent = null;
+    mChildren = null;
+    mNumChildren = new int[1];
+    mNumChildren[0] = 0;
+   
+    ArrayList<Long> list = new ArrayList<Long>();
+    list.add(bmp.getID());
+      
+    mData = mMapNodeID.get(list);
+   
+    if( mData!=null )
+      {
+      mData.numPointingNodes++;
+      }
+    else
+      {
+      mData = new NodeData(++mNextNodeID);   
+      mMapNodeID.put(list, mData);  
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Copy-constructs new Node of the Tree from another Node.
+ *     
+ * @param node The DistortedNode to copy data from.
+ * @param flags bit field composed of a subset of the following:
+ *        {@link Distorted#CLONE_BITMAP},  {@link Distorted#CLONE_MATRIX}, {@link Distorted#CLONE_VERTEX},
+ *        {@link Distorted#CLONE_FRAGMENT} and {@link Distorted#CLONE_CHILDREN}.
+ *        For example flags = CLONE_BITMAP | CLONE_CHILDREN.
+ */
+  public DistortedNode(DistortedNode node, int flags)
+    {
+    mParent = null;  
+    mBitmap = new DistortedBitmap(node.mBitmap, flags);  
+   
+    if( (flags & Distorted.CLONE_CHILDREN) != 0 ) 
+      {
+      mChildren = node.mChildren;
+      mNumChildren = node.mNumChildren;
+      }
+    else
+      {
+      mChildren = null;
+      mNumChildren = new int[1];
+      mNumChildren[0] = 0;
+      }
+   
+    ArrayList<Long> list = generateIDList();
+   
+    mData = mMapNodeID.get(list);
+   
+    if( mData!=null )
+      {
+      mData.numPointingNodes++;
+      }
+    else
+      {
+      mData = new NodeData(++mNextNodeID);   
+      mMapNodeID.put(list, mData);
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new child to the last position in the list of our Node's children.
+ * 
+ * @param node The new Node to add.
+ * @return <code>true</code> if we successfully added the new child.
+ */
+  public synchronized void attach(DistortedNode node)
+    {
+    ArrayList<Long> prev = generateIDList(); 
+   
+    if( mChildren==null ) mChildren = new ArrayList<DistortedNode>(2);
+     
+    node.mParent = this;
+    mChildren.add(node);
+    mNumChildren[0]++;
+     
+    RecomputeNodeID(prev);
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new child to the last position in the list of our Node's children.
+ * 
+ * @param bmp DistortedBitmap to initialize our child Node with.
+ * @return the newly constructed child Node, or null if we couldn't allocate resources.
+ */
+  public synchronized DistortedNode attach(DistortedBitmap bmp)
+    {
+    ArrayList<Long> prev = generateIDList(); 
+      
+    if( mChildren==null ) mChildren = new ArrayList<DistortedNode>(2);  
+    DistortedNode node = new DistortedNode(bmp);
+    node.mParent = this;
+    mChildren.add(node);
+    mNumChildren[0]++;
+   
+    RecomputeNodeID(prev);
+   
+    return node;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the first occurrence of a specified child from the list of children of our Node.
+ * 
+ * @param node The Node to remove.
+ * @return <code>true</code> if the child was successfully removed.
+ */
+  public synchronized boolean detach(DistortedNode node)
+    {
+    if( mNumChildren[0]>0 )
+      {
+      ArrayList<Long> prev = generateIDList();  
+         
+      if( mChildren.remove(node) )
+        {
+        node.mParent = null;  
+        mNumChildren[0]--;
+     
+        RecomputeNodeID(prev);
+     
+        return true;
+        }
+      }
+   
+    return false;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the first occurrence of a specified child from the list of children of our Node.
+ * 
+ * @param bmp The DistortedBitmap to remove.
+ * @return <code>true</code> if the child was successfully removed.
+ */
+  public synchronized boolean detach(DistortedBitmap bmp)
+    {
+    long id = bmp.getID();
+    DistortedNode node;
+   
+    for(int i=0; i<mNumChildren[0]; i++)
+      {
+      node = mChildren.get(i);
+     
+      if( node.mBitmap.getID()==id )
+        {
+        ArrayList<Long> prev = generateIDList();   
+     
+        node.mParent = null;  
+        mChildren.remove(i);
+        mNumChildren[0]--;
+      
+        RecomputeNodeID(prev);
+      
+        return true;
+        }
+      }
+   
+    return false;
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all children Nodes.
+ */
+  public synchronized void detachAll()
+    {
+    for(int i=0; i<mNumChildren[0]; i++)
+      {
+      mChildren.get(i).mParent = null;
+      }
+   
+    if( mNumChildren[0]>0 )
+      {
+      ArrayList<Long> prev = generateIDList();  
+      
+      mNumChildren[0] = 0;
+      mChildren.clear();
+      RecomputeNodeID(prev);
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Draws the Node, and all its children, to the default framebuffer 0 (i.e. the screen).
+ *   
+ * @param currTime Current time, in milliseconds.
+ */
+  public void draw(long currTime)
+    {  
+    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+    GLES20.glUniform1i(Distorted.mTextureUniformH, 0);  
+    
+    markRecursive();
+    drawRecursive(currTime,Distorted.mProjection,0);
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the DistortedBitmap object that's in the Node.
+ * 
+ * @return The DistortedBitmap contained in the Node.
+ */
+  public DistortedBitmap getBitmap()
+    {
+    return mBitmap;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Debug - print all the Node IDs
+
+  public void debug(int depth)
+    {
+    String tmp="";  
+    int i;
+   
+    for(i=0; i<depth; i++) tmp +="   ";
+    tmp += (""+mData.ID);
+   
+    android.util.Log.e("node", tmp);
+   
+    for(i=0; i<mNumChildren[0]; i++)
+      mChildren.get(i).debug(depth+1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  }
+
diff --git a/src/main/java/org/distorted/library/DistortedObject.java b/src/main/java/org/distorted/library/DistortedObject.java
new file mode 100644
index 0000000..cef1677
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedObject.java
@@ -0,0 +1,2440 @@
+package org.distorted.library;
+
+import android.graphics.Bitmap;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class DistortedObject 
+{ 
+    static final int TYPE_NUM = 3;
+    private static final int TYPE_MASK= (1<<TYPE_NUM)-1;
+    private static float[] mViewMatrix = new float[16];
+   
+    protected EffectListMatrix   mM;
+    protected EffectListFragment mF;
+    protected EffectListVertex   mV;
+
+    protected boolean matrixCloned, vertexCloned, fragmentCloned;
+ 
+    protected GridObject mGrid = null;
+    protected long mID;
+    protected int mSizeX, mSizeY, mSizeZ, mSize; // in screen space
+
+    protected Bitmap[] mBmp= null; // 
+    int[] mTextureDataH;           // have to be shared among all the cloned Objects
+    boolean[] mBitmapSet;          // 
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    protected void initializeData(int size)
+      {
+      mID             = DistortedObjectList.add(this);
+      mSize           = size;
+      mTextureDataH   = new int[1];
+      mTextureDataH[0]= 0;
+      mBmp            = new Bitmap[1];
+      mBmp[0]         = null;
+      mBitmapSet      = new boolean[1];
+      mBitmapSet[0]   = false;
+      
+      initializeEffectLists(this,0);
+      
+      if( Distorted.isInitialized() ) resetTexture();    
+      }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    protected void initializeEffectLists(DistortedObject d, int flags)
+      {
+      if( (flags & Distorted.CLONE_MATRIX) != 0 )
+        {
+        mM = d.mM;
+        matrixCloned = true;
+        } 
+      else
+        {
+        mM = new EffectListMatrix(d);
+        matrixCloned = false;  
+        }
+    
+      if( (flags & Distorted.CLONE_VERTEX) != 0 )
+        {
+        mV = d.mV;
+        vertexCloned = true;
+        } 
+      else
+        {
+        mV = new EffectListVertex(d);
+        vertexCloned = false;  
+        }
+    
+      if( (flags & Distorted.CLONE_FRAGMENT) != 0 )
+        {
+        mF = d.mF;
+        fragmentCloned = true;
+        } 
+      else
+        {
+        mF = new EffectListFragment(d);
+        fragmentCloned = false;   
+        }
+      }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this will be called on startup and every time OpenGL context has been lost
+// also call this from the constructor if the OpenGL context has been created already.
+    
+    void resetTexture()
+      {
+      if( mTextureDataH!=null ) 
+        {
+        if( mTextureDataH[0]==0 ) GLES20.glGenTextures(1, mTextureDataH, 0);
+
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataH[0]);       
+        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR );
+        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR );
+        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE );
+        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE );
+       
+        if( mBmp!=null && mBmp[0]!=null)
+          {
+          GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBmp[0], 0);
+          mBmp[0] = null;
+          }
+        }
+      }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+    void drawPriv(long currTime, DistortedProjection dp)
+      {
+      GLES20.glViewport(0, 0, dp.width, dp.height); 
+      
+      mM.compute(currTime);
+      mM.send(mViewMatrix, dp);
+      
+      mV.compute(currTime);
+      mV.postprocess();
+      mV.send();
+        
+      mF.compute(currTime);
+      mF.postprocess(mViewMatrix);
+      mF.send();
+       
+      mGrid.draw();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+    void drawNoEffectsPriv(DistortedProjection dp)
+      {
+      GLES20.glViewport(0, 0, dp.width, dp.height);
+      mM.sendNoEffects(dp);
+      mV.sendZero();
+      mF.sendZero();
+      mGrid.draw();
+      }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+    void releasePriv()
+      {
+      if( matrixCloned  ==false) mM.abortAll();
+      if( vertexCloned  ==false) mV.abortAll();
+      if( fragmentCloned==false) mF.abortAll();
+
+      mBmp          = null;
+      mGrid         = null;
+      mM            = null;
+      mV            = null;
+      mF            = null;
+      mTextureDataH = null;
+      }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    long getBitmapID()
+      {
+      return mBmp==null ? 0 : mBmp.hashCode();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Draw the DistortedObject to the location specified by current Matrix effects.    
+ *     
+ * @param currTime current time, in milliseconds, as returned by System.currentTimeMillis().
+ *        This gets passed on to Interpolators inside the Effects that are currently applied to the 
+ *        Object.
+ */
+   public void draw(long currTime)
+     {
+     GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+     GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+     GLES20.glUniform1i(Distorted.mTextureUniformH, 0);  
+     GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataH[0]); 
+      
+     drawPriv(currTime, Distorted.mProjection);
+     }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Releases all resources.
+ */
+   public synchronized void release()
+     {
+     releasePriv();  
+     DistortedObjectList.remove(this);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the underlying android.graphics.Bitmap object and uploads it to the GPU. 
+ * <p>
+ * You can only recycle() the passed Bitmap once the OpenGL context gets created (i.e. after call 
+ * to onSurfaceCreated) because only after this point can the Library upload it to the GPU!
+ * 
+ * @param bmp The android.graphics.Bitmap object to apply effects to and display.
+ */
+   
+   public void setBitmap(Bitmap bmp)
+     {
+     mBitmapSet[0] = true; 
+      
+     if( Distorted.isInitialized() )
+       {
+       GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+       GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataH[0]);        
+       GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
+       }
+     else
+       {
+       mBmp[0] = bmp;  
+       }
+     }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds the calling class to the list of Listeners that get notified each time some event happens 
+ * to one of the Effects that are currently applied to the DistortedBitmap.
+ * 
+ * @param el A class implementing the EffectListener interface that wants to get notifications.
+ */
+   public void addEventListener(EffectListener el)
+     {
+     mV.addListener(el);
+     mF.addListener(el);
+     mM.addListener(el);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the calling class from the list of Listeners.
+ * 
+ * @param el A class implementing the EffectListener interface that no longer wants to get notifications.
+ */
+   public void removeEventListener(EffectListener el)
+     {
+     mV.removeListener(el);
+     mF.removeListener(el);
+     mM.removeListener(el);
+     }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the height of the DistortedObject.
+ *    
+ * @return height of the object, in pixels.
+ */
+   public int getWidth()
+     {
+     return mSizeX;   
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the width of the DistortedObject.
+ * 
+ * @return width of the Object, in pixels.
+ */
+    public int getHeight()
+      {
+      return mSizeY;  
+      }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the depth of the DistortedObject.
+ * 
+ * @return depth of the Object, in pixels.
+ */
+    public int getDepth()
+      {
+      return mSizeZ;  
+      }
+        
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns unique ID of this instance.
+ * 
+ * @return ID of the object.
+ */
+    public long getID()
+      {
+      return mID;  
+      }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts all Effects. 
+ */
+    public void abortAllEffects()
+      {
+      mM.abortAll();
+      mV.abortAll();
+      mF.abortAll();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts a subset of Effects.
+ * 
+ * @param mask Bitmask of the types of effects we want to abort, e.g. TYPE_MATR | TYPE_VERT | TYPE_FRAG.
+ */
+    public void abortAllEffects(int mask)
+      {
+      if( (mask & Distorted.TYPE_MATR) != 0 ) mM.abortAll();
+      if( (mask & Distorted.TYPE_VERT) != 0 ) mV.abortAll();
+      if( (mask & Distorted.TYPE_FRAG) != 0 ) mF.abortAll();
+      }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts a single Effect.
+ * 
+ * @param id ID of the Effect we want to abort.
+ * @return <code>true</code> if the Effect was found and successfully aborted.
+ */
+    public boolean abortEffect(long id)
+      {
+      switch( (int)(id&TYPE_MASK) )
+        {
+        case Distorted.TYPE_MATR: return mM.removeByID(id>>TYPE_NUM);
+        case Distorted.TYPE_VERT: return mV.removeByID(id>>TYPE_NUM);
+        case Distorted.TYPE_FRAG: return mF.removeByID(id>>TYPE_NUM);
+        default                 : return false;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Abort all Effects of a given type, for example all rotations.
+ * 
+ * @param effectType one of the constants defined in {@link EffectNames}
+ * @return <code>true</code> if a single Effect of type effectType has been found and aborted. 
+ */
+    public boolean abortEffectType(EffectNames effectType)
+      {
+      switch(effectType.getType())
+        {
+        case Distorted.TYPE_MATR: return mM.removeByType(effectType);
+        case Distorted.TYPE_VERT: return mV.removeByType(effectType);
+        case Distorted.TYPE_FRAG: return mF.removeByType(effectType);
+        default                 : return false;
+        }
+      }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Print some info about a given Effect to Android's standard out. Used for debugging only.
+ * 
+ * @param id Effect ID we want to print info about
+ * @return <code>true</code> if a single Effect of type effectType has been found.
+ */
+    
+    public boolean printEffect(long id)
+      {
+      switch( (int)(id&TYPE_MASK) )
+        {
+        case Distorted.TYPE_MATR: return mM.printByID(id>>TYPE_NUM);
+        case Distorted.TYPE_VERT: return mV.printByID(id>>TYPE_NUM);
+        case Distorted.TYPE_FRAG: return mF.printByID(id>>TYPE_NUM);
+        default                 : return false;
+        }
+      }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Individual effect functions.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Matrix-based effects
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// MOVE
+/**
+ * Moves the Object by a vector that changes in time as interpolated by the Interpolator.
+ * 
+ * @param di a 3-dimensional Interpolator which at any given time will return a Float3D
+ *           representing the current coordinates of the vector we want to move the Object with.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long move(Interpolator3D di)
+    {   
+    return mM.add(EffectNames.MOVE,null,di);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Moves the Bitmap by a vector that smoothly changes from (0,0,0) to (x,y,z).
+ *  
+ * @param x The x-coordinate of the vector we want to move the Object with. 
+ * @param y The y-coordinate of the vector we want to move the Object with.
+ * @param z The z-coordinate of the vector we want to move the Object with.
+ * @param duration The time, in milliseconds, it takes to complete the movement.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long move(float x,float y,float z, int duration)
+    {   
+    Interpolator3D di = new Interpolator3D();  
+    di.setCount(0.5f);
+    di.setDuration(duration);
+    di.add(new Float3D(0.0f,0.0f,0.0f));                             
+    di.add(new Float3D(x,y,z));                        
+
+    return mM.add(EffectNames.MOVE,null,di);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Moves the Bitmap by vector (x,y,z) immediately.
+ *   
+ * @param x The x-coordinate of the vector we want to move the Object with. 
+ * @param y The y-coordinate of the vector we want to move the Object with.
+ * @param z The z-coordinate of the vector we want to move the Object with.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long move(float x,float y,float z)
+    {   
+    return mM.add(EffectNames.MOVE,0.0f,0.0f,0.0f,x,y,z,0.0f);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SCALE
+/**
+ * Scales the Object by factors that change in time as returned by the Interpolator.
+ * 
+ * @param di a 3-dimensional Interpolator which at any given time returns a Float3D
+ *           representing the current x- , y- and z- scale factors.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long scale(Interpolator3D di)
+    {   
+    return mM.add(EffectNames.SCALE,null,di);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Scales the Object by a factor that smoothly changes from (1,1,1) at time 0 to (xscale,yscale,zscale)
+ * after 'duration' milliseconds. 
+ *    
+ * @param xscale After time 'duration' passes, Bitmap's width will get multiplied by xscale; e.g. if 
+ *               xscale=2, after 'duration' milliseconds the Object will become twice broader.
+ * @param yscale factor to scale Object's height with.
+ * @param zscale factor to scale Object's depth with.
+ * @param duration Time, in milliseconds, it takes to interpolate to the full (xscale,yscale,zscale) scaling factors.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long scale(float xscale,float yscale,float zscale, int duration)
+    {   
+    Interpolator3D di = new Interpolator3D();  
+    di.setCount(0.5f);
+    di.setDuration(duration);
+    di.add(new Float3D(1.0f,1.0f,1.0f));                             
+    di.add(new Float3D(xscale,yscale,zscale));                        
+
+    return mM.add(EffectNames.SCALE,null,di);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Immediately scales the Object's width by (xscale,yscale,zscale).   
+ *   
+ * @param xscale Object's width gets multiplied by this factor; e.g. if 
+ *               xscale=2, the Object immediately becomes twice broader.
+ * @param yscale factor to scale Object's height with.
+ * @param zscale factor to scale Object's depth with. 
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long scale(float xscale,float yscale,float zscale)
+    {   
+    return mM.add(EffectNames.SCALE,0.0f,0.0f,0.0f,xscale,yscale,zscale,0.0f);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Convenience function - scale the Object by the same factor in all 3 dimensions.   
+ *   
+ * @param scale all 3 Object's dimensions gets multiplied by this factor; e.g. if 
+ *              scale=2, the Object immediately becomes twice larger.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long scale(float scale)
+    {   
+    return mM.add(EffectNames.SCALE,0.0f,0.0f,0.0f,scale,scale,scale,0.0f);  
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// ROTATE
+/**
+ * Rotates the Object around a (possibly moving) point, with angle and axis that change in time.
+ * 
+ * @param i 3-dimensional Interpolator which at any given time will return the current center of 
+ *          the rotation
+ * @param v 4-dimensional Interpolator which at any given time will return a Float4D
+ *          representing the current rotation in the (angle,axisX,axisY,axisY) form. 
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long rotate(Interpolator3D i, Interpolator4D v)
+    {   
+    return mM.add(EffectNames.ROTATE, i, v);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Rotates the Object around a static point, with angle and axis that change in time.
+ * 
+ * @param p the center of the rotation
+ * @param v 4-dimensional Interpolator which at any given time will return a Float4D
+ *          representing the current rotation in the (angle,axisX,axisY,axisY) form. 
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long rotate(Float3D point, Interpolator4D v)
+    {   
+    return mM.add(EffectNames.ROTATE, point.x, point.y, point.z , v);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Rotates the Object around a static point, with angle that changes in time.
+ * 
+ * @param p the center of the rotation
+ * @param v 1-dimensional Interpolator which at any given time will return the current rotation 
+ *          angle.
+ * @param (axisX, axisY, axisZ) the rotation vector          
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long rotate(Float3D point, Interpolator1D v, float axisX, float axisY, float axisZ)
+    {   
+    return mM.add(EffectNames.ROTATE, point.x, point.y, point.z , v, axisX, axisY, axisZ);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotates the Object around a (possibly moving) point, with angle that changes in time.
+ * Axis of rotation is the vector (0,0,1), i.e. a vector normal to the screen surface.
+ * 
+ * @param i 3-dimensional Interpolator which at any given time will return the current center
+ *          of the rotation.
+ * @param a 1-dimensional Interpolator which returns the current rotation angle.         
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long rotate(Interpolator3D i, Interpolator1D a)
+    {   
+    return mM.add(EffectNames.ROTATE, i, a, 0.0f,0.0f,1.0f);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotates the Object around a constant point, with angle that changes in time.  
+ * Axis of rotation is the vector (0,0,1), i.e. a vector normal to the screen surface.
+ *   
+ * @param p Coordinates of the Point we are rotating around.
+ * @param angle The angle, in degrees, that we want to rotate the Bitmap to.
+ * @param duration Time, in milliseconds, it takes to complete one rotation from 0 to 'angle' degrees.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long rotate(Float3D point, int angle, int duration)
+    {   
+    Interpolator1D di = new Interpolator1D();  
+    di.setCount(0.5f);
+    di.setDuration(duration);
+    di.add(new Float1D(    0));                             
+    di.add(new Float1D(angle));                        
+
+    return mM.add(EffectNames.ROTATE,point.x,point.y,point.z, di, 0.0f,0.0f,1.0f);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotates the Object immediately by 'angle' degrees around point p.   
+ * Axis of rotation is given by the last 3 floats.
+ *   
+ * @param p Coordinates of the Point we are rotating around.
+ * @param angle The angle, in degrees, that we want to rotate the Bitmap to.
+ * @param (axisX,axisY,axisZ) - axis of rotation.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long rotate(Float3D point, float angle, float axisX, float axisY, float axisZ)
+    {   
+    return mM.add(EffectNames.ROTATE, point.x, point.y, point.z, angle, axisX, axisY, axisZ);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotates the Object immediately by 'angle' degrees around point p.   
+ *   
+ * @param p      Coordinates of the Point we are rotating around.
+ * @param angle  The angle, in degrees, that we want to rotate the Bitmap to.
+ * @return       ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long rotate(Float3D point, int angle)
+    {   
+    return mM.add(EffectNames.ROTATE,point.x,point.y,point.z,angle,0.0f,0.0f,1.0f);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// QUATERNION
+/**
+ * Rotates the Object immediately by quaternion (qX,qY,qZ,qW).
+ *   
+ * @param p Coordinates of the Point we are rotating around.
+ * @param (qX,qY,qZ,qW) - the quaternion.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long quaternion(Float3D point, float qX, float qY, float qZ, float qW)
+    {   
+    return mM.add(EffectNames.QUATERNION,point.x,point.y,point.z,qX,qY,qZ,qW);   
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotates the Object by a quaternion that's at the moment returned by the InterpolatorQuat.
+ *   
+ * @param p Coordinates of the Point we are rotating around.
+ * @param iq - Interpolator that's going to, at any given moment, return a quaternion.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long quaternion(Float3D point, InterpolatorQuat iq)
+    {   
+    return mM.add(EffectNames.QUATERNION,point.x,point.y,point.z,iq);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotates the Object around a moving point by a quaternion that's at the moment returned by the InterpolatorQuat.
+ *   
+ * @param i Interpolator that returns the current center of rotation.
+ * @param iq Interpolator that's going to, at any given moment, return a quaternion representing the current rotation.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long quaternion(Interpolator3D i, InterpolatorQuat iq)
+    {   
+    return mM.add(EffectNames.QUATERNION,i,iq);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SHEAR
+/**
+ * Shears the Object. If the Interpolator is 1D, it will shear along the X-axis. 2D Interpolator adds
+ * shearing along the Y-axis, 3D one along Z axis.
+ *
+ * @param p  Center of shearing, i.e. the point which stays unmoved. 
+ * @param di 1- 2- or 3D Interpolator which, at any given point, returns the ordered 1-, 2- or 3-tuple 
+ *           of shear factors.
+ * @return   ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long shear(Float3D point, Interpolator di)
+    {
+    return mM.add(EffectNames.SHEAR, point.x, point.y, point.z, di);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Shears the Object in 3D. Order: first X shearing, then Y, then Z.
+ * 
+ * @param p  Center of shearing, i.e. the point which stays unmoved. 
+ * @param v  ordered 3-tuple (x-degree, y-degree, z-degree) of shearing (tangent of the angle with 
+ *           which the X,Y and Z axis get slanted) 
+ * @return   ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long shear(Float3D point, Float3D vector)
+    {
+    Interpolator3D di = new Interpolator3D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float3D(0.0f,0.0f,0.0f));              
+    di.add(vector);                                                
+        
+    return mM.add(EffectNames.SHEAR, point.x, point.y, point.z, di );  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Fragment-based effects  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// MACROBLOCK
+/**
+ * Creates macroblocks at and around point defined by the Interpolator2D and the Region. 
+ * Size of the macroblocks at any given time is returned by the Interpolator1D.
+ * 
+ * @param a a 1-dimensional Interpolator which, at any given time, returns the size of the macroblocks.
+ * @param r The Region this Effect is limited to.
+ *          Null here means 'apply the effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long macroblock(Interpolator1D a, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.MACROBLOCK, a, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates macroblocks at and around point defined by the Point2D and the Region. 
+ * Size of the macroblocks at any given time is returned by the Interpolator1D.
+ * <p>
+ * The difference between this and the previous method is that here the center of the Effect stays constant.
+ *    
+ * @param a a 1-dimensional Interpolator which, at any given time, returns the size of the macroblocks.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long macroblock(Interpolator1D a, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.MACROBLOCK, a, region, point.x, point.y);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates macroblocks at and around point defined by the Interpolator2D and the Region. 
+ * <p>
+ * The macroblocks change in size; at time 0 there are none, and after 'duration/2' milliseconds 
+ * they are of (pixels X pixels) size; after 'duration' milliseconds there are again none (i.e. their
+ * size is 1X1, i.e. 1 pixel).   
+ * 
+ * @param pixels The maximum size, in pixels, of the Macroblocks we want to see.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long macroblock(int pixels, Float4D region, Interpolator2D i, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D();
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                             
+    di.add(new Float1D(pixels));                        
+
+    return mF.add(EffectNames.MACROBLOCK, di, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates macroblocks at and around point defined by the Point2D and the Region. 
+ * <p>
+ * The macroblocks change in size; at time 0 there are none, and after 'duration/2' milliseconds 
+ * they are of (pixels X pixels) size; after 'duration' milliseconds there are again none (i.e. their
+ * size is 1X1, i.e. 1 pixel).   
+ * <p>
+ * The difference between this and the previous method is that here the center of the Effect stays constant.
+ *    
+ * @param pixels The maximum size, in pixels, of the Macroblocks we want to see.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long macroblock(int pixels, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D();
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                             
+    di.add(new Float1D(pixels));                        
+
+    return mF.add(EffectNames.MACROBLOCK, di, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates macroblocks on the whole Bitmap. 
+ * <p>
+ * The macroblocks change in size; at time 0 there are none, and after 'duration/2' milliseconds 
+ * they are of (pixels X pixels) size; after 'duration' milliseconds there are again none (i.e. their
+ * size is 1X1, i.e. 1 pixel).   
+ * <p>
+ * The difference between this and the previous method is that here there is no masking Region; thus
+ * there is also no center of the Effect. 
+ *    
+ * @param pixels The maximum size, in pixels, of the Macroblocks we want to see.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long macroblock(int pixels, int duration, float count) 
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                            
+    di.add(new Float1D(pixels));                        
+   
+    return mF.add(EffectNames.MACROBLOCK, di, null, 0.0f, 0.0f);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// CHROMA
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ *        
+ * @param a a 1-dimensional Interpolator that returns the level of blend a given pixel will be mixed with 
+ *          the next parameter 'color': pixel = (1-level)*pixel + level*color
+ * @param color The color to mix.         
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long chroma(Interpolator1D t, Float3D color, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.CHROMA, t, color, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param a a 1-dimensional Interpolator that returns the level of blend a given pixel will be mixed with 
+ *          the next parameter 'color': pixel = (1-level)*pixel + level*color
+ * @param color The color to mix.         
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long chroma(Interpolator1D t, Float3D color, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.CHROMA, t, color, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ *        
+ * @param t the level of blend a given pixel will be mixed with the next parameter 'color': 
+ *          pixel = (1-t)*pixel + t*color
+ * @param color The color to mix.       
+ * @param reg The Region this Effect is limited to.
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long chroma(float t, Float3D color, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.CHROMA, t, color, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param t the level of blend a given pixel will be mixed with the next parameter 'color': 
+ *          pixel = (1-t)*pixel + t*color
+ * @param color The color to mix.       
+ * @param reg The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long chroma(float t, Float3D color, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.CHROMA, t, color, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ * <p>
+ * Here the Effect applies to the whole bitmap.
+ *         
+ * @param t the level of blend a given pixel will be mixed with the next parameter 'color': 
+ *          pixel = (1-t)*pixel + t*color
+ * @param color The color to mix.       
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long chroma(float t, Float3D color)
+    {
+    return mF.add(EffectNames.CHROMA, t, color, null, 0, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ * 
+ * See {@link #chroma(Interpolator1D, Float3D, Float4D, Interpolator2D)}
+ */
+  public long smooth_chroma(Interpolator1D t, Float3D color, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.SMOOTH_CHROMA, t, color, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ * 
+ * See {@link #chroma(Interpolator1D, Float3D, Float4D, Float2D)}
+ */
+  public long smooth_chroma(Interpolator1D t, Float3D color, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.SMOOTH_CHROMA, t, color, region, point.x, point.y);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ * 
+ * See {@link #chroma(float, Float3D, Float4D, Interpolator2D)}
+ */
+  public long smooth_chroma(float t, Float3D color, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.SMOOTH_CHROMA, t, color, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ * 
+ * See {@link #chroma(float, Float3D, Float4D, Float2D)}
+ */
+  public long smooth_chroma(float t, Float3D color, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.SMOOTH_CHROMA, t, color, region, point.x, point.y);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change all three of its RGB components.
+ * 
+ * See {@link #chroma(float, Float3D)}
+ */
+  public long smooth_chroma(float t, Float3D color)
+    {
+    return mF.add(EffectNames.SMOOTH_CHROMA, t, color, null, 0, 0);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// ALPHA
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ *        
+ * @param a a 1-dimensional Interpolator that returns the level of transparency we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long alpha(Interpolator1D a, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.ALPHA, a, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param a a 1-dimensional Interpolator that returns the level of transparency we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long alpha(Interpolator1D a, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.ALPHA, a, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ *        
+ * @param alpha Level of Alpha (0<=Alpha<=1) we want to interpolate to.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long alpha(float alpha, Float4D region, Interpolator2D i, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(alpha));                         
+   
+    return mF.add(EffectNames.ALPHA, di, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param alpha Level of Alpha (0<=Alpha<=1) we want to interpolate to.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long alpha(float alpha, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(alpha));                         
+   
+    return mF.add(EffectNames.ALPHA, di, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ * <p>
+ * Here the center of the Effect stays constant and the effect for now change in time.
+ *         
+ * @param alpha Level of Alpha (0<=Alpha<=1) we want to interpolate to.
+ * @param r The Region this Effect is limited to.
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long alpha(float alpha, Float4D region, Float2D point)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(alpha));                         
+   
+    return mF.add(EffectNames.ALPHA, di, region, point.x, point.y);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes the whole Bitmap change its transparency level.
+ * 
+ * @param alpha Level of Alpha (0<=Alpha<=1) we want to interpolate to.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long alpha(float alpha, int duration, float count) 
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                            
+    di.add(new Float1D(alpha));                        
+         
+    return mF.add(EffectNames.ALPHA, di,null, 0.0f, 0.0f);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ * 
+ * See {@link #alpha(Interpolator1D, Float4D, Interpolator2D)}
+ */
+  public long smooth_alpha(Interpolator1D a, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.SMOOTH_ALPHA, a, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ * 
+ * See {@link #alpha(int, Float4D, Float2D, int, float)}
+ */
+  public long smooth_alpha(Interpolator1D a, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.SMOOTH_ALPHA, a, region, point.x, point.y);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ * 
+ * See {@link #alpha(int, Float4D, Interpolator2D, int, float)}
+ */
+  public long smooth_alpha(float alpha, Float4D region, Interpolator2D i, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(alpha));                         
+   
+    return mF.add(EffectNames.SMOOTH_ALPHA, di, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ * 
+ * See {@link #alpha(int, Float4D, Float2D, int, float)}
+ */
+  public long smooth_alpha(float alpha, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(alpha));                         
+   
+    return mF.add(EffectNames.SMOOTH_ALPHA, di, region, point.x, point.y);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its transparency level.
+ * 
+ * See {@link #alpha(int, Float4D, Float2D)}
+ */
+  public long smooth_alpha(float alpha, Float4D region, Float2D point)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(alpha));                         
+   
+    return mF.add(EffectNames.SMOOTH_ALPHA, di, region, point.x, point.y);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// BRIGHTNESS
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ *        
+ * @param a a 1-dimensional Interpolator that returns the level of brightness we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long brightness(Interpolator1D a, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.BRIGHTNESS, a, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param a a 1-dimensional Interpolator that returns the level of brightness we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to.
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long brightness(Interpolator1D a, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.BRIGHTNESS, a, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ *        
+ * @param brightness Level of Brightness (0<=brightness<=infinity) we want to interpolate to.
+ *        1 - level of brightness unchanged, anything less than 1 - 'darken the image', anything more than 1-
+ *        lighten it up. 
+ * @param r The Region this Effect is limited to.
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long brightness(float brightness, Float4D region, Interpolator2D i, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(brightness));                         
+   
+    return mF.add(EffectNames.BRIGHTNESS, di, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param brightness Level of Brightness (0<=brightness<=infinity) we want to interpolate to.
+ *        1 - level of brightness unchanged, anything less than 1 - 'darken the image', anything more than 1-
+ *        lighten it up.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long brightness(float brightness, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(brightness));                         
+   
+    return mF.add(EffectNames.BRIGHTNESS, di, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ * <p>
+ * Here the center of the Effect stays constant and the effect for now change in time.
+ *         
+ * @param brightness Level of Brightness (0<=brightness<=infinity) we want to interpolate to.
+ *        1 - level of brightness unchanged, anything less than 1 - 'darken the image', anything more than 1-
+ *        lighten it up.
+ * @param r The Region this Effect is limited to.
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long brightness(float brightness, Float4D region, Float2D point)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(brightness));                         
+   
+    return mF.add(EffectNames.BRIGHTNESS, di, region, point.x, point.y);
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SMOOTH BRIGHTNESS
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ *        
+ * @param a a 1-dimensional Interpolator that returns the level of brightness we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_brightness(Interpolator1D a, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.SMOOTH_BRIGHTNESS, a, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param a a 1-dimensional Interpolator that returns the level of brightness we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_brightness(Interpolator1D a, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.SMOOTH_BRIGHTNESS, a, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ *        
+ * @param brightness Level of Brightness (0<=brightness<=infinity) we want to interpolate to.
+ *        1 - level of brightness unchanged, anything less than 1 - 'darken the image', anything more than 1-
+ *        lighten it up. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_brightness(float brightness, Float4D region, Interpolator2D i, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(brightness));                         
+   
+    return mF.add(EffectNames.SMOOTH_BRIGHTNESS, di, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param brightness Level of Brightness (0<=brightness<=infinity) we want to interpolate to.
+ *        1 - level of brightness unchanged, anything less than 1 - 'darken the image', anything more than 1-
+ *        lighten it up.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_brightness(float brightness, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(brightness));                         
+   
+    return mF.add(EffectNames.SMOOTH_BRIGHTNESS, di, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its brightness level.
+ * <p>
+ * Here the center of the Effect stays constant and the effect for now change in time.
+ *         
+ * @param brightness Level of Brightness (0<=brightness<=infinity) we want to interpolate to.
+ *        1 - level of brightness unchanged, anything less than 1 - 'darken the image', anything more than 1-
+ *        lighten it up.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_brightness(float brightness, Float4D region, Float2D point)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(brightness));                         
+   
+    return mF.add(EffectNames.SMOOTH_BRIGHTNESS, di, region, point.x, point.y);
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes the whole Bitmap change its brightness level.
+ * 
+ * @param brightness Level of Brightness (0<=brightness<=infinity) we want to interpolate to.
+ *        1 - level of brightness unchanged, anything less than 1 - 'darken the image', anything more than 1-
+ *        lighten it up.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_brightness(float brightness, int duration, float count) 
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                            
+    di.add(new Float1D(brightness));                        
+         
+    return mF.add(EffectNames.SMOOTH_BRIGHTNESS, di,null, 0.0f, 0.0f);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// CONTRAST
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ *        
+ * @param a a 1-dimensional Interpolator that returns the level of contrast we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long contrast(Interpolator1D a, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.CONTRAST, a, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param a a 1-dimensional Interpolator that returns the level of contrast we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to.
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long contrast(Interpolator1D a, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.CONTRAST, a, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ *        
+ * @param contrast Level of contrast (0<=contrast<=infinity) we want to interpolate to.
+ *        1 - level of contrast unchanged, anything less than 1 - reduce contrast, anything more than 1-
+ *        increase the contrast. 
+ * @param r The Region this Effect is limited to.
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long contrast(float contrast, Float4D region, Interpolator2D i, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(contrast));                         
+   
+    return mF.add(EffectNames.CONTRAST, di, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param contrast Level of contrast (0<=contrast<=infinity) we want to interpolate to.
+ *        1 - level of contrast unchanged, anything less than 1 - reduce contrast, anything more than 1-
+ *        increase the contrast. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long contrast(float contrast, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(contrast));                         
+   
+    return mF.add(EffectNames.CONTRAST, di, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ * <p>
+ * Here the center of the Effect stays constant and the effect for now change in time.
+ *         
+ * @param contrast Level of contrast (0<=contrast<=infinity) we want to interpolate to.
+ *        1 - level of contrast unchanged, anything less than 1 - reduce contrast, anything more than 1-
+ *        increase the contrast. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long contrast(float contrast, Float4D region, Float2D point)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(contrast));                         
+   
+    return mF.add(EffectNames.CONTRAST, di, region, point.x, point.y);
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SMOOTH CONTRAST
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ *        
+ * @param a a 1-dimensional Interpolator that returns the level of contrast we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_contrast(Interpolator1D a, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.SMOOTH_CONTRAST, a, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param a a 1-dimensional Interpolator that returns the level of contrast we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_contrast(Interpolator1D a, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.SMOOTH_CONTRAST, a, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ *        
+ * @param contrast Level of contrast (0<=contrast<=infinity) we want to interpolate to.
+ *        1 - level of contrast unchanged, anything less than 1 - reduce contrast, anything more than 1-
+ *        increase the contrast. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_contrast(float contrast, Float4D region, Interpolator2D i, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(contrast));                         
+   
+    return mF.add(EffectNames.SMOOTH_CONTRAST, di, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param contrast Level of contrast (0<=contrast<=infinity) we want to interpolate to.
+ *        1 - level of contrast unchanged, anything less than 1 - reduce contrast, anything more than 1-
+ *        increase the contrast. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_contrast(float contrast, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(contrast));                         
+   
+    return mF.add(EffectNames.SMOOTH_CONTRAST, di, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its contrast level.
+ * <p>
+ * Here the center of the Effect stays constant and the effect for now change in time.
+ *         
+ * @param contrast Level of contrast (0<=contrast<=infinity) we want to interpolate to.
+ *        1 - level of contrast unchanged, anything less than 1 - reduce contrast, anything more than 1-
+ *        increase the contrast. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_contrast(float contrast, Float4D region, Float2D point)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(contrast));                         
+   
+    return mF.add(EffectNames.SMOOTH_CONTRAST, di, region, point.x, point.y);
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes the whole Bitmap change its contrast level.
+ * 
+ * @param contrast Level of contrast (0<=contrast<=infinity) we want to interpolate to.
+ *        1 - level of contrast unchanged, anything less than 1 - reduce contrast, anything more than 1-
+ *        increase the contrast. 
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_contrast(float contrast, int duration, float count) 
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                            
+    di.add(new Float1D(contrast));                        
+         
+    return mF.add(EffectNames.SMOOTH_CONTRAST, di,null, 0.0f, 0.0f);
+    }
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SATURATION
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ *        
+ * @param a a 1-dimensional Interpolator that returns the level of saturation we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long saturation(Interpolator1D a, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.SATURATION, a, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param a a 1-dimensional Interpolator that returns the level of saturation we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long saturation(Interpolator1D a, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.SATURATION, a, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ *        
+ * @param saturation Level of saturation (0<=saturation<=infinity) we want to interpolate to.
+ *        1 - level of saturation unchanged, anything less than 1 - reduce saturation, anything more than 1-
+ *        increase the saturation. 
+ * @param r The Region this Effect is limited to.
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long saturation(float saturation, Float4D region, Interpolator2D i, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(saturation));                         
+   
+    return mF.add(EffectNames.SATURATION, di, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param saturation Level of saturation (0<=saturation<=infinity) we want to interpolate to.
+ *        1 - level of saturation unchanged, anything less than 1 - reduce saturation, anything more than 1-
+ *        increase the saturation. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long saturation(float saturation, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(saturation));                         
+   
+    return mF.add(EffectNames.SATURATION, di, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ * <p>
+ * Here the center of the Effect stays constant and the effect for now change in time.
+ *         
+ * @param saturation Level of saturation (0<=saturation<=infinity) we want to interpolate to.
+ *        1 - level of saturation unchanged, anything less than 1 - reduce saturation, anything more than 1-
+ *        increase the saturation. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long saturation(float saturation, Float4D region, Float2D point)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(saturation));                         
+   
+    return mF.add(EffectNames.SATURATION, di, region, point.x, point.y);
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SMOOTH_SATURATION
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ *        
+ * @param a a 1-dimensional Interpolator that returns the level of saturation we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_saturation(Interpolator1D a, Float4D region, Interpolator2D i)
+    {
+    return mF.add(EffectNames.SMOOTH_SATURATION, a, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param a a 1-dimensional Interpolator that returns the level of saturation we want to have at any given 
+ *          moment.
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_saturation(Interpolator1D a, Float4D region, Float2D point)
+    {
+    return mF.add(EffectNames.SMOOTH_SATURATION, a, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ *        
+ * @param saturation Level of saturation (0<=saturation<=infinity) we want to interpolate to.
+ *        1 - level of saturation unchanged, anything less than 1 - reduce saturation, anything more than 1-
+ *        increase the saturation. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param i 2-dimensional Interpolator which, at any given time, returns a Float2D representing the
+ *          current center of the effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_saturation(float saturation, Float4D region, Interpolator2D i, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(saturation));                         
+   
+    return mF.add(EffectNames.SMOOTH_SATURATION, di, region, i);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ * <p>
+ * Here the center of the Effect stays constant.
+ *         
+ * @param saturation Level of saturation (0<=saturation<=infinity) we want to interpolate to.
+ *        1 - level of saturation unchanged, anything less than 1 - reduce saturation, anything more than 1-
+ *        increase the saturation. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_saturation(float saturation, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(saturation));                         
+   
+    return mF.add(EffectNames.SMOOTH_SATURATION, di, region, point.x, point.y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Bitmap smoothly change its saturation level.
+ * <p>
+ * Here the center of the Effect stays constant and the effect for now change in time.
+ *         
+ * @param saturation Level of saturation (0<=saturation<=infinity) we want to interpolate to.
+ *        1 - level of saturation unchanged, anything less than 1 - reduce saturation, anything more than 1-
+ *        increase the saturation. 
+ * @param r The Region this Effect is limited to. 
+ *          Null here means 'apply the Effect to the whole Bitmap'.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_saturation(float saturation, Float4D region, Float2D point)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(1));                          
+    di.add(new Float1D(saturation));                         
+   
+    return mF.add(EffectNames.SMOOTH_SATURATION, di, region, point.x, point.y);
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes the whole Bitmap change its saturation level.
+ * 
+ * @param saturation Level of saturation (0<=saturation<=infinity) we want to interpolate to.
+ *        1 - level of saturation unchanged, anything less than 1 - reduce saturation, anything more than 1-
+ *        increase the saturation. 
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long smooth_saturation(float saturation, int duration, float count) 
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                            
+    di.add(new Float1D(saturation));                        
+         
+    return mF.add(EffectNames.SMOOTH_SATURATION, di,null, 0.0f, 0.0f);
+    }
+            
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Vertex-based effects  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// DISTORT
+/**
+ * Distort a (possibly changing in time) part of the Bitmap by a (possibly changing in time) vector of force.
+ * 
+ * @param i A 2- or 3-dimensional Interpolator that returns a 2- or 3-dimensional Point which represents
+ *          the vector the Center of the Effect is currently being dragged with.
+ * @param r Region that masks the effect of the Distortion.
+ * @param p A 2-dimensional Interpolator that, at any given time, returns a Point2D representing 
+ *          the Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long distort(Interpolator i, Float4D region, Interpolator2D p)
+    {  
+    return mV.add(EffectNames.DISTORT, i, region, p);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Distort part of the Bitmap by a (possibly changing in time) vector of force.
+ * <p>
+ * Difference between this and the previous method is that here the center of the Effect stays constant.
+ *   
+ * @param i A 2- or 3-dimensional Interpolator that returns a 2- or 3-dimensional Point which represents
+ *          the vector the Center of the Effect is currently being dragged with.
+ * @param r Region that masks the effect of the Distortion.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long distort(Interpolator i, Float4D region, Float2D point)
+    {  
+    return mV.add(EffectNames.DISTORT, i, region, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Distort the whole Bitmap by a (possibly changing in time) vector of force.
+ * 
+ * @param i A 2- or 3-dimensional Interpolator that returns a 2- or 3-dimensional Point which represents
+ *          the vector the Center of the Effect is currently being dragged with.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long distort(Interpolator i, Float2D point)
+    {
+    return mV.add(EffectNames.DISTORT, i, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Distort part of the Bitmap by a vector of force that changes from (0,0,0) to v.
+ * 
+ * @param v The maximum vector of force. 
+ * @param r Region that masks the effect of the Distortion.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long distort(Float3D vector, Float4D region, Float2D point, int duration, float count)
+    {  
+    Interpolator3D di = new Interpolator3D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float3D(0.0f,0.0f,0.0f));               
+    di.add(vector);                                                  
+           
+    return mV.add(EffectNames.DISTORT, di, region, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Distort the whole Bitmap by a vector of force that changes from (0,0,0) to v.
+ * 
+ * @param v The maximum vector of force.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long distort(Float3D vector, Float2D point, int duration, float count)
+    {
+    Interpolator3D di = new Interpolator3D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float3D(0.0f,0.0f,0.0f));               
+    di.add(vector);                                                 
+           
+    return mV.add(EffectNames.DISTORT, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Distort the whole Bitmap by a vector of force that changes from (0,0,0) to v.
+ * <p>
+ * Difference between this and the previous method is that here the vector of force will get interpolated
+ * to the maximum v and the effect will end. We are thus limited to count=0.5.
+ * 
+ * @param v The maximum, final vector of force.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long distort(Float3D vector, Float2D point, int duration)
+    {
+    Interpolator3D di = new Interpolator3D();  
+    di.setCount(0.5f);
+    di.setDuration(duration);
+    di.add(new Float3D(0.0f,0.0f,0.0f));              
+    di.add(vector);                 
+           
+    return mV.add(EffectNames.DISTORT, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Distort the whole Bitmap by a vector of force v.
+ * <p>
+ * Here we apply a constant vector of force.
+ * 
+ * @param v The vector of force.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long distort(Float3D vector, Float2D point )
+    {
+    Interpolator3D di = new Interpolator3D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float3D(0.0f,0.0f,0.0f));              
+    di.add(vector);           
+           
+    return mV.add(EffectNames.DISTORT, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// DEFORM
+/**
+ * Deform the shape of the whole Bitmap with a (possibly changing in time) vector of force applied to 
+ * a (possibly changing in time) point on the Bitmap.
+ *     
+ * @param i A 2-dimensional Interpolator that, at any given time, returns a Point2D representing 
+ *          vector of force that deforms the shapre of the whole Bitmap.
+ * @param p A 2-dimensional Interpolator that, at any given time, returns a Point2D representing 
+ *          the Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long deform(Interpolator i, Interpolator2D point)
+    {  
+    return mV.add(EffectNames.DEFORM, i, null, point);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Deform the shape of the whole Bitmap with a (possibly changing in time) vector of force applied to 
+ * a constant point on the Bitmap.
+ * 
+ * @param i A 2-dimensional Interpolator that, at any given time, returns a Point2D representing 
+ *          vector of force that deforms the shapre of the whole Bitmap.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long deform(Interpolator i, Float2D point)
+    {
+    return mV.add(EffectNames.DEFORM, i, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Deform the shape of the whole Bitmap with a vector of force smoothly changing from (0,0,0) to v 
+ * applied to a constant point on the Bitmap. 
+ * 
+ * @param v Vector of force.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long deform(Float3D vector, Float2D point, int duration, float count)
+    {
+    Interpolator3D di = new Interpolator3D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float3D(0.0f,0.0f,0.0f));               
+    di.add(vector);                
+           
+    return mV.add(EffectNames.DEFORM, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Deform the shape of the whole Bitmap with a vector of force smoothly changing from (0,0,0) to v 
+ * applied to a constant point on the Bitmap. 
+ * <p>
+ * Identical to calling the previous method with count=0.5.
+ * 
+ * @param v Final vector of force.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long deform(Float3D vector, Float2D point, int duration)
+    {
+    Interpolator3D di = new Interpolator3D();  
+    di.setCount(0.5f);
+    di.setDuration(duration);
+    di.add(new Float3D(0.0f,0.0f,0.0f));              
+    di.add(vector);             
+           
+    return mV.add(EffectNames.DEFORM, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Deform the shape of the whole Bitmap with a constant vector of force applied to a constant 
+ * point on the Bitmap. 
+ * 
+ * @param v Vector of force.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long deform(Float3D vector, Float2D point )
+    {
+    Interpolator3D di = new Interpolator3D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float3D(0.0f,0.0f,0.0f));              
+    di.add(vector);            
+           
+    return mV.add(EffectNames.DEFORM, di, null, point.x, point.y);  
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SINK
+/**
+ * Pull all points around the center of the effect towards the center (if degree>=1) or push them 
+ * away from the center (degree<=1)
+ *    
+ * @param di A 1-dimensional Interpolator which, at any given time, returns a Point1D representing
+ *          the current degree of the effect.
+ * @param r Region that masks the effect of the Sink.
+ * @param p A 2-dimensional Interpolator that, at any given time, returns a Point2D representing 
+ *          the Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long sink(Interpolator1D di, Float4D region, Interpolator2D point)
+    {
+    return mV.add(EffectNames.SINK, di, region, point);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Pull all points around the center of the effect towards the center (if degree>=1) or push them 
+ * away from the center (degree<=1).
+ * <p>
+ * Here the Center stays constant.
+ *      
+ * @param di A 1-dimensional Interpolator which, at any given time, returns a Point1D representing
+ *          the current degree of the effect.
+ * @param r Region that masks the effect of the Sink.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long sink(Interpolator1D di, Float4D region, Float2D point)
+    {
+    return mV.add(EffectNames.SINK, di, region, point.x, point.y);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Pull all points around the center of the effect towards the center (if degree>=1) or push them 
+ * away from the center (degree<=1).
+ * <p>
+ * Here we can only interpolate between 1 and degree.
+ * 
+ * @param degree How much to push or pull. 0<=degree<=infinity.
+ * @param r Region that masks the effect of the Sink.
+ * @param p A 2-dimensional Interpolator that, at any given time, returns a Point2D representing 
+ *          the Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long sink(float degree, Float4D region, Interpolator2D p, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                                
+    di.add(new Float1D(degree));                          
+    
+    return mV.add(EffectNames.SINK, di, region, p);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Pull all points around the center of the effect towards the center (if degree>=1) or push them 
+ * away from the center (degree<=1).
+ *   
+ * @param degree How much to push or pull. 0<=degree<=infinity.
+ * @param r Region that masks the effect of the Sink.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long sink(float degree, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                                
+    di.add(new Float1D(degree));                          
+    
+    return mV.add(EffectNames.SINK, di, region, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Pull all points of the Bitmap towards the center of the Effect (if degree>=1) or push them 
+ * away from the center (degree<=1).
+ * 
+ * @param degree How much to push or pull. 0<=degree<=infinity.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long sink(float degree, Float2D point, int duration, float count) 
+    {
+    Interpolator1D di = new Interpolator1D();  
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                                
+    di.add(new Float1D(degree));                          
+         
+    return mV.add(EffectNames.SINK, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Pull all points of the Bitmap towards the center of the Effect (if degree>=1) or push them 
+ * away from the center (degree<=1).
+ * <p>
+ * Equivalent to calling the previous method with count=0.5.
+ * 
+ * @param degree How much to push or pull. 0<=degree<=infinity.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long sink(float degree, Float2D point, int duration) 
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(duration);
+    di.add(new Float1D(1));                               
+    di.add(new Float1D(degree));                      
+        
+    return mV.add(EffectNames.SINK, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Pull all points of the Bitmap towards the center of the Effect (if degree>=1) or push them 
+ * away from the center (degree<=1).
+ * <p>
+ * Equivalent of calling the previous method with duration=0; i.e. we pull immediately.
+ * 
+ * @param degree How much to push or pull. 0<=degree<=infinity.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long sink(float degree, Float2D point) 
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(1));                               
+    di.add(new Float1D(degree));                      
+        
+    return mV.add(EffectNames.SINK, di, null, point.x, point.y);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SWIRL
+/**
+ * Rotate part of the Bitmap around the Center of the Effect by a certain angle (as returned by the
+ * Interpolator). 
+ *   
+ * @param di A 1-dimensional Interpolator which, at any given time, returns a Point1D representing 
+ *           the degree of Swirl.
+ * @param r Region that masks the effect of the Swirl.
+ * @param p A 2-dimensional Interpolator that, at any given time, returns a Point2D representing 
+ *          the Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long swirl(Interpolator1D di, Float4D region, Interpolator2D point)
+    {    
+    return mV.add(EffectNames.SWIRL, di, region, point);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotate part of the Bitmap around the Center of the Effect by a certain angle (as returned by the
+ * Interpolator).
+ * <p>
+ * Here the Center stays constant.
+ *      
+ * @param di A 1-dimensional Interpolator which, at any given time, returns a Point1D representing 
+ *           the degree of Swirl.
+ * @param r Region that masks the effect of the Swirl.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long swirl(Interpolator1D di, Float4D region, Float2D point)
+    {    
+    return mV.add(EffectNames.SWIRL, di, region, point.x, point.y);  
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotate part of the Bitmap around the Center of the Effect by 'degree' angle.
+ *   
+ * @param degree Angle, in degrees, of rotation.
+ * @param r Region that masks the effect of the Swirl.
+ * @param p A 2-dimensional Interpolator that, at any given time, returns a Point2D representing 
+ *          the Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long swirl(int degree, Float4D region, Interpolator2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(0));                                
+    di.add(new Float1D(degree));                          
+    
+    return mV.add(EffectNames.SWIRL, di, region, point);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotate part of the Bitmap around the Center of the Effect by 'degree' angle.
+ * <p>
+ * Here the Center stays constant.
+ *    
+ * @param degree Angle, in degrees, of rotation.
+ * @param r Region that masks the effect of the Swirl.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long swirl(int degree, Float4D region, Float2D point, int duration, float count)
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(0));                                
+    di.add(new Float1D(degree));                          
+    
+    return mV.add(EffectNames.SWIRL, di, region, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotate the whole Bitmap around the Center of the Effect by 'degree' angle.
+ * 
+ * @param degree Angle, in degrees, of rotation.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @param count Controls how many interpolations we want to do. See {@link Interpolator#setCount(float)}
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long swirl(int degree, Float2D point, int duration, float count) 
+    {
+    Interpolator1D di = new Interpolator1D();  
+    di.setCount(count);
+    di.setDuration(duration);
+    di.add(new Float1D(0));                                
+    di.add(new Float1D(degree));                          
+         
+    return mV.add(EffectNames.SWIRL, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotate the whole Bitmap around the Center of the Effect by 'degree' angle.
+ * <p>
+ * Equivalent to calling the previous method with count=0.5.
+ * 
+ * @param degree Angle, in degrees, of rotation.
+ * @param p Center of the Effect.
+ * @param duration Time, in milliseconds, it takes to do one full interpolation.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long swirl(int degree, Float2D point, int duration) 
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(duration);
+    di.add(new Float1D(0));                               
+    di.add(new Float1D(degree));                      
+        
+    return mV.add(EffectNames.SWIRL, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotate the whole Bitmap around the Center of the Effect by 'degree' angle.
+ * <p>
+ * Equivalent to calling the previous method with duration=0.
+ * 
+ * @param degree Angle, in degrees, of rotation.
+ * @param p Center of the Effect.
+ * @return ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long swirl(int degree, Float2D point) 
+    {
+    Interpolator1D di = new Interpolator1D(); 
+    di.setCount(0.5f);
+    di.setDuration(0);
+    di.add(new Float1D(0));                               
+    di.add(new Float1D(degree));                      
+        
+    return mV.add(EffectNames.SWIRL, di, null, point.x, point.y);  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// WAVE
+
+///////////////////////////////////////////////////////////////////////////////////////////////////   
+}
diff --git a/src/main/java/org/distorted/library/DistortedObjectList.java b/src/main/java/org/distorted/library/DistortedObjectList.java
new file mode 100644
index 0000000..ad9d02b
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedObjectList.java
@@ -0,0 +1,62 @@
+package org.distorted.library;
+
+import java.util.HashMap;
+/**
+ * List of all DistortedObjects currently created by the application.
+ * We need to be able to quickly retrieve an Object by its ID, thus a HashMap.
+ */
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+final class DistortedObjectList 
+  {
+  private static long id =0;  
+  private static HashMap<Long,DistortedObject> mBitmaps = new HashMap<Long,DistortedObject>();
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized long add(DistortedObject dBmp)
+    {
+    long ret = id++;  
+    mBitmaps.put(ret,dBmp);
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized void remove(DistortedObject dBmp)
+    {
+    mBitmaps.remove(dBmp);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static DistortedObject get(long id)
+    {
+    return mBitmaps.get(id);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  static synchronized void reset()
+    {
+    for(long id: mBitmaps.keySet()) 
+      {
+      get(id).resetTexture();  
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized void release()
+    {
+    for(long id: mBitmaps.keySet()) 
+      {
+      get(id).releasePriv();  
+      }
+   
+    mBitmaps.clear();
+    id = 0;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  }
diff --git a/src/main/java/org/distorted/library/DistortedProjection.java b/src/main/java/org/distorted/library/DistortedProjection.java
new file mode 100644
index 0000000..db9f478
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedProjection.java
@@ -0,0 +1,65 @@
+package org.distorted.library;
+
+import android.opengl.Matrix;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class DistortedProjection 
+  {
+  int width,height,depth,distance;
+  float[] projectionMatrix;
+  
+  private boolean invert;  // invert top with bottom? We don't do that for the projection to the screen,
+                           // but we need that for the projection to FBOs. (that's because each time we
+                           // render to FBO we invert the texture upside down because its vertex coords
+                           // are purposefully set upside down in DistortedBackground; so each time we
+                           // render through FBO we need to invert it once more to counter this effect)
+  
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public DistortedProjection(boolean inv) 
+   {
+   invert = inv;  
+   projectionMatrix = new float[16];   
+   }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void onSurfaceChanged(int surfaceWidth, int surfaceHeight)
+    {
+    width = surfaceWidth;
+    height= surfaceHeight;  
+    
+    float ratio  = (float) surfaceWidth / surfaceHeight;
+    float left   =-ratio;          //
+    float right  = ratio;          // Create a new perspective projection matrix.
+    float bottom = -1.0f;          //
+    float top    =  1.0f;          // any change to those values will have serious consequences!
+    float near, far;
+   
+    if( Distorted.mFOV>0.0f )  // perspective projection
+      {  
+      near= (float)(top / Math.tan(Distorted.mFOV*Math.PI/360)); 
+      distance = (int)(height*near/(top-bottom));
+      far = 2*distance-near;
+     
+      if( invert ) Matrix.frustumM(projectionMatrix, 0, left, right, top, bottom, near, far);
+      else         Matrix.frustumM(projectionMatrix, 0, left, right, bottom, top, near, far);        
+      }
+    else                      // parallel projection
+      {
+      near= (float)(top / Math.tan(Math.PI/360)); 
+      distance = (int)(height*near/(top-bottom));
+      far = 2*distance-near;
+    
+      if( invert ) Matrix.orthoM(projectionMatrix, 0, -surfaceWidth/2, surfaceWidth/2, surfaceHeight/2,-surfaceHeight/2, near, far);
+      else         Matrix.orthoM(projectionMatrix, 0, -surfaceWidth/2, surfaceWidth/2,-surfaceHeight/2, surfaceHeight/2, near, far);
+      }
+   
+    depth = (int)((far-near)/2);
+    }
+  }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
diff --git a/src/main/java/org/distorted/library/EffectList.java b/src/main/java/org/distorted/library/EffectList.java
new file mode 100644
index 0000000..2d8cbaf
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectList.java
@@ -0,0 +1,297 @@
+package org.distorted.library;
+
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class EffectList 
+  {
+  protected static final int DEFAULT_NUM_EFFECTS = 5;
+  
+  protected static final int MATRIX  =0;
+  protected static final int VERTEX  =1;
+  protected static final int FRAGMENT=2;
+  
+  protected byte mNumEffects;   // number of effects at the moment
+  protected long mTotalEffects; // total number of effects ever created
+  
+  protected int[] mType;
+  protected float[] mUniforms;
+  protected Interpolator[] mInterP;  // center of the effect
+  protected Interpolator[] mInterI;  // all other interpolated values
+  protected long[] mCurrentDuration;
+  protected byte[] mFreeIndexes;
+  protected byte[] mIDIndex;
+  protected long[] mID;
+  
+  protected long mTime=0;
+  protected float mObjHalfX, mObjHalfY, mObjHalfZ;
+  
+  protected static int[] mMax = new int[3];
+  protected int mMaxIndex;
+  protected static boolean mCreated = false;
+ 
+  protected Vector<EffectListener> mListeners =null;
+  protected int mNumListeners=0;  // ==mListeners.length(), but we only create mListeners if the first one gets added
+  protected long mBitmapID;
+  
+  static
+    {
+    mMax[MATRIX]   = DEFAULT_NUM_EFFECTS;
+    mMax[VERTEX]   = DEFAULT_NUM_EFFECTS;
+    mMax[FRAGMENT] = DEFAULT_NUM_EFFECTS;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectList(DistortedObject obj, int numUniforms, int index) 
+    {
+    mNumEffects   = 0;
+    mTotalEffects = 0;
+    mMaxIndex     = index;
+    
+    mObjHalfX = obj.getWidth() /2.0f;
+    mObjHalfY = obj.getHeight()/2.0f;
+    mObjHalfZ = obj.getDepth() /2.0f;
+
+    mBitmapID = obj.getID();
+    
+    if( mMax[mMaxIndex]>0 )
+      {
+      mType            = new int[mMax[mMaxIndex]];
+      mUniforms        = new float[numUniforms*mMax[mMaxIndex]];
+      mInterI          = new Interpolator[mMax[mMaxIndex]];
+      mInterP          = new Interpolator2D[mMax[mMaxIndex]];
+      mCurrentDuration = new long[mMax[mMaxIndex]];
+      mID              = new long[mMax[mMaxIndex]];
+      mIDIndex         = new byte[mMax[mMaxIndex]];
+      mFreeIndexes     = new byte[mMax[mMaxIndex]];
+     
+      for(byte i=0; i<mMax[mMaxIndex]; i++) mFreeIndexes[i] = i;
+      }
+   
+    mCreated = true;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumEffects()
+    {
+    return mNumEffects;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void addListener(EffectListener el)
+    {
+    if( mNumListeners==0 ) mListeners = new Vector<EffectListener>(2,2);  
+   
+    mListeners.add(el);
+    mNumListeners++;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeListener(EffectListener el)
+    {
+    if( mNumListeners>0 )  
+      {
+      mListeners.remove(el);
+      mNumListeners--;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void reset()
+    {
+    mMax[MATRIX]   = DEFAULT_NUM_EFFECTS;
+    mMax[VERTEX]   = DEFAULT_NUM_EFFECTS;
+    mMax[FRAGMENT] = DEFAULT_NUM_EFFECTS;
+   
+    mCreated = false;  
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized boolean removeByID(long id)
+    {
+    int i = getEffectIndex(id);
+   
+    if( i>=0 ) 
+      {
+      remove(i);
+      return true;
+      }
+   
+    return false; 
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized boolean removeByType(EffectNames effect)
+    {
+    boolean ret = false;  
+    int ord = effect.ordinal();  
+     
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mType[i]==ord )
+        {
+        remove(i);
+        ret = true;
+        }
+      }
+   
+    return ret;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  protected synchronized int getEffectIndex(long id)
+    {
+    int index = mIDIndex[(int)(id%mMax[mMaxIndex])];
+    return (index<mNumEffects && mID[index]==id ? index : -1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void abortAll()
+    {
+    for(int i=0; i<mNumEffects; i++ )
+      {
+      mInterI[i] = null;
+      mInterP[i] = null;
+      } 
+   
+    mNumEffects= 0;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this assumes 0<=effect<mNumEffects
+  
+  protected void remove(int effect)
+    {
+    mNumEffects--;     
+    
+    byte removedIndex = (byte)(mID[effect]%mMax[mMaxIndex]);
+    byte removedPosition = mIDIndex[removedIndex];
+    mFreeIndexes[mNumEffects] = removedIndex;
+    
+    long removedID = mID[effect];
+    int removedType= mType[effect];
+    
+    for(int j=0; j<mMax[mMaxIndex]; j++)
+      {
+      if( mIDIndex[j] > removedPosition ) mIDIndex[j]--; 
+      }
+         
+    for(int j=effect; j<mNumEffects; j++ ) 
+      {
+      mType[j]            = mType[j+1];
+      mInterI[j]          = mInterI[j+1];
+      mInterP[j]          = mInterP[j+1];
+      mCurrentDuration[j] = mCurrentDuration[j+1];
+      mID[j]              = mID[j+1];
+    
+      moveEffect(j);
+      }
+   
+    mInterI[mNumEffects] = null;
+    mInterP[mNumEffects] = null;
+   
+    for(int i=0; i<mNumListeners; i++) 
+      EffectMessageSender.newMessage( mListeners.elementAt(i),
+                                      EffectMessage.EFFECT_REMOVED, 
+                                      (removedID<<DistortedObject.TYPE_NUM)+EffectNames.getType(removedType), 
+                                      removedType,
+                                      mBitmapID);  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  protected long addBase(EffectNames eln)
+    {    
+    mType[mNumEffects]  = eln.ordinal();  
+    mCurrentDuration[mNumEffects] = 0;
+    
+    int index = mFreeIndexes[mNumEffects];
+    long id = mTotalEffects*mMax[mMaxIndex] + index;
+    mID[mNumEffects] = id;
+    mIDIndex[index] = mNumEffects;  
+   
+    mNumEffects++; 
+    mTotalEffects++;
+   
+    return (id<<DistortedObject.TYPE_NUM)+eln.getType();
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// used only for debugging
+  
+  protected String printEffects(int max)
+    {
+    long[] indexes = new long[mMax[mMaxIndex]];
+   
+    for(int g=0; g<mMax[mMaxIndex]; g++)
+      {
+      indexes[g] = -1;  
+      }
+   
+    String ret= new String();
+    ret = "(";
+   
+    int f;
+   
+    for(int g=0; g<max; g++) 
+      {
+      f = getEffectIndex(g);
+      if( f>=0 ) indexes[f] = g;
+      }
+   
+    for(int g=0; g<mMax[mMaxIndex]; g++)
+      {
+      ret += (g>0 ? ",":"")+(indexes[g]>=0 ? indexes[g] : " ");   
+      }
+   
+    ret += ")";
+   
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only used for debugging
+  
+  protected boolean printByID(long id)
+    {
+    int index = getEffectIndex(id);
+   
+    if( index>=0 ) 
+      {
+      boolean interI = mInterI[index]==null; 
+      boolean interP = mInterP[index]==null; 
+      
+      android.util.Log.e("EffectList", "numEffects="+mNumEffects+" effect id="+id+" index="+index+" duration="+mCurrentDuration[index]+" interI null="+interI+" interP null="+interP);
+      
+      if( interI==false )
+        {
+        android.util.Log.e("EffectList","interI: "+mInterI[index].print());  
+        }
+      if( interP==false )
+        {
+        android.util.Log.e("EffectList","interP: "+mInterP[index].print());  
+        }
+     
+      return true;
+      }
+   
+    android.util.Log.e("EffectList", "effect id="+id+" not found");
+
+    return false;  
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract void moveEffect(int index);
+  }
+///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectListFragment.java b/src/main/java/org/distorted/library/EffectListFragment.java
new file mode 100644
index 0000000..4f72ea7
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectListFragment.java
@@ -0,0 +1,296 @@
+package org.distorted.library;
+
+import android.opengl.GLES20;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectListFragment extends EffectList
+  {
+  private static final int NUM_UNIFORMS = 9; 
+  private float[] mBuf;
+  private static int mNumEffectsH;
+  private static int mTypeH;
+  private static int mUniformsH;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectListFragment(DistortedObject bmp) 
+    { 
+    super(bmp,NUM_UNIFORMS,FRAGMENT);
+   
+    if( mMax[FRAGMENT]>0 )
+      {
+      mBuf= new float[4*mMax[FRAGMENT]];
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only max Byte.MAX_VALUE concurrent effects per bitmap.
+// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
+  
+  static boolean setMax(int m)
+    {
+    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[FRAGMENT] ) 
+      {
+           if( m<0              ) m = 0;
+      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
+      
+      mMax[FRAGMENT] = m;
+      return true;
+      }
+   
+    return false;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static int getMax()
+    {
+    return mMax[FRAGMENT];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mNumEffectsH= GLES20.glGetUniformLocation( mProgramH, "fNumEffects");
+    mTypeH      = GLES20.glGetUniformLocation( mProgramH, "fType");
+    mUniformsH  = GLES20.glGetUniformLocation( mProgramH, "fUniforms");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void compute(long currTime) 
+    { 
+    if( currTime==mTime ) return;
+    if( mTime==0 ) mTime = currTime;
+    long step = (currTime-mTime);
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mInterI[i]==null ) continue;    
+      
+      if( mInterP[i]!=null ) mInterP[i].interpolateMain(mBuf, 4*i, mCurrentDuration[i]);
+        
+      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i, mCurrentDuration[i], step) )      
+        {
+        for(int j=0; j<mNumListeners; j++)   
+          EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                          EffectMessage.EFFECT_FINISHED, 
+                                          (mID[i]<<DistortedObject.TYPE_NUM)+Distorted.TYPE_FRAG, 
+                                          mType[i], 
+                                          mBitmapID); 
+      
+        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i) )
+          {
+          remove(i);
+          i--;
+          continue;
+          }
+        }
+           
+      mCurrentDuration[i] += step;
+      }
+   
+    mTime = currTime;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected void moveEffect(int index)
+    {
+    mBuf[4*index  ] = mBuf[4*index+4];
+    mBuf[4*index+1] = mBuf[4*index+5];
+    mBuf[4*index+2] = mBuf[4*index+6];
+    mBuf[4*index+3] = mBuf[4*index+7];
+              
+    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
+    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
+    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];  
+    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void send() 
+    {
+    GLES20.glUniform1i( mNumEffectsH, mNumEffects);
+      
+    if( mNumEffects>0 )
+      {     
+      GLES20.glUniform1iv( mTypeH    ,  mNumEffects, mType    ,0);
+      GLES20.glUniform3fv( mUniformsH,3*mNumEffects, mUniforms,0);
+      }  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void sendZero() 
+    {
+    GLES20.glUniform1i( mNumEffectsH, 0);
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Do various post-processing on already computed effects.
+// 1) move all Points and scale all Region radii by a ModelView matrix
+// 2) in case of macroblock, pre-compute some values so that we don't have to do it in the fragment shader.
+  
+  void postprocess(float[] MVmatrix)
+    {
+    if( mNumEffects>0 )
+      {
+      float tx,ty;   
+      float w = (float)Math.sqrt(MVmatrix[0]*MVmatrix[0] + MVmatrix[4]*MVmatrix[4]);  // The scale factors are the lengths of the first 3 vectors of the upper-left 3x3 submatrix; here
+      float h = (float)Math.sqrt(MVmatrix[1]*MVmatrix[1] + MVmatrix[5]*MVmatrix[5]);  // m[2]=m[6]=m[8]=m[9]=0 so it is really only the upper-left 2x2 matrix.
+   
+      for(int i=0; i<mNumEffects; i++)
+        {   
+        tx = mBuf[4*i  ]-mObjHalfX; // we have to invert y and move everything by (half of bmp width, half of bmp height)
+        ty =-mBuf[4*i+1]+mObjHalfY; //
+      
+        mUniforms[NUM_UNIFORMS*i+4] = w*mBuf[4*i+2];                                  // in fragment shader rx and ry radii are the second and third values of the Region thus 9*i+4 and 9*i+5
+        mUniforms[NUM_UNIFORMS*i+5] = h*mBuf[4*i+3];                                  // 
+     // mUniforms[NUM_UNIFORMS*i+6] =                                                 // this value is not used in Fragment Shader   
+        mUniforms[NUM_UNIFORMS*i+7] = MVmatrix[0]*tx + MVmatrix[4]*ty + MVmatrix[12]; // multiply the ModelView matrix times the (x,y,0,1) point, i.e. the (x,y) point on the surface of the bitmap.
+        mUniforms[NUM_UNIFORMS*i+8] = MVmatrix[1]*tx + MVmatrix[5]*ty + MVmatrix[13]; //  
+        
+        if( mType[i]==EffectNames.MACROBLOCK.ordinal() ) // fill up the .y and .z components of the Interpolated values already to avoid having to compute this in the fragment shader
+          {
+          mUniforms[NUM_UNIFORMS*i+1] = 2.0f*mObjHalfX/mUniforms[NUM_UNIFORMS*i];
+          mUniforms[NUM_UNIFORMS*i+2] = 2.0f*mObjHalfY/mUniforms[NUM_UNIFORMS*i];
+          }
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+       
+  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, Interpolator2D point)
+    {
+    if( mMax[FRAGMENT]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects); 
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = point;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+   
+      return addBase(eln); 
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, float x, float y)
+    {
+    if( mMax[FRAGMENT]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects);    
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = null;
+      mBuf[4*mNumEffects  ] = x;
+      mBuf[4*mNumEffects+1] = y;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+   
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+       
+  synchronized long add(EffectNames eln, Interpolator1D inter, Float3D c, Float4D region, Interpolator2D point)
+    {
+    if( mMax[FRAGMENT]>mNumEffects )
+      {
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = point;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+   
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
+     
+      return addBase(eln); 
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(EffectNames eln, Interpolator1D inter, Float3D c, Float4D region, float x, float y)
+    {
+    if( mMax[FRAGMENT]>mNumEffects )
+      {
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = null;
+      mBuf[4*mNumEffects  ] = x;
+      mBuf[4*mNumEffects+1] = y;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
+   
+      return addBase(eln);
+      }
+       
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+       
+  synchronized long add(EffectNames eln, float t, Float3D c, Float4D region, Interpolator2D point)
+    {
+    if( mMax[FRAGMENT]>mNumEffects )
+      {
+      mInterI[mNumEffects] = null;
+      mInterP[mNumEffects] = point;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+   
+      mUniforms[NUM_UNIFORMS*mNumEffects+0] = t;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
+     
+      return addBase(eln); 
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(EffectNames eln, float t, Float3D c, Float4D region, float x, float y)
+    {
+    if( mMax[FRAGMENT]>mNumEffects )
+      {
+      mInterI[mNumEffects] = null;
+      mInterP[mNumEffects] = null;
+      mBuf[4*mNumEffects  ] = x;
+      mBuf[4*mNumEffects+1] = y;
+      mBuf[4*mNumEffects+2] = (region==null || region.z<=0.0f) ? 1000*mObjHalfX : region.z;
+      mBuf[4*mNumEffects+3] = (region==null || region.w<=0.0f) ? 1000*mObjHalfY : region.w;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+0] = t;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = c.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = c.y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = c.z;
+   
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of FragmentEffect   
+  }
diff --git a/src/main/java/org/distorted/library/EffectListMatrix.java b/src/main/java/org/distorted/library/EffectListMatrix.java
new file mode 100644
index 0000000..8f1eec3
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectListMatrix.java
@@ -0,0 +1,345 @@
+package org.distorted.library;
+
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectListMatrix extends EffectList
+  {   
+  private static final int NUM_UNIFORMS = 7; 
+  private static float[] mMVPMatrix= new float[16];
+  private static float[] mTmpMatrix= new float[16];
+  
+  private static int mBmpDH;      // This is a handle to half a bitmap dimensions
+  private static int mDepthH;     // Handle to the max Depth, i.e (farplane-nearplane)/2
+  private static int mMVPMatrixH; // pass in the transformation matrix
+  private static int mMVMatrixH;  // pass in the modelview matrix.
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectListMatrix(DistortedObject bmp) 
+    { 
+    super(bmp,NUM_UNIFORMS,MATRIX);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void multiplyByQuat(float[] matrix, float X, float Y, float Z, float W)
+    {
+    float xx= X * X;
+    float xy= X * Y;
+    float xz= X * Z;
+    float xw= X * W;
+    float yy= Y * Y;
+    float yz= Y * Z;
+    float yw= Y * W;
+    float zz= Z * Z;
+    float zw= Z * W;
+
+    mTmpMatrix[0]  = 1 - 2 * ( yy + zz );
+    mTmpMatrix[1]  =     2 * ( xy - zw );
+    mTmpMatrix[2]  =     2 * ( xz + yw );
+    mTmpMatrix[4]  =     2 * ( xy + zw );
+    mTmpMatrix[5]  = 1 - 2 * ( xx + zz );
+    mTmpMatrix[6]  =     2 * ( yz - xw );
+    mTmpMatrix[8]  =     2 * ( xz - yw );
+    mTmpMatrix[9]  =     2 * ( yz + xw );
+    mTmpMatrix[10] = 1 - 2 * ( xx + yy );
+    mTmpMatrix[3]  = mTmpMatrix[7] = mTmpMatrix[11] = mTmpMatrix[12] = mTmpMatrix[13] = mTmpMatrix[14] = 0;
+    mTmpMatrix[15] = 1;
+    
+    Matrix.multiplyMM(mMVPMatrix, 0, matrix, 0, mTmpMatrix, 0);  
+    for(int j=0; j<16; j++) matrix[j] = mMVPMatrix[j];   
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only max Byte.MAX_VALUE concurrent effects per bitmap.
+// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
+  
+  static boolean setMax(int m)
+    {
+    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[MATRIX] ) 
+      {
+           if( m<0              ) m = 0;
+      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
+      
+      mMax[MATRIX] = m;
+      return true;
+      }
+   
+    return false;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static int getMax()
+    {
+    return mMax[MATRIX];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mBmpDH     = GLES20.glGetUniformLocation(mProgramH, "u_bmpD");
+    mDepthH    = GLES20.glGetUniformLocation(mProgramH, "u_Depth");
+    mMVPMatrixH= GLES20.glGetUniformLocation(mProgramH, "u_MVPMatrix");
+    mMVMatrixH = GLES20.glGetUniformLocation(mProgramH, "u_MVMatrix"); 
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void compute(long currTime) 
+    {
+    if( currTime==mTime ) return;
+    if( mTime==0 ) mTime = currTime;
+    long step = (currTime-mTime);
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mInterI[i]==null ) continue;    
+           
+      if( mInterP[i]!=null ) 
+        {
+        mInterP[i].interpolateMain(mUniforms, NUM_UNIFORMS*i, mCurrentDuration[i]);
+        }
+        
+      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i+3, mCurrentDuration[i], step) )      
+        {   
+        for(int j=0; j<mNumListeners; j++)   
+          EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                          EffectMessage.EFFECT_FINISHED, 
+                                         (mID[i]<<DistortedObject.TYPE_NUM)+Distorted.TYPE_MATR, 
+                                          mType[i], 
+                                          mBitmapID); 
+       
+        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i+3) )
+          {  
+          remove(i);
+          i--;
+          continue;
+          }
+        }
+    
+      mCurrentDuration[i] += step;
+      }
+     
+    mTime = currTime;  
+    }  
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected void moveEffect(int index)
+    {
+    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
+    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
+    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];
+    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];
+    mUniforms[NUM_UNIFORMS*index+4] = mUniforms[NUM_UNIFORMS*(index+1)+4];
+    mUniforms[NUM_UNIFORMS*index+5] = mUniforms[NUM_UNIFORMS*(index+1)+5];
+    mUniforms[NUM_UNIFORMS*index+6] = mUniforms[NUM_UNIFORMS*(index+1)+6];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// here construct the ModelView Matrix
+
+  synchronized void send(float[] viewMatrix, DistortedProjection dp) 
+    {
+    Matrix.setIdentityM(viewMatrix, 0);
+    Matrix.translateM(viewMatrix, 0, -dp.width/2, dp.height/2, -dp.distance);
+    
+    float x,y,z, sx,sy,sz=1.0f;
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if (mType[i] == EffectNames.ROTATE.ordinal() )
+        {
+        x = mUniforms[NUM_UNIFORMS*i  ];
+        y = mUniforms[NUM_UNIFORMS*i+1];
+        z = mUniforms[NUM_UNIFORMS*i+2];
+     
+        Matrix.translateM(viewMatrix, 0, x,-y, z); 
+        Matrix.rotateM( viewMatrix, 0, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);  
+        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
+        }
+      else if(mType[i] == EffectNames.QUATERNION.ordinal() )
+        {
+        x = mUniforms[NUM_UNIFORMS*i  ];
+        y = mUniforms[NUM_UNIFORMS*i+1];
+        z = mUniforms[NUM_UNIFORMS*i+2];
+     	
+        Matrix.translateM(viewMatrix, 0, x,-y, z); 
+        multiplyByQuat(viewMatrix, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);
+        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
+        }
+      else if(mType[i] == EffectNames.MOVE.ordinal() )
+        {
+        sx = mUniforms[NUM_UNIFORMS*i+3];   
+        sy = mUniforms[NUM_UNIFORMS*i+4];   
+        sz = mUniforms[NUM_UNIFORMS*i+5];   
+        
+        Matrix.translateM(viewMatrix, 0, sx,-sy, sz);   
+        }
+      else if(mType[i] == EffectNames.SCALE.ordinal() )
+        {
+        sx = mUniforms[NUM_UNIFORMS*i+3];   
+        sy = mUniforms[NUM_UNIFORMS*i+4];   
+        sz = mUniforms[NUM_UNIFORMS*i+5];   
+
+        Matrix.scaleM(viewMatrix, 0, sx, sy, sz);  
+        }
+      else if(mType[i] == EffectNames.SHEAR.ordinal() )
+        {
+        x  = mUniforms[NUM_UNIFORMS*i  ];
+        y  = mUniforms[NUM_UNIFORMS*i+1];
+        z  = mUniforms[NUM_UNIFORMS*i+2];
+        
+        sx = mUniforms[NUM_UNIFORMS*i+3];   
+        sy = mUniforms[NUM_UNIFORMS*i+4];   
+        sz = mUniforms[NUM_UNIFORMS*i+5];   
+        
+        Matrix.translateM(viewMatrix, 0, x,-y, z); 
+      
+        viewMatrix[4] += sx*viewMatrix[0]; // Multiply viewMatrix by 1 x 0 0 , i.e. X-shear. TODO: change this so it is symmetric w respect to all the axis.
+        viewMatrix[5] += sx*viewMatrix[1]; //                        0 1 0 0 
+        viewMatrix[6] += sx*viewMatrix[2]; //                        0 0 1 0
+        viewMatrix[7] += sx*viewMatrix[3]; //                        0 0 0 1
+      
+        viewMatrix[0] += sy*viewMatrix[4]; // Multiply viewMatrix by 1 0 0 0 , i.e. Y-shear. TODO: change this so it is symmetric w respect to all the axis.
+        viewMatrix[1] += sy*viewMatrix[5]; //                        y 1 0 0
+        viewMatrix[2] += sy*viewMatrix[6]; //                        0 0 1 0
+        viewMatrix[3] += sy*viewMatrix[7]; //                        0 0 0 1      
+      
+        // TODO: implement Z-shear.
+        
+        Matrix.translateM(viewMatrix, 0,-x, y, -z);
+        }
+      }
+   
+    Matrix.translateM(viewMatrix, 0, mObjHalfX,-mObjHalfY, -mObjHalfZ);
+    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, viewMatrix, 0);
+    
+    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
+    GLES20.glUniform1f( mDepthH, dp.depth);   
+    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, viewMatrix, 0);
+    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// here construct the ModelView Matrix, but without any effects
+
+  synchronized void sendNoEffects(DistortedProjection dp) 
+    {
+    Matrix.setIdentityM(mTmpMatrix, 0);
+    Matrix.translateM(mTmpMatrix, 0, mObjHalfX-dp.width/2, dp.height/2-mObjHalfY, mObjHalfZ-dp.distance);
+    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, mTmpMatrix, 0);
+    
+    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
+    GLES20.glUniform1f( mDepthH, dp.depth);  
+    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, mTmpMatrix, 0);
+    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator i)
+    {
+    if( mMax[MATRIX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = p;
+      mInterI[mNumEffects] = i;
+      
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator i)
+    {
+    if( mMax[MATRIX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = null;
+      mInterI[mNumEffects] = i;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
+            
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator1D i, float aX, float aY, float aZ)
+    {
+    if( mMax[MATRIX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = null;
+      mInterI[mNumEffects] = i;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
+      
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator1D i, float aX, float aY, float aZ)
+    {
+    if( mMax[MATRIX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = p;
+      mInterI[mNumEffects] = i;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
+      
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float x, float y, float z, float aA, float aX, float aY, float aZ)
+    {
+    if( mMax[MATRIX]>mNumEffects )
+      {
+      mInterP[mNumEffects] = null; 
+      mInterI[mNumEffects] = null;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] =  x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] =  y;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] =  z;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = aA;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
+      
+      return addBase(eln);   
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of VertexEffect  
+  }
diff --git a/src/main/java/org/distorted/library/EffectListVertex.java b/src/main/java/org/distorted/library/EffectListVertex.java
new file mode 100644
index 0000000..98fe28d
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectListVertex.java
@@ -0,0 +1,235 @@
+package org.distorted.library;
+
+import android.opengl.GLES20;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectListVertex extends EffectList
+  { 
+  private static final int NUM_UNIFORMS = 9;    
+  private static int mNumEffectsH;
+  private static int mTypeH;
+  private static int mUniformsH;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectListVertex(DistortedObject bmp) 
+    { 
+    super(bmp,NUM_UNIFORMS,VERTEX);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only max Byte.MAX_VALUE concurrent effects per bitmap.
+// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
+  
+  static boolean setMax(int m)
+    {
+    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[VERTEX] ) 
+      {
+           if( m<0              ) m = 0;
+      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
+      
+      mMax[VERTEX] = m;
+      return true;
+      }
+   
+    return false;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static int getMax()
+    {
+    return mMax[VERTEX];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mNumEffectsH= GLES20.glGetUniformLocation( mProgramH, "vNumEffects");
+    mTypeH      = GLES20.glGetUniformLocation( mProgramH, "vType");
+    mUniformsH  = GLES20.glGetUniformLocation( mProgramH, "vUniforms");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void compute(long currTime) 
+    {
+    if( currTime==mTime ) return;
+    if( mTime==0 ) mTime = currTime;
+    long step = (currTime-mTime);
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mInterI[i]==null ) continue;    
+      
+      if( mInterP[i]!=null ) 
+        {
+        mInterP[i].interpolateMain(mUniforms, NUM_UNIFORMS*i+7, mCurrentDuration[i]);
+      
+        mUniforms[NUM_UNIFORMS*i+7] = mUniforms[NUM_UNIFORMS*i+7]-mObjHalfX;
+        mUniforms[NUM_UNIFORMS*i+8] =-mUniforms[NUM_UNIFORMS*i+8]+mObjHalfY;
+        }
+        
+      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i, mCurrentDuration[i], step) )      
+        {
+        for(int j=0; j<mNumListeners; j++)   
+          EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                          EffectMessage.EFFECT_FINISHED, 
+                                         (mID[i]<<DistortedObject.TYPE_NUM)+Distorted.TYPE_VERT, 
+                                          mType[i], 
+                                          mBitmapID); 
+      
+        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i) )
+          {
+          remove(i);
+          i--;
+          continue;
+          }
+        }
+     
+      mCurrentDuration[i] += step;
+      }
+     
+    mTime = currTime;  
+    }  
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected void moveEffect(int index)
+    {
+    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
+    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
+    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];
+    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];
+    mUniforms[NUM_UNIFORMS*index+4] = mUniforms[NUM_UNIFORMS*(index+1)+4];
+    mUniforms[NUM_UNIFORMS*index+5] = mUniforms[NUM_UNIFORMS*(index+1)+5];
+    mUniforms[NUM_UNIFORMS*index+6] = mUniforms[NUM_UNIFORMS*(index+1)+6];
+    mUniforms[NUM_UNIFORMS*index+7] = mUniforms[NUM_UNIFORMS*(index+1)+7];
+    mUniforms[NUM_UNIFORMS*index+8] = mUniforms[NUM_UNIFORMS*(index+1)+8];
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void send() 
+    {
+    GLES20.glUniform1i( mNumEffectsH, mNumEffects);
+      
+    if( mNumEffects>0 )
+      {     
+      GLES20.glUniform1iv( mTypeH    ,  mNumEffects, mType    ,0);
+      GLES20.glUniform3fv( mUniformsH,3*mNumEffects, mUniforms,0);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void sendZero() 
+    {
+    GLES20.glUniform1i( mNumEffectsH, 0);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Do various post-processing on already computed effects.
+// 1) here unlike in the fragment queue, we don't have to multiply the points by ModelView matrix because that gets done in the shader.
+// 2) in case of swirl, pre-compute the sine and cosine of its rotation angle
+  
+  void postprocess()
+    {
+    double d;  
+     
+    for(int i=0; i<mNumEffects; i++)
+      {      
+      if( mType[i]==EffectNames.SWIRL.ordinal() )
+        {
+        d = Math.PI*mUniforms[NUM_UNIFORMS*i]/180;  
+        mUniforms[NUM_UNIFORMS*i+1] = (float)Math.sin(d);
+        mUniforms[NUM_UNIFORMS*i+2] = (float)Math.cos(d);
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, Interpolator2D point)
+    {
+    if( mMax[VERTEX]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects);    
+      
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = point;
+
+      return addPriv(eln,region);
+      }
+      
+    return -1;
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator inter, Float4D region, float x, float y)
+    {
+    if( mMax[VERTEX]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects);    
+      
+      mInterI[mNumEffects] = inter;
+      mInterP[mNumEffects] = null;
+      mUniforms[NUM_UNIFORMS*mNumEffects+7] = x-mObjHalfX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+8] =-y+mObjHalfY;  
+     
+      return addPriv(eln,region);
+      }
+      
+    return -1;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float v1, float v2, float v3, Float4D region, float x, float y)
+    {
+    if( mMax[VERTEX]>mNumEffects )
+      {
+      EffectNames.fillWithUnities(eln.ordinal(), mUniforms, NUM_UNIFORMS*mNumEffects); 
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] = v1;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = v2;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = v3;  
+     
+      mInterI[mNumEffects] = null;
+      mInterP[mNumEffects] = null;
+      mUniforms[NUM_UNIFORMS*mNumEffects+7] = x-mObjHalfX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+8] =-y+mObjHalfY;  
+      
+      return addPriv(eln,region);    
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private long addPriv(EffectNames eln, Float4D region)
+    {    
+    if( region!=null )
+      {
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = region.x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] =-region.y;   // invert y already
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = region.z<=0.0f ? 1000*mObjHalfX : region.z;
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = region.w;
+      }
+    else
+      {
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = 0.0f;
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = 0.0f;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = 1000*mObjHalfX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = 0.0f;
+      }
+    
+    return addBase(eln);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of VertexEffect  
+  }
diff --git a/src/main/java/org/distorted/library/EffectListener.java b/src/main/java/org/distorted/library/EffectListener.java
new file mode 100644
index 0000000..17c084e
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectListener.java
@@ -0,0 +1,32 @@
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * 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 DistortedBitmap#addEventListener(EffectListener)}.
+ * List of all possible events that can happen is defined in {@link EffectMessage}
+ */
+
+public interface EffectListener 
+  {
+
+/**
+ * Gets called when event of type 'em' happens to effect 'effectID'. 
+ * 
+ * @param eventMessage 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 DistortedBitmap.{deform,distort,move,...} functions.
+ * @param effectType   Type of the effect as defined in EffectNames, e.g. if effectType==EffectNames.MOVE.ordinal(),
+ *                     then the event happened to a MOVE effect. 
+ * @param bitmapID     the ID of the DistortedBitmap object, as returned by {@link DistortedBitmap#getID()}, this event 
+ *                     happened to. If the object has been created using a copy constructor from another instance of
+ *                     DistortedBitmap, the ID here will be the one of the original object.     
+ *                                     
+ * @see EffectMessage
+ * @see EffectNames
+ */
+   
+  void effectMessage(EffectMessage eventMessage, long effectID, int effectType, long bitmapID);
+  }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectMessage.java b/src/main/java/org/distorted/library/EffectMessage.java
new file mode 100644
index 0000000..0f5111c
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectMessage.java
@@ -0,0 +1,34 @@
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/** 
+* Defines all possible events a class implementing the {@link EffectListener} interface can receive.
+*/
+
+public enum EffectMessage 
+  {
+/**
+ * The effect has been removed. This can happen if:
+ * <ul>
+ * <li> someone explicitly removed the effect with a call to {@link DistortedBitmap#abortEffect(long)} (or one of the other 'abort' methods)
+ * <li> the interpolation of the effect has finished and the end result is equal to the effect's zero point.
+ * </ul>    
+ */
+  EFFECT_REMOVED,
+  
+/**
+ * Interpolation of the effect has finished. 
+ * <p>
+ * If you set up an interpolated effect and set its Interpolator to do 3.5 interpolations of 1000 ms each
+ * with calls to {@link DistortedInterpolator#setCount(3.5f)} and {@link DistortedInterpolator#setDuration(1000)},
+ * then you are going to get this message exactly once after 3.5*1000 = 3500 milliseconds when the interpolation 
+ * finishes. You will never get this message if you set the effect to go on indefinitely with a call to 
+ * {@link DistortedInterpolator#setCount(0.0)}.
+ * <p>  
+ * If then the end effect is equal to the effect's zero point, then immediately after this message you 
+ * will also get a EFFECT_REMOVED message.
+ */
+  EFFECT_FINISHED;
+  }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectMessageSender.java b/src/main/java/org/distorted/library/EffectMessageSender.java
new file mode 100644
index 0000000..ab8c62a
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectMessageSender.java
@@ -0,0 +1,99 @@
+package org.distorted.library;
+
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+final class EffectMessageSender extends Thread
+  {
+  private class Message
+    {
+    EffectListener mListener;
+    EffectMessage mMessage;
+    long mEffectID;
+    int mEffectType;
+    long mBitmapID;
+   
+    Message(EffectListener l, EffectMessage m, long id, int type, long bmpID)
+      {
+      mListener   = l;
+      mMessage    = m;
+      mEffectID   = id;
+      mEffectType = type;
+      mBitmapID   = bmpID;
+      }
+    }
+  
+  private static Vector<Message> mList =null;
+  private static EffectMessageSender mThis=null;
+  private static volatile boolean mPaused;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  private EffectMessageSender() 
+    {
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void startSending()
+    {
+    mPaused = false;
+       
+    if( mThis==null )
+      {
+      mList = new Vector<Message>();
+      mThis = new EffectMessageSender();
+      mThis.start();
+      }
+    else  
+      {  
+      synchronized(mThis)
+        {
+        mThis.notify();
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  static void stopSending()
+    {
+    mPaused = true;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  public void run()
+    {
+    Message tmp;  
+     
+    while(true)
+      {
+      if( mList.size()>0 )
+        {
+        tmp = mList.get(0); 
+        tmp.mListener.effectMessage(tmp.mMessage, tmp.mEffectID, tmp.mEffectType, tmp.mBitmapID);
+        mList.remove(0);
+        }
+     
+      if( mPaused ) 
+        {
+        synchronized(mThis)
+          {
+          try  { mThis.wait(); }
+          catch(InterruptedException ex) { }
+          }
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+        
+  static void newMessage(EffectListener l, EffectMessage m, long id, int type, long bmpID)
+    {
+    Message msg = mThis.new Message(l,m,id,type,bmpID);  
+    mList.add(msg);   
+    }
+  }
+///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectNames.java b/src/main/java/org/distorted/library/EffectNames.java
new file mode 100644
index 0000000..17f0e40
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectNames.java
@@ -0,0 +1,146 @@
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+enum EffectNames 
+  {
+  // EFFECT NAME /////// EFFECT TYPE ////////////// UNITY /////////////////////////
+   
+  ROTATE           ( Distorted.TYPE_MATR,   new float[] {0.0f}           ),
+  QUATERNION       ( Distorted.TYPE_MATR,   new float[] {0.0f,0.0f,0.0f} ),      // quaternion is a unity iff its axis (x,y,z) is (0,0,0) 
+  MOVE             ( Distorted.TYPE_MATR,   new float[] {0.0f,0.0f,0.0f} ),
+  SCALE            ( Distorted.TYPE_MATR,   new float[] {1.0f,1.0f,1.0f} ),
+  SHEAR            ( Distorted.TYPE_MATR,   new float[] {0.0f,0.0f,0.0f} ),
+  // add new Matrix effects here...
+  
+  DISTORT          ( Distorted.TYPE_VERT,   new float[] {0.0f,0.0f,0.0f} ),      // keep this the first VERT effect (reason: getType)
+  DEFORM           ( Distorted.TYPE_VERT,   new float[] {0.0f,0.0f}      ),
+  SINK             ( Distorted.TYPE_VERT,   new float[] {1.0f}           ),
+  SWIRL            ( Distorted.TYPE_VERT,   new float[] {0.0f}           ),
+  WAVE             ( Distorted.TYPE_VERT,   new float[] {0.0f}           ),
+  // add new Vertex Effects here...
+  
+  MACROBLOCK       ( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),      // keep this the first FRAG effect (reason: getType)
+  ALPHA            ( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
+  SMOOTH_ALPHA     ( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
+  CHROMA           ( Distorted.TYPE_FRAG,   new float[] {0.0f}           ),
+  SMOOTH_CHROMA    ( Distorted.TYPE_FRAG,   new float[] {0.0f}           ),
+  BRIGHTNESS       ( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
+  SMOOTH_BRIGHTNESS( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
+  SATURATION       ( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
+  SMOOTH_SATURATION( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
+  CONTRAST         ( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
+  SMOOTH_CONTRAST  ( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
+  HUE              ( Distorted.TYPE_FRAG,   new float[] {0.0f}           ),
+  SMOOTH_HUE       ( Distorted.TYPE_FRAG,   new float[] {0.0f}           );
+  // add new Fragment effects here...
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private static final int MAXDIM = 4;  // maximum supported dimension of an effect  
+  
+  private int type;
+  private float[] unity;
+  
+  static private float[] unities;
+  static private int[] dimensions;
+  
+  static
+    {
+    int len = values().length;
+    int i=0;
+    
+    dimensions = new int[len];
+    unities    = new float[MAXDIM*len];
+    
+    for(EffectNames name: EffectNames.values())
+      {
+      dimensions[i] = name.unity.length;
+      
+      switch(dimensions[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+0] = name.unity[0];
+        case 0: break;
+        }
+      
+      i++;  
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private EffectNames(int type, float[] unity)
+    {
+    this.type = type;  
+    this.unity= unity;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  public int getType()
+    {
+    return type;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  static int getType(int ordinal)
+    {
+    if( ordinal<DISTORT.ordinal()     ) return Distorted.TYPE_MATR;
+    if( ordinal<MACROBLOCK.ordinal()  ) return Distorted.TYPE_VERT;
+   
+    return Distorted.TYPE_FRAG;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void fillWithUnities(int ordinal, float[] buffer, int index)
+    {
+    switch(dimensions[ordinal])
+      {
+      case 0: break;
+      case 1: buffer[index+0]=unities[MAXDIM*ordinal+0];
+              break;
+      case 2: buffer[index+0]=unities[MAXDIM*ordinal+0]; 
+              buffer[index+1]=unities[MAXDIM*ordinal+1];
+              break;
+      case 3: buffer[index+0]=unities[MAXDIM*ordinal+0]; 
+              buffer[index+1]=unities[MAXDIM*ordinal+1]; 
+              buffer[index+2]=unities[MAXDIM*ordinal+2];
+              break;
+      case 4: buffer[index+0]=unities[MAXDIM*ordinal+0]; 
+              buffer[index+1]=unities[MAXDIM*ordinal+1]; 
+              buffer[index+2]=unities[MAXDIM*ordinal+2];
+              buffer[index+3]=unities[MAXDIM*ordinal+3];
+              break;
+      }  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  static boolean isUnity(int ordinal, float[] buffer, int index)
+    {
+    switch(dimensions[ordinal])
+      {
+      case 0: return true;
+      case 1: return buffer[index+0]==unities[MAXDIM*ordinal+0];
+      case 2: return buffer[index+0]==unities[MAXDIM*ordinal+0] && 
+                     buffer[index+1]==unities[MAXDIM*ordinal+1];
+      case 3: return buffer[index+0]==unities[MAXDIM*ordinal+0] && 
+                     buffer[index+1]==unities[MAXDIM*ordinal+1] && 
+                     buffer[index+2]==unities[MAXDIM*ordinal+2];
+      case 4: return buffer[index+0]==unities[MAXDIM*ordinal+0] && 
+                     buffer[index+1]==unities[MAXDIM*ordinal+1] && 
+                     buffer[index+2]==unities[MAXDIM*ordinal+2] &&
+                     buffer[index+3]==unities[MAXDIM*ordinal+3];
+      }
+   
+    return false;
+    }
+  }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
diff --git a/src/main/java/org/distorted/library/Float1D.java b/src/main/java/org/distorted/library/Float1D.java
new file mode 100644
index 0000000..e793d35
--- /dev/null
+++ b/src/main/java/org/distorted/library/Float1D.java
@@ -0,0 +1,71 @@
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * A 1-dimensional data structure containing a single float. The float has no particular meaning; 
+ * when this data structure is used in Interpolators, we can think of it as a 1-dimensional Point 
+ * a few of which the Interpolator interpolates between.
+ */
+
+public class Float1D 
+  {
+  float x;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor that initialises the value of the single float to ox.   
+ *   
+ * @param ox value of the single float.
+ */
+  public Float1D(int ox)
+    {
+    x = ox;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor that initialises the value of the single float to ox.   
+ *   
+ * @param ox value of the single float.
+ */  
+  public Float1D(float ox)
+    {
+    x = ox;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resets the value of the single float.
+ * 
+ * @param ox new value of the single float.
+ */
+  public void set(int ox)
+    {
+    x = ox;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resets the value of the single float.
+ * 
+ * @param ox new value of the single float.
+ */
+  public void set(float ox)
+    {
+    x = ox;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the value of the float contained.
+ * 
+ * @return The single float.
+ */
+  public float getX()
+    {
+    return x;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of class   
+  }
diff --git a/src/main/java/org/distorted/library/Float2D.java b/src/main/java/org/distorted/library/Float2D.java
new file mode 100644
index 0000000..3f07d90
--- /dev/null
+++ b/src/main/java/org/distorted/library/Float2D.java
@@ -0,0 +1,79 @@
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * A 2-dimensional data structure containing two floats. The floats have no particular meaning; 
+ * when this data structure is used in Interpolators, we can think of it as a 2-dimensional Point 
+ * a few of which the Interpolator interpolates between.
+ */
+
+public class Float2D extends Float1D
+  {
+  float y;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor that initialises the value of the two floats to (ox,oy).   
+ *   
+ * @param ox value of the first float.
+ * @param oy value of the second float.
+ */  
+  public Float2D(int ox, int oy)
+    {
+    super(ox);
+    y = oy;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor that initialises the value of the two floats to (ox,oy).   
+ *   
+ * @param ox value of the first float.
+ * @param oy value of the second float.
+ */    
+  public Float2D(float ox, float oy)
+    {
+    super(ox);
+    y = oy;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Reset the value of the floats to (ox,oy).
+ * 
+ * @param ox new value of the first float
+ * @param oy new value of the second float
+ */
+  public void set(int ox, int oy)
+    {
+    x = ox;
+    y = oy;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Reset the value of the floats to (ox,oy).
+ * 
+ * @param ox new value of the first float
+ * @param oy new value of the seond float
+ */
+  public void set(float ox, float oy)
+    {
+    x = ox;
+    y = oy;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the value of the second float contained.
+ * 
+ * @return The second float.
+ */
+  public float getY()
+    {
+    return y;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of class   
+  }
diff --git a/src/main/java/org/distorted/library/Float3D.java b/src/main/java/org/distorted/library/Float3D.java
new file mode 100644
index 0000000..5f8faa7
--- /dev/null
+++ b/src/main/java/org/distorted/library/Float3D.java
@@ -0,0 +1,85 @@
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * A 3-dimensional data structure containing three floats. The floats have no particular meaning; 
+ * when this data structure is used in Interpolators, we can think of it as a 3-dimensional Point 
+ * a few of which the Interpolator interpolates between.
+ */
+
+public class Float3D extends Float2D 
+  {
+  float z;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor that initialises the value of the three floats to (vx,vy,vz).   
+ *   
+ * @param vx value of the first float.
+ * @param vy value of the second float.
+ * @param vz value of the third float.
+ */ 
+  public Float3D(int vx, int vy, int vz)
+    {
+    super(vx,vy);
+    z = vz;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor that initialises the value of the three floats to (vx,vy,vz).   
+ *   
+ * @param vx value of the first float.
+ * @param vy value of the second float.
+ * @param vz value of the third float.
+ */ 
+  public Float3D(float vx, float vy, float vz)
+    {
+    super(vx,vy);
+    z = vz;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Reset the value of the floats to (vx,vy,vz).
+ * 
+ * @param vx new value of the first float
+ * @param vy new value of the second float
+ * @param vz new value of the third float
+ */
+  public void set(int vx, int vy, int vz)
+    {
+    x = vx;
+    y = vy;
+    z = vz;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Reset the value of the floats to (vx,vy,vz).
+ * 
+ * @param vx new value of the first float
+ * @param vy new value of the second float
+ * @param vz new value of the third float
+ */
+  public void set(float vx, float vy, float vz)
+    {
+    x = vx;
+    y = vy;
+    z = vz;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the value of the third float contained.
+ * 
+ * @return The third float.
+ */
+  public float getZ()
+    {
+    return z;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of class   
+  }
diff --git a/src/main/java/org/distorted/library/Float4D.java b/src/main/java/org/distorted/library/Float4D.java
new file mode 100644
index 0000000..7c2f0f4
--- /dev/null
+++ b/src/main/java/org/distorted/library/Float4D.java
@@ -0,0 +1,91 @@
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * A 4-dimensional data structure containing four floats. The floats have no particular meaning; 
+ * when this data structure is used in Interpolators, we can think of it as a 4-dimensional Point 
+ * a few of which the Interpolator interpolates between.
+ */
+
+public class Float4D extends Float3D 
+  {
+  float w;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor that initialises the value of the four floats to (vx,vy,vz,vw).   
+ *   
+ * @param vx value of the first float.
+ * @param vy value of the second float.
+ * @param vz value of the third float.
+ * @param vw value of the fourth float.
+ */ 
+  public Float4D(int vx, int vy, int vz, int vw)
+    {
+    super(vx,vy,vz);
+    w = vw;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor that initialises the value of the four floats to (vx,vy,vz,vw).   
+ *   
+ * @param vx value of the first float.
+ * @param vy value of the second float.
+ * @param vz value of the third float.
+ * @param vw value of the fourth float.
+ */ 
+  public Float4D(float vx, float vy, float vz, float vw)
+    {
+    super(vx,vy,vz);
+    w = vw;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Reset the value of the floats to (vx,vy,vz,vw).
+ * 
+ * @param vx new value of the first float
+ * @param vy new value of the second float
+ * @param vz new value of the third float
+ * @param vz new value of the fourth float
+ */
+  public void set(int vx, int vy, int vz, int vw)
+    {
+    x = vx;
+    y = vy;
+    z = vz;
+    w = vw;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Reset the value of the floats to (vx,vy,vz,vw).
+ * 
+ * @param vx new value of the first float
+ * @param vy new value of the second float
+ * @param vz new value of the third float
+ * @param vz new value of the fourth float
+ */
+  public void set(float vx, float vy, float vz, float vw)
+    {
+    x = vx;
+    y = vy;
+    z = vz;
+    w = vw;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the value of the fourth float contained.
+ * 
+ * @return The fourth float.
+ */
+  public float getW()
+    {
+    return w;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of class   
+  }
diff --git a/src/main/java/org/distorted/library/GridBitmap.java b/src/main/java/org/distorted/library/GridBitmap.java
new file mode 100644
index 0000000..6514b13
--- /dev/null
+++ b/src/main/java/org/distorted/library/GridBitmap.java
@@ -0,0 +1,97 @@
+package org.distorted.library;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class GridBitmap extends GridObject
+  {
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  GridBitmap(int xLength, int yLength)
+    {
+    dataLength = 2*xLength*(yLength-1)+2*(yLength-2); // (yLength-1) strips, 2*xLength triangles in each, plus 2 degenerate triangles per each of (yLength-2) joins 
+    final short[] indexData = new short[dataLength];
+    
+    int offset=0;    
+    for(int y=0; y<yLength-1; y++) 
+      {
+      if (y>0) indexData[offset++] = (short) (y*xLength); // Degenerate begin: repeat first vertex
+
+      for (int x = 0; x < xLength; x++) 
+        {
+        indexData[offset++] = (short) (( y   *xLength)+x);
+        indexData[offset++] = (short) (((y+1)*xLength)+x);
+        }
+
+      if (y<yLength-2) indexData[offset++] = (short) (((y+1)*xLength) + (xLength-1)); // Degenerate end: repeat last vertex
+      }
+
+    // for(int g=0; g<dataLength; g++) Log.e(TAG_BACKGROUND, "index["+g+"]="+indexData[g]);
+
+    float[] bufferData= new float[COLOR_DATA_SIZE*dataLength];
+
+    offset=0;
+    for(int i=0; i<dataLength; i++)
+      {
+      bufferData[offset++] = 1.0f; // r
+      bufferData[offset++] = 1.0f; // g
+      bufferData[offset++] = 1.0f; // b
+      bufferData[offset++] = 1.0f; // a
+      }
+    mGridColors = ByteBuffer.allocateDirect(COLOR_DATA_SIZE*dataLength*BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();                                                        
+    mGridColors.put(bufferData).position(0); 
+
+    bufferData = new float[NORMAL_DATA_SIZE*dataLength];
+
+    offset=0;
+    for(int i=0; i<dataLength; i++)
+      {
+      bufferData[offset++] = 0.0f; // x
+      bufferData[offset++] = 0.0f; // y
+      bufferData[offset++] = 1.0f; // z
+      }
+    mGridNormals = ByteBuffer.allocateDirect(NORMAL_DATA_SIZE*dataLength*BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();                                                        
+    mGridNormals.put(bufferData).position(0); 
+
+    float tmpx,tmpy,tmpz;
+    bufferData = new float[TEX_DATA_SIZE*dataLength];
+
+    offset=0;
+    for(int i=0; i<dataLength; i++)
+      {
+      tmpx = ((float)(indexData[offset/2]%xLength))/(xLength-1);
+      tmpy = ((float)(indexData[offset/2]/xLength))/(yLength-1);
+
+      bufferData[offset++] = tmpx; // s=x
+      bufferData[offset++] = tmpy; // t=y
+      }
+    mGridTexture = ByteBuffer.allocateDirect(TEX_DATA_SIZE*dataLength*BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();                                                        
+    mGridTexture.put(bufferData).position(0); 
+
+    //for(int g=0; g<dataLength; g++) Log.e(TAG_BACKGROUND, "tex["+g+"]=("+bufferData[2*g]+","+bufferData[2*g+1]+")");
+    //Log.e(TAG, "regWidth="+(2*mRegW)+" regHeight="+(2*mRegH)+" xLength="+xLength+" yLength="+yLength);
+
+    offset=0;
+    bufferData= new float[POSITION_DATA_SIZE*dataLength];
+
+    for(int i=0; i<dataLength; i++)
+      {
+      tmpx = ((float)(indexData[offset/3]%xLength))/(xLength-1);
+      tmpy = ((float)(indexData[offset/3]/xLength))/(yLength-1);
+      tmpz = 0;
+
+      bufferData[offset++] = (tmpx-0.5f); // x
+      bufferData[offset++] = (0.5f-tmpy); // y
+      bufferData[offset++] =        tmpz; // z
+      }
+    mGridPositions = ByteBuffer.allocateDirect(POSITION_DATA_SIZE*dataLength*BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();                                                        
+    mGridPositions.put(bufferData).position(0); 
+
+    //for(int g=0; g<dataLength; g++) android.util.Log.e("BACKGROUND", "pos["+g+"]=("+bufferData[3*g]+","+bufferData[3*g+1]+")");
+    }
+  };
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/GridCubes.java b/src/main/java/org/distorted/library/GridCubes.java
new file mode 100644
index 0000000..59efdef
--- /dev/null
+++ b/src/main/java/org/distorted/library/GridCubes.java
@@ -0,0 +1,739 @@
+package org.distorted.library;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class GridCubes extends GridObject
+   {
+   private static final float R = 0.2f;
+   private static final float FRONTZ = 0.5f;
+   private static final float BACKZ  =-0.5f;
+   
+   private static final int NORTH = 0;
+   private static final int WEST  = 1;
+   private static final int EAST  = 2;
+   private static final int SOUTH = 3;
+   
+   private static final boolean BACK  = true;
+   private static final boolean FRONT = false;
+   private static final boolean UPPER = false;
+   private static final boolean LOWER = true;
+   
+   private static final float[] mNormalX = new float[4];
+   private static final float[] mNormalY = new float[4];
+   
+   private class Edge
+     {
+     final int side; 
+     final int row;
+     final int col;
+     
+     public Edge(int s, int r, int c)
+       {
+       side= s; 
+       row = r;
+       col = c;
+       }
+     };
+   
+   private int frontVert, sideVert;
+   private int mCols, mRows;
+   private short[][] mCubes;
+   private ArrayList<Edge> mEdges = new ArrayList<Edge>();
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private int computeDataLength(boolean frontOnly)
+      {
+      int frontWalls=0, frontSegments=0, sideWalls=0, sideBends=0;
+      
+      for(int i=0; i<mRows; i++)
+         for(int j=0; j<mCols; j++)
+            {
+            if( mCubes[i][j]%2 == 1 )  // land
+              {
+              frontWalls++;
+              if( j==mCols-1 || mCubes[i][j+1]%2 == 0 ) frontSegments++;
+              }
+              
+            if( (i==0 && mCubes[i][j]!=2) || (i!=0 && mCubes[i][j] != mCubes[i-1][j  ]) ) sideWalls++; // up
+            if( (j==0 && mCubes[i][j]!=2) || (j!=0 && mCubes[i][j] != mCubes[i  ][j-1]) ) sideWalls++; // left
+            if( i==mRows-1 && mCubes[i][j]!=2                                           ) sideWalls++; // bottom
+            if( j==mCols-1 && mCubes[i][j]!=2                                           ) sideWalls++; // right
+            }
+
+      int edges= mEdges.size();
+      
+      for(int i=0; i<edges; i++) 
+        {
+        Edge curr = mEdges.get(i);
+        Edge next = getNextEdge(curr);
+        int startX = curr.col;
+        int startY = curr.row;
+        int startS = curr.side;
+        
+        do
+          {
+          if( next.side != curr.side ) sideBends++; 
+          curr  = next; 
+          next = getNextEdge(curr);
+          }
+        while( curr.col!=startX || curr.row!=startY || curr.side!=startS );
+        }
+      
+      frontVert = 2*( frontWalls + 2*frontSegments - 1);
+      sideVert  = 2*( sideWalls + sideBends + edges -1);
+      
+      int dataL = frontOnly ? frontVert : (frontVert+1) + (1+sideVert+1) + (1+frontVert);
+      
+      android.util.Log.e("CUBES","frontVert="+frontVert+" sideVert="+sideVert);
+      android.util.Log.e("CUBES", "frontW="+frontWalls+" fSegments="+frontSegments+" sWalls="+sideWalls+" sSegments="+edges+" sideBends="+sideBends+" dataLen="+dataL );
+      
+      return dataL<0 ? 0:dataL;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+   private static String debug(short[] val)
+     {
+     String ret="";
+     
+     for(int i=0; i<val.length; i++) ret+=(" "+val[i]); 
+     
+     return ret;
+     }
+*/
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+   private static String debug(float[] val, int stop)
+     {
+     String ret="";
+
+     for(int i=0; i<val.length; i++) 
+        {
+        if( i%stop==0 ) ret+="\n";
+        ret+=(" "+val[i]);
+        }
+
+     return ret;
+     }
+*/  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+   private static String debug(Edge e)
+     {
+     String d = "";
+     
+     switch(e.side)
+       {
+       case NORTH: d+="NORTH "; break;
+       case SOUTH: d+="SOUTH "; break;
+       case WEST : d+="WEST  "; break;
+       case EAST : d+="EAST  "; break;
+       }
+     
+     d+=("("+e.row+","+e.col+")");
+     
+     return d;
+     }   
+*/ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void buildGrid(int cols, String desc, boolean frontOnly)
+     {
+     mRows     =0;
+     mCols     =0;
+     dataLength=0;
+     
+     if( cols>0 )
+       {
+       int reallen = desc.length();
+       int len = reallen;
+
+       if( (reallen/cols)*cols != reallen )
+         {
+         len = ((reallen/cols)+1)*cols; 
+         for(int i=reallen; i<len; i++) desc += "0";
+         }
+    
+       if( desc.indexOf("1")>=0 )
+         {
+         mCols = cols;
+         mRows = len/cols;
+
+         mCubes = new short[mRows][mCols];
+       
+         for(int j=0; j<mCols; j++) 
+           for(int i=0; i<mRows; i++)
+             mCubes[i][j] = (short)(desc.charAt(i*mCols+j) == '1' ? 1:0); 
+       
+         markRegions();
+         dataLength = computeDataLength(frontOnly);
+         }
+       }
+     }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Mark all the 'regions' of our grid  - i.e. separate pieces of 'land' (connected blocks that will 
+// be rendered) and 'water' (connected holes in between) with integers. Each connected block of land
+// gets a unique odd integer, each connected block of water a unique even integer.
+//
+// Water on the edges of the grid is also considered connected to itself!   
+//   
+// This function also creates a list of 'Edges'. Each Edge is a data structure from which later on we
+// will start building the side walls of each connected black of land (and sides of holes of water 
+// inside)   
+   
+   private void markRegions()
+     {
+     int i, j, numWater=1, numLand=0;
+     
+     for(i=0; i<mRows;i++) if( mCubes[      i][      0]==0 ) markRegion((short)2,      i,       0);
+     for(i=0; i<mRows;i++) if( mCubes[      i][mCols-1]==0 ) markRegion((short)2,      i, mCols-1);
+     for(i=0; i<mCols;i++) if( mCubes[0      ][      i]==0 ) markRegion((short)2,      0,       i);
+     for(i=0; i<mCols;i++) if( mCubes[mRows-1][      i]==0 ) markRegion((short)2,mRows-1,       i);
+           
+     for(i=0; i<mRows; i++)
+        for(j=0; j<mCols; j++)
+           {
+           if( mCubes[i][j] == 0 ) { numWater++; markRegion( (short)(2*numWater ),i,j); mEdges.add(new Edge(NORTH,i,j)); }
+           if( mCubes[i][j] == 1 ) { numLand ++; markRegion( (short)(2*numLand+1),i,j); mEdges.add(new Edge(NORTH,i,j)); }
+           }
+     
+     // now we potentially need to kick out some Edges - precisely the edges with water inside -
+     // which are surrounded by more than one type of land. Otherwise the following does not work:
+     //
+     // 0 1 0
+     // 1 0 1
+     // 0 1 0
+     //
+     // The 'water inside' edges that did not get kicked out by this procedure need to be transformed
+     // with Edge(NORTH,row,col) -> Edge(SOUTH,row-1,col) so that later on normals work correctly
+     // (Edge always needs to point out from land to water for that)
+     
+     int numEdges= mEdges.size();
+     short initLand;
+     int initCol, initRow;
+     boolean kicked;
+     Edge e;
+     
+     for(i=0; i<numEdges; i++) 
+       {
+       e = mEdges.get(i);
+       initRow= e.row;
+       initCol= e.col;
+         
+       //android.util.Log.e("CUBES", "checking edge "+debug(e));
+             
+       if( mCubes[initRow][initCol]%2==0 )
+         {
+         kicked = false; 
+         initLand = mCubes[initRow-1][initCol];
+         
+         do
+           {
+           e = getNextEdge(e); 
+           //android.util.Log.e("CUBES", " next edge "+debug(e));   
+       
+           switch(e.side)
+             {
+             case NORTH: if( initLand!=mCubes[e.row-1][e.col  ] ) kicked=true; break;
+             case SOUTH: if( initLand!=mCubes[e.row+1][e.col  ] ) kicked=true; break;
+             case WEST:  if( initLand!=mCubes[e.row  ][e.col-1] ) kicked=true; break;
+             case EAST:  if( initLand!=mCubes[e.row  ][e.col+1] ) kicked=true; break;
+             }
+           
+           if( kicked )
+             {
+             //android.util.Log.e("CUBES", "kicking out edge!");
+             mEdges.remove(i);
+             i--;
+             numEdges--; 
+             }
+           }
+         while( kicked==false && (e.col!=initCol || e.row!=initRow || e.side!=NORTH) );
+         
+         if( kicked==false )
+           {
+           mEdges.set(i, new Edge(SOUTH,e.row-1,e.col)); 
+           }
+         }
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// when calling, make sure that newVal != val
+   
+   private void markRegion(short newVal, int row, int col)
+     {
+     short val = mCubes[row][col];
+     mCubes[row][col] = newVal;
+     
+     if( row>0       && mCubes[row-1][col  ]==val ) markRegion(newVal, row-1, col  );
+     if( row<mRows-1 && mCubes[row+1][col  ]==val ) markRegion(newVal, row+1, col  );
+     if( col>0       && mCubes[row  ][col-1]==val ) markRegion(newVal, row  , col-1);
+     if( col<mCols-1 && mCubes[row  ][col+1]==val ) markRegion(newVal, row  , col+1);
+     }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+   private void createNormals(int row, int col)
+     {
+     int td,lr; 
+      
+	  int nw = (col>0       && row>0      ) ? (mCubes[row-1][col-1]%2) : 0;
+	  int w  = (col>0                     ) ? (mCubes[row  ][col-1]%2) : 0;
+	  int n  = (               row>0      ) ? (mCubes[row-1][col  ]%2) : 0;
+	  int c  =                                (mCubes[row  ][col  ]%2);
+	  int sw = (col>0       && row<mRows-1) ? (mCubes[row+1][col-1]%2) : 0;
+     int s  = (               row<mRows-1) ? (mCubes[row+1][col  ]%2) : 0;
+     int ne = (col<mCols-1 && row>0      ) ? (mCubes[row-1][col+1]%2) : 0;
+     int e  = (col<mCols-1               ) ? (mCubes[row  ][col+1]%2) : 0;
+     int se = (col<mCols-1 && row<mRows-1) ? (mCubes[row+1][col+1]%2) : 0;
+     
+     td = nw+n-w-c;
+     lr = c+n-w-nw;
+     if( td<0 ) td=-1;
+     if( td>0 ) td= 1;
+     if( lr<0 ) lr=-1;
+     if( lr>0 ) lr= 1;
+     mNormalX[0] = lr*R;
+     mNormalY[0] = td*R;
+     
+     td = w+c-sw-s;
+     lr = c+s-w-sw;
+     if( td<0 ) td=-1;
+     if( td>0 ) td= 1;
+     if( lr<0 ) lr=-1;
+     if( lr>0 ) lr= 1;
+     mNormalX[1] = lr*R;
+     mNormalY[1] = td*R;
+     
+     td = n+ne-c-e;
+     lr = e+ne-c-n;
+     if( td<0 ) td=-1;
+     if( td>0 ) td= 1;
+     if( lr<0 ) lr=-1;
+     if( lr>0 ) lr= 1;
+     mNormalX[2] = lr*R;
+     mNormalY[2] = td*R;
+     
+     td = c+e-s-se;
+     lr = e+se-c-s;
+     if( td<0 ) td=-1;
+     if( td>0 ) td= 1;
+     if( lr<0 ) lr=-1;
+     if( lr>0 ) lr= 1;
+     mNormalX[3] = lr*R;
+     mNormalY[3] = td*R;
+     /*
+	  android.util.Log.d("CUBES", "row="+row+" col="+col);
+	  android.util.Log.d("CUBES", mNormalX[0]+" "+mNormalY[0]);
+	  android.util.Log.d("CUBES", mNormalX[1]+" "+mNormalY[1]);
+	  android.util.Log.d("CUBES", mNormalX[2]+" "+mNormalY[2]);
+	  android.util.Log.d("CUBES", mNormalX[3]+" "+mNormalY[3]);
+     */
+     }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+   private int buildFrontBackGrid(boolean front, int vertex, float[] position, float[] normal, float[] texture)
+     {
+     short last, current;
+     boolean seenland=false;
+     float centerX, centerY;
+    
+     for(int i=0; i<mRows; i++)
+       {
+       last =0;
+         
+       for(int j=0; j<mCols; j++)
+         {
+         current = mCubes[i][j];
+            
+         if( current%2 == 1 )
+           {
+           centerX = j-(mCols-1.0f)/2.0f;
+           centerY = (mRows-1.0f)/2.0f-i;
+      
+           createNormals(i,j);
+          
+           if( last != current )
+             {
+             if( seenland ) vertex = repeatLast(vertex,position,normal,texture);    
+
+             if( front ) // NW corner
+               {
+               position[3*vertex+0] = (centerX-0.5f)/mCols;
+               position[3*vertex+1] = (centerY+0.5f)/mRows;
+               position[3*vertex+2] = FRONTZ;
+               normal[3*vertex+0]   = mNormalX[0];
+               normal[3*vertex+1]   = mNormalY[0];
+               normal[3*vertex+2]   = 1.0f;
+               texture[2*vertex+0]  = (float)j/mCols;
+               texture[2*vertex+1]  = (float)i/mRows;     
+               vertex++;
+               }
+             else  // SW corner
+               { 
+               position[3*vertex+0] = (centerX-0.5f)/mCols;
+               position[3*vertex+1] = (centerY-0.5f)/mRows; 
+               position[3*vertex+2] = BACKZ; 
+               normal[3*vertex+0]   = mNormalX[1];
+               normal[3*vertex+1]   = mNormalY[1];
+               normal[3*vertex+2]   =-1.0f;
+               texture[2*vertex+0]  = (float)j/mCols;
+               texture[2*vertex+1]  = (float)(i+1)/mRows;
+               vertex++;
+               
+               if( !seenland ) vertex = repeatLast(vertex,position,normal,texture);   //  if drawing the back, repeat the very first vertex
+               }
+             
+             if( seenland ) vertex = repeatLast(vertex,position,normal,texture);    
+
+             if( front ) // SW corner
+               {
+               position[3*vertex+0] = (centerX-0.5f)/mCols;
+               position[3*vertex+1] = (centerY-0.5f)/mRows; 
+               position[3*vertex+2] = FRONTZ; 
+               normal[3*vertex+0]   = mNormalX[1];
+               normal[3*vertex+1]   = mNormalY[1];
+               normal[3*vertex+2]   = 1.0f;
+               texture[2*vertex+0]  = (float)j/mCols;
+               texture[2*vertex+1]  = (float)(i+1)/mRows;
+               vertex++; 
+               }
+             else  // NW corner
+               {
+               position[3*vertex+0] = (centerX-0.5f)/mCols;
+               position[3*vertex+1] = (centerY+0.5f)/mRows;
+               position[3*vertex+2] = BACKZ;
+               normal[3*vertex+0]   = mNormalX[0];
+               normal[3*vertex+1]   = mNormalY[0];
+               normal[3*vertex+2]   =-1.0f;
+               texture[2*vertex+0]  = (float)j/mCols;
+               texture[2*vertex+1]  = (float)i/mRows;     
+               vertex++; 
+               }
+             }
+              
+           if( front )  // NE corner
+             {
+             position[3*vertex+0] = (centerX+0.5f)/mCols;
+             position[3*vertex+1] = (centerY+0.5f)/mRows;
+             position[3*vertex+2] = FRONTZ; 
+             normal[3*vertex+0]   = mNormalX[2];
+             normal[3*vertex+1]   = mNormalY[2];
+             normal[3*vertex+2]   = 1.0f;
+             texture[2*vertex+0]  = (float)(j+1)/mCols;
+             texture[2*vertex+1]  = (float)i/mRows;
+             vertex++;
+             }
+           else // SE corner
+             {
+             position[3*vertex+0] = (centerX+0.5f)/mCols;
+             position[3*vertex+1] = (centerY-0.5f)/mRows;
+             position[3*vertex+2] = BACKZ; 
+             normal[3*vertex+0]   = mNormalX[3];
+             normal[3*vertex+1]   = mNormalY[3];
+             normal[3*vertex+2]   =-1.0f;
+             texture[2*vertex+0]  = (float)(j+1)/mCols;
+             texture[2*vertex+1]  = (float)(i+1)/mRows;
+             vertex++; 
+             }
+           
+           if( front )  // SE corner
+             {
+             position[3*vertex+0] = (centerX+0.5f)/mCols;
+             position[3*vertex+1] = (centerY-0.5f)/mRows;
+             position[3*vertex+2] = FRONTZ; 
+             normal[3*vertex+0]   = mNormalX[3];
+             normal[3*vertex+1]   = mNormalY[3];
+             normal[3*vertex+2]   = 1.0f;
+             texture[2*vertex+0]  = (float)(j+1)/mCols;
+             texture[2*vertex+1]  = (float)(i+1)/mRows;
+             vertex++;
+             }
+           else // NE corner
+             {
+             position[3*vertex+0] = (centerX+0.5f)/mCols;
+             position[3*vertex+1] = (centerY+0.5f)/mRows;
+             position[3*vertex+2] = BACKZ; 
+             normal[3*vertex+0]   = mNormalX[2];
+             normal[3*vertex+1]   = mNormalY[2];
+             normal[3*vertex+2]   =-1.0f;
+             texture[2*vertex+0]  = (float)(j+1)/mCols;
+             texture[2*vertex+1]  = (float)i/mRows;
+             vertex++; 
+             }
+           
+           seenland = true;
+           }
+            
+         last = current;
+         }
+       }
+     
+     return vertex;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private int repeatLast(int vertex, float[] position, float[] normal, float[] texture)
+     {
+     if( vertex>0 )
+       {
+       position[3*vertex+0] = position[3*vertex-3]; 
+       position[3*vertex+1] = position[3*vertex-2];
+       position[3*vertex+2] = position[3*vertex-1];
+
+       normal[3*vertex+0]   = normal[3*vertex-3]; 
+       normal[3*vertex+1]   = normal[3*vertex-2];
+       normal[3*vertex+2]   = normal[3*vertex-1];
+
+       texture[2*vertex+0]  = texture[2*vertex-2];
+       texture[2*vertex+1]  = texture[2*vertex-1];
+         
+       vertex++;     
+       }
+     
+     return vertex;
+     }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private int buildSideGrid(int vertex, float[] position, float[] normal, float[] texture)
+     {
+     int edges= mEdges.size();
+     
+     for(int i=0; i<edges; i++) 
+       {
+       vertex = buildIthSide(mEdges.get(i), vertex, position, normal, texture);  
+       } 
+      
+     return vertex;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private int buildIthSide(Edge curr, int vertex, float[] position, float[] normal, float[] texture)
+     {
+     Edge prev; 
+     
+     if( curr.side==NORTH ) // water outside
+       {
+       prev = new Edge(WEST,curr.row,curr.col);
+       }
+     else                   // land outside; we need to move forward one link because we are going in opposite direction and we need to start from a bend.
+       {
+       prev = curr;
+       curr = new Edge(EAST,curr.row+1,curr.col-1);
+       }
+     
+     int col = curr.col;
+     int row = curr.row;
+     int side= curr.side;  
+     Edge next = getNextEdge(curr);
+     
+     addVertex(curr,BACK,LOWER,prev.side,vertex,position,normal,texture);
+     vertex++;
+     
+     do
+       {
+       if( prev.side!=curr.side )
+         {
+         addVertex(curr,BACK,LOWER,prev.side,vertex,position,normal,texture);
+         vertex++;
+         addVertex(curr,BACK,UPPER,prev.side,vertex,position,normal,texture);
+         vertex++;
+         }
+       
+       addVertex(curr,FRONT,LOWER,next.side,vertex,position,normal,texture);
+       vertex++;
+       addVertex(curr,FRONT,UPPER,next.side,vertex,position,normal,texture);
+       vertex++;
+       
+       prev = curr;
+       curr = next; 
+       next = getNextEdge(curr);
+       }
+     while( curr.col!=col || curr.row!=row || curr.side!=side );
+     
+     vertex = repeatLast(vertex,position,normal,texture);
+     
+     return vertex;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private Edge getNextEdge(Edge curr)
+     {
+     int col = curr.col;
+     int row = curr.row;
+      
+     //android.util.Log.e("CUBES", "row="+row+" col="+col+" mRows="+mRows+" mCols="+mCols);
+                       
+     switch(curr.side) 
+       {
+       case NORTH: if( col==mCols-1 ) 
+                     return new Edge(EAST,row,col);
+                   if( row>0 && mCubes[row-1][col+1]==mCubes[row][col] )
+                     return new Edge(WEST,row-1,col+1);
+                   if( mCubes[row][col+1]==mCubes[row][col] )
+                     return new Edge(NORTH,row,col+1);
+                   else  
+                     return new Edge(EAST,row,col);
+                   
+       case SOUTH: if( col==0 ) 
+                     return new Edge(WEST,row,col);
+                   if( (row<mRows-1) && mCubes[row+1][col-1]==mCubes[row][col] )
+                     return new Edge(EAST,row+1,col-1); 
+                   if( mCubes[row][col-1]==mCubes[row][col] )
+                     return new Edge(SOUTH,row,col-1);
+                   else
+                     return new Edge(WEST,row,col); 
+                     
+       case EAST : if( row==mRows-1 ) 
+                     return new Edge(SOUTH,row,col);
+                   if( (col<mCols-1) && mCubes[row+1][col+1]==mCubes[row][col] )
+                     return new Edge(NORTH,row+1,col+1);
+                   if( mCubes[row+1][col]==mCubes[row][col] )
+                     return new Edge(EAST,row+1,col);
+                   else 
+                     return new Edge(SOUTH,row,col);
+                   
+       case WEST : if( row==0 )
+                     return new Edge(NORTH,row,col);
+                   if( col>0 && mCubes[row-1][col-1]==mCubes[row][col] )
+                     return new Edge(SOUTH,row-1,col-1);
+                   if( mCubes[row-1][col]==mCubes[row][col] )
+                     return new Edge(WEST,row-1,col);
+                   else
+                     return new Edge(NORTH,row,col);     
+       }
+     
+     return null;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+   private void addVertex(Edge curr, boolean back, boolean lower,int side, int vertex, float[] position, float[] normal, float[] texture)
+     {
+     float centerX = curr.col-(mCols-1.0f)/2.0f;
+     float centerY = (mRows-1.0f)/2.0f-curr.row;
+  
+     switch(curr.side)
+       {
+       case NORTH: position[3*vertex+0] = (back ? (centerX-0.5f) : (centerX+0.5f))/mCols; 
+                   position[3*vertex+1] = (centerY+0.5f)/mRows;
+                   position[3*vertex+2] = lower ? BACKZ : FRONTZ;
+
+                   normal[3*vertex+0]   = side==NORTH ? 0.0f : (side==WEST?-R:R);
+                   normal[3*vertex+1]   = 1.0f;
+                   normal[3*vertex+2]   = lower ? -R:R;
+
+                   texture[2*vertex+0]  = (float)(back ? (curr.col  ):(curr.col+1))/mCols;
+                   texture[2*vertex+1]  = (float)(lower? (curr.row-1):(curr.row  ))/mRows;  
+                   break;
+       case SOUTH: position[3*vertex+0] = (back ? (centerX+0.5f) : (centerX-0.5f))/mCols;
+                   position[3*vertex+1] = (centerY-0.5f)/mRows;
+                   position[3*vertex+2] = lower ? BACKZ : FRONTZ;  
+            
+                   normal[3*vertex+0]   = side==SOUTH ? 0.0f: (side==EAST?-R:R);
+                   normal[3*vertex+1]   =-1.0f;
+                   normal[3*vertex+2]   = lower ? -R:R;
+
+                   texture[2*vertex+0]  = (float)(back ? (curr.col+1):(curr.col  ))/mCols;
+                   texture[2*vertex+1]  = (float)(lower? (curr.row+2):(curr.row+1))/mRows;
+                   break;
+       case WEST : position[3*vertex+0] = (centerX-0.5f)/mCols;
+                   position[3*vertex+1] = (back ? (centerY-0.5f):(centerY+0.5f))/mRows;
+                   position[3*vertex+2] = lower ? BACKZ : FRONTZ;
+
+                   normal[3*vertex+0]   =-1.0f;
+                   normal[3*vertex+1]   = side==WEST ? 0.0f : (side==NORTH?-R:R);
+                   normal[3*vertex+2]   = lower ? -R:R;
+ 
+                   texture[2*vertex+0]  = (float)(lower ? (curr.col-1):(curr.col  ))/mCols;
+                   texture[2*vertex+1]  = (float)(back  ? (curr.row+1):(curr.row  ))/mRows;
+                   break;
+       case EAST : position[3*vertex+0] = (centerX+0.5f)/mCols;
+                   position[3*vertex+1] = (back ? (centerY+0.5f):(centerY-0.5f))/mRows;
+                   position[3*vertex+2] = lower ? BACKZ : FRONTZ;
+
+                   normal[3*vertex+0]   = 1.0f;
+                   normal[3*vertex+1]   = side==EAST ? 0.0f : (side==SOUTH?-R:R);
+                   normal[3*vertex+2]   = lower ? -R:R; 
+
+                   texture[2*vertex+0]  = (float)(lower ? (curr.col+2):(curr.col+1))/mCols;
+                   texture[2*vertex+1]  = (float)(back  ? (curr.row  ):(curr.row+1))/mRows;
+                   break;
+       }
+     
+     if(texture[2*vertex+0]>1.0f) texture[2*vertex+0] =2.0f-texture[2*vertex+0];
+     if(texture[2*vertex+0]<0.0f) texture[2*vertex+0] =    -texture[2*vertex+0];
+     if(texture[2*vertex+1]>1.0f) texture[2*vertex+1] =2.0f-texture[2*vertex+1];
+     if(texture[2*vertex+1]<0.0f) texture[2*vertex+1] =    -texture[2*vertex+1];
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+/**
+ * Creates the underlying grid of vertices, normals, texture coords and colors.
+ *    
+ * @param rows See {@link DistortedCubes#DistortedCubes(String)} 
+ * @param desc See {@link DistortedCubes#DistortedCubes(String)}
+ */
+   public GridCubes(int cols, String desc, boolean frontOnly) 
+      {
+      buildGrid(cols,desc,frontOnly);
+       
+      int numVertices=0;
+      float[] colorData   = new float[COLOR_DATA_SIZE   *dataLength];
+      float[] positionData= new float[POSITION_DATA_SIZE*dataLength];
+      float[] normalData  = new float[NORMAL_DATA_SIZE  *dataLength];
+      float[] textureData = new float[TEX_DATA_SIZE     *dataLength];
+      
+      for(int i=0; i<dataLength; i++)
+        {
+        colorData[COLOR_DATA_SIZE*i+0] = 1.0f; // r
+        colorData[COLOR_DATA_SIZE*i+1] = 1.0f; // g
+        colorData[COLOR_DATA_SIZE*i+2] = 1.0f; // b
+        colorData[COLOR_DATA_SIZE*i+3] = 1.0f; // a
+        }
+
+      numVertices = buildFrontBackGrid(true, numVertices,positionData,normalData,textureData);
+      
+      if( !frontOnly )
+        {
+        numVertices = repeatLast(numVertices,positionData,normalData,textureData);
+        numVertices = buildSideGrid (numVertices,positionData,normalData,textureData);
+        numVertices = buildFrontBackGrid (false,numVertices,positionData,normalData,textureData);
+        }
+      
+      /*
+      android.util.Log.e("CUBES","dataLen="+dataLength+" vertex="+numVertices);
+      android.util.Log.d("CUBES", "position: "+debug(positionData,3) );
+      android.util.Log.d("CUBES", "normal: "  +debug(  normalData,3) );
+      android.util.Log.d("CUBES", "texture: " +debug( textureData,2) );
+      */
+      mGridColors = ByteBuffer.allocateDirect(COLOR_DATA_SIZE*dataLength*BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();                                                        
+      mGridColors.put(colorData).position(0); 
+
+      mGridPositions = ByteBuffer.allocateDirect(POSITION_DATA_SIZE*dataLength*BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();                                                        
+      mGridPositions.put(positionData).position(0); 
+      
+      mGridNormals = ByteBuffer.allocateDirect(NORMAL_DATA_SIZE*dataLength*BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();                                                        
+      mGridNormals.put(normalData).position(0); 
+
+      mGridTexture = ByteBuffer.allocateDirect(TEX_DATA_SIZE*dataLength*BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();                                                        
+      mGridTexture.put(textureData).position(0); 
+      }
+   }
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
diff --git a/src/main/java/org/distorted/library/GridObject.java b/src/main/java/org/distorted/library/GridObject.java
new file mode 100644
index 0000000..3e4f0e4
--- /dev/null
+++ b/src/main/java/org/distorted/library/GridObject.java
@@ -0,0 +1,34 @@
+package org.distorted.library;
+
+import java.nio.FloatBuffer;
+
+import android.opengl.GLES20;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+abstract class GridObject 
+   {
+   protected static final int BYTES_PER_FLOAT   = 4; //
+   protected static final int POSITION_DATA_SIZE= 3; // Size of the position data in elements
+   protected static final int COLOR_DATA_SIZE   = 4; // Size of the color data in elements 
+   protected static final int NORMAL_DATA_SIZE  = 3; // Size of the normal data in elements.
+   protected static final int TEX_DATA_SIZE     = 2; // Size of the texture coordinate data in elements. 
+
+   protected int dataLength;                       
+      
+   protected FloatBuffer mGridPositions,mGridColors,mGridNormals,mGridTexture;
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+   void draw()
+     { 
+     GLES20.glVertexAttribPointer(Distorted.mPositionH    , POSITION_DATA_SIZE, GLES20.GL_FLOAT, false, 0, mGridPositions);          
+     GLES20.glVertexAttribPointer(Distorted.mColorH       , COLOR_DATA_SIZE   , GLES20.GL_FLOAT, false, 0, mGridColors   );   
+     GLES20.glVertexAttribPointer(Distorted.mNormalH      , NORMAL_DATA_SIZE  , GLES20.GL_FLOAT, false, 0, mGridNormals  );
+     GLES20.glVertexAttribPointer(Distorted.mTextureCoordH, TEX_DATA_SIZE     , GLES20.GL_FLOAT, false, 0, mGridTexture  );  
+
+     GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, dataLength); 
+     }
+   }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/Interpolator.java b/src/main/java/org/distorted/library/Interpolator.java
new file mode 100644
index 0000000..c5e6ff6
--- /dev/null
+++ b/src/main/java/org/distorted/library/Interpolator.java
@@ -0,0 +1,210 @@
+package org.distorted.library;
+
+import java.util.Random;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/** A class to interpolate between a List of FloatNDs.
+* <p><ul>
+* <li>if there is only one Point, just jump to it.
+* <li>if there are two Points, linearly bounce between them
+* <li>if there are more, interpolate a loop (or a path!) between them.
+* </ul>
+*/
+
+// The way Interpolation between more than 2 Points is done:
+// 
+// Def: let w[i] = (w[i](x), w[i](y), w[i](z)) be the direction and speed we have to be flying at Point P[i]
+//
+// time it takes to fly though one segment v[i] --> v[i+1] : 0.0 --> 1.0
+// w[i] should be parallel to v[i+1] - v[i-1]   (cyclic notation)
+// |w[i]| proportional to | P[i]-P[i+1] |
+//
+// Given that the flight route (X(t), Y(t), Z(t)) from P(i) to P(i+1)  (0<=t<=1) has to satisfy
+// X(0) = P[i  ](x), Y(0)=P[i  ](y), Z(0)=P[i  ](z), X'(0) = w[i  ](x), Y'(0) = w[i  ](y), Z'(0) = w[i  ](z)
+// X(1) = P[i+1](x), Y(1)=P[i+1](y), Z(1)=P[i+1](z), X'(1) = w[i+1](x), Y'(1) = w[i+1](y), Z'(1) = w[i+1](z)
+//
+// we have the solution:  X(t) = at^3 + bt^2 + ct + d where
+// a =  2*P[i](x) +   w[i](x) - 2*P[i+1](x) + w[i+1](x)
+// b = -3*P[i](x) - 2*w[i](x) + 3*P[i+1](x) - w[i+1](x)
+// c = w[i](x)<br>
+// d = P[i](x)
+//
+// and similarly Y(t) and Z(t).
+
+public abstract class Interpolator 
+  {
+  /**
+   * One revolution takes us from the first vector to the last and back to first through the shortest path. 
+   */
+  public static final int MODE_LOOP = 0; 
+  /**
+   * We come back from the last to the first vector through the same way we got there.
+   */
+  public static final int MODE_PATH = 1; 
+  /**
+   * We just jump back from the last point to the first.
+   */
+  public static final int MODE_JUMP = 2; 
+ 
+  protected static Random mRnd = new Random();
+  
+  protected static final int NUM_NOISE = 5; // used iff mNoise>0.0. Number of intermediary points between each pair of adjacent vectors
+                                            // where we randomize noise factors to make the way between the two vectors not so smooth.
+  protected int numPoints;
+  protected int mVecCurr;    
+  protected boolean cacheDirty; // VectorCache not up to date
+  protected int mMode;          // LOOP, PATH or JUMP
+  protected long mDuration;     // number of miliseconds it takes to do a full loop/path from first vector to the last and back to the first 
+  protected float mCount;       // number of loops/paths we will do; mCount = 1.5 means we go from the first vector to the last, back to first, and to the last again. 
+  protected float mNoise;       // how 'smooth' our path form each vector to the next is. mNoise = 0.0 (min) --> completely smooth; mNoise==1.0 (max) --> very uneven
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// hide this from Javadoc
+  
+  Interpolator()
+    {
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  void interpolateMain(float[] buffer, int offset, long currentDuration)
+    {
+    if( mDuration<=0.0f ) 
+      {
+      interpolate(buffer,offset,mCount-(int)mCount);  
+      }
+    else
+      {
+      float x = (float)currentDuration/mDuration;
+           
+      if( x<=mCount || mCount<=0.0f )
+        {
+        interpolate(buffer,offset,x-(int)x);
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean interpolateMain(float[] buffer, int offset, long currentDuration, long step)
+    {
+    if( mDuration<=0.0f ) 
+      {
+      interpolate(buffer,offset,mCount-(int)mCount);
+      return false;
+      }
+     
+    float x = (float)currentDuration/mDuration;
+           
+    if( x<=mCount || mCount<=0.0f )
+      {
+      interpolate(buffer,offset,x-(int)x);
+        
+      if( currentDuration+step > mDuration*mCount && mCount>0.0f )
+        {
+        interpolate(buffer,offset,mCount-(int)mCount);
+        return true;
+        }
+      }
+    
+    return false;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// internal debugging only!
+  
+  String print()
+    {
+    return "duration="+mDuration+" count="+mCount+" Noise="+mNoise+" numVectors="+numPoints+" mMode="+mMode;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  abstract void interpolate(float[] buffer, int offset, float time);
+  abstract void createNoise();
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the mode of the interpolation to Loop, Path or Jump.
+ * <ul>
+ * <li>Loop is when we go from the first point all the way to the last, and the back to the first through 
+ * the shortest way.
+ * <li>Path is when we come back from the last point back to the first the same way we got there.
+ * <li>Jump is when we go from first to last and then jump back to the first.
+ * </ul>
+ * 
+ * @param mode {@link Interpolator.MODE_LOOP}, {@link Interpolator.MODE_PATH} or {@link Interpolator.MODE_JUMP}.
+ */
+
+  public void setMode(int mode)
+    {
+    mMode = mode;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the number of FloatNDs this Interpolator has been fed with.
+ *   
+ * @return the number of FloatNDs we are currently interpolating through.
+ */
+  public synchronized int getNumPoints()
+    {
+    return numPoints;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Controls how many times we want to interpolate.
+ * <p>
+ * Count equal to 1 means 'go from the first FloatND to the last and back'. Does not have to be an 
+ * integer - i.e. count=1.5 would mean 'start at the first Point, go to the last, come back to the first, 
+ * go to the last again and stop'.
+ * Count<=0 means 'go on interpolating indefinitely'.
+ * 
+ * @param count the number of times we want to interpolate between our collection of FloatNDs. 
+ */
+  public void setCount(float count)
+    {
+    mCount = count;  
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the time it takes to do one full interpolation.
+ * 
+ * @param duration Time, in milliseconds, it takes to do one full interpolation, i.e. go from the first 
+ *                 Point to the last and back. 
+ */
+  
+  public void setDuration(long duration)
+    {
+    mDuration = duration;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the 'smoothness' of interpolation. 
+ * <p>
+ * When Noise=0 (the default), we interpolate between our Points through the most smooth path possible. 
+ * Increasing noise makes the Interpolator increasingly deviate from this path, pseudo-randomly speeding 
+ * up and slowing down, etc.
+ * 
+ * @param noise The noise level. Permitted range: 0 <= noise <= 1.
+ */
+  
+  public void setNoise(float noise)
+    {
+    if( mNoise==0.0f && noise != 0.0f )  
+      createNoise();
+   
+    if( mNoise<0.0f ) mNoise = 0.0f;
+    if( mNoise>1.0f ) mNoise = 1.0f;
+   
+    mNoise = noise;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// end of DistortedInterpolator
+  }
diff --git a/src/main/java/org/distorted/library/Interpolator1D.java b/src/main/java/org/distorted/library/Interpolator1D.java
new file mode 100644
index 0000000..3e20ffc
--- /dev/null
+++ b/src/main/java/org/distorted/library/Interpolator1D.java
@@ -0,0 +1,480 @@
+package org.distorted.library;
+
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/** 
+* A 1-dimensional implementation of the Interpolator class to interpolate between a list 
+* of Float1Ds.
+*/
+
+public class Interpolator1D extends Interpolator 
+  {
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// the coefficients of the X(t) polynomials: X(t) = ax*T^3 + bx*T^2 + cx*t + dx  etc.
+// (x) is the vector tangent to the path.
+// (vx) is the original vector from vv (copied here so when interpolating we can see if it is 
+// still valid and if not - rebuild the Cache
+   
+  private class VectorCache
+    {
+    float ax, bx, cx, dx;
+   
+    float x;
+    float vx;
+    }
+  
+  private class VectorNoise
+    {
+    float[] nx;
+   
+    public VectorNoise()
+      {
+      nx = new float[NUM_NOISE]; 
+      nx[0] = mRnd.nextFloat();
+      for(int i=1; i<NUM_NOISE; i++) nx[i] = nx[i-1]+mRnd.nextFloat();
+      float sum = nx[NUM_NOISE-1] + mRnd.nextFloat();
+      for(int i=0; i<NUM_NOISE; i++) nx[i] /=sum;
+      }
+    }
+  
+  private Vector<VectorCache> vc;
+  private VectorCache tmp1, tmp2;
+ 
+  private Vector<Float1D> vv;
+  private Float1D prev, curr, next;
+ 
+  private Vector<VectorNoise> vn;
+  private VectorNoise tmpN;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void createNoise()
+    {
+    if( vn==null )
+      {
+      vn = new Vector<VectorNoise>();
+      for(int i=0; i<numPoints; i++) vn.add(new VectorNoise());
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// no array bounds checking!
+  
+  private void vec(int c)
+    {
+    int p = c>0 ? c-1: numPoints-1;
+    int n = c<numPoints-1 ? c+1: 0;
+    
+    prev = vv.elementAt(p);
+    curr = vv.elementAt(c);
+    next = vv.elementAt(n);
+
+    tmp1 = vc.elementAt(c);
+    
+    float px = curr.x - prev.x;
+    float nx = next.x - curr.x;
+     
+    float d = nx*nx;
+    
+    if( d>0 )
+      {
+      float q = (float)Math.sqrt((px*px)/d);
+      
+      if( q>1 )
+        {
+        tmp1.x = nx+px/q;
+        }
+      else
+        {
+        tmp1.x = px+nx*q;
+        }
+      }
+    else
+      {
+      tmp1.x = 0.0f;
+      }
+    }
+      
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private void recomputeCache()
+    {  
+    if( numPoints==1 )
+      {
+      tmp1= vc.elementAt(0);
+      curr= vv.elementAt(0);
+        
+      tmp1.ax = 0.0f;
+      tmp1.bx = 0.0f;
+      tmp1.cx = curr.x;
+      tmp1.dx = 0.0f;
+      }
+    else if( numPoints==2 )
+      {
+      tmp1= vc.elementAt(0);
+      tmp2= vc.elementAt(1);
+      curr= vv.elementAt(0);
+      next= vv.elementAt(1);
+          
+      tmp1.ax = 0.0f;
+      tmp1.bx = 0.0f;
+      tmp1.cx = next.x - curr.x;
+      tmp1.dx = curr.x;
+      
+      tmp2.ax = 0.0f;
+      tmp2.bx = 0.0f;
+      tmp2.cx = curr.x - next.x;
+      tmp2.dx = next.x;
+      }
+    else
+      {
+      int i, n;  
+         
+      for(i=0; i<numPoints; i++) vec(i);
+   
+      for(i=0; i<numPoints; i++)
+        {
+        n = i<numPoints-1 ? i+1:0;  
+      
+        tmp1= vc.elementAt(i);
+        tmp2= vc.elementAt(n);
+        curr= vv.elementAt(i);
+        next= vv.elementAt(n);
+    
+        tmp1.vx = curr.x;
+        
+        tmp1.ax =  2*curr.x +   tmp1.x - 2*next.x + tmp2.x;
+        tmp1.bx = -3*curr.x - 2*tmp1.x + 3*next.x - tmp2.x;
+        tmp1.cx = tmp1.x;
+        tmp1.dx = curr.x;
+        }
+      }
+   
+    cacheDirty = false;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float noise(float time,int vecNum)
+    {
+    float lower, upper, len;  
+    float d = time*(NUM_NOISE+1);
+    int index = (int)d;
+    if( index>=NUM_NOISE+1 ) index=NUM_NOISE;
+    tmpN = vn.elementAt(vecNum);
+   
+    if( index==0 )
+      {
+      len = 1.0f/(NUM_NOISE+1);  
+      return (len + mNoise*(tmpN.nx[0]-len))*d;
+      }
+    if( index==NUM_NOISE )
+      {
+      len = ((float)NUM_NOISE)/(NUM_NOISE+1);
+      lower = len + mNoise*(tmpN.nx[NUM_NOISE-1]-len);   
+      return (1.0f-lower)*(d-NUM_NOISE) + lower;   
+      }
+   
+    len = ((float)index)/(NUM_NOISE+1);
+    lower = len + mNoise*(tmpN.nx[index-1]-len);   
+    len = ((float)index+1)/(NUM_NOISE+1); 
+    upper = len + mNoise*(tmpN.nx[index  ]-len);
+            
+    return (upper-lower)*(d-index) + lower; 
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default constructor.
+ */
+  public Interpolator1D()
+    {
+    vv = new Vector<Float1D>();
+    vc = new Vector<VectorCache>();
+    vn = null;
+    numPoints = 0;
+    cacheDirty = false;
+    mMode = MODE_LOOP;
+    mDuration = 0;
+    mCount = 0.5f;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the location'th Float1D. 
+ *   
+ * @param location the index of the Point we are interested in.
+ * @return The Float1D, if 0<=location&lt;getNumPoints(), or null otherwise. 
+ */
+  public synchronized Float1D getPoint(int location)
+    {
+    return (location>=0 && location<numPoints) ? vv.elementAt(location) : null;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resets the location'th Point.
+ * 
+ * @param location the index of the Point we are setting.
+ * @param x New value of its first float.
+ */
+  public synchronized void setPoint(int location, float x)
+    {
+    if( location>=0 && location<numPoints )
+      {
+      curr = vv.elementAt(location);
+   
+      if( curr!=null )
+        {
+        curr.set(x);
+        cacheDirty=true;
+        }
+      }
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float1D to the end of our list of Points to interpolate through.
+ * <p>   
+ * Only a reference to the Point gets added to the List; this means that one can add a Point 
+ * here, and later on {@link Float1D#set(float)} it to some new value and the change will
+ * be seamlessly reflected in the interpolated path.  
+ * <p>
+ * A Point can be added multiple times.
+ *   
+ * @param v The Point to add.
+ */
+  public synchronized void add(Float1D v)
+    {
+    if( v!=null )
+      {
+      vv.add(v);
+     
+      if( vn!=null ) vn.add(new VectorNoise());
+       
+      switch(numPoints)
+        {
+        case 0: 
+        case 1: break;
+        case 2: vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                cacheDirty = true;
+                break;
+        default:vc.add(new VectorCache());
+                cacheDirty = true;
+        }
+     
+      numPoints++;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float1D to the location'th place in our List of Points to interpolate through.  
+ *   
+ * @param location Index in our List to add the new Point at.
+ * @param v The Point to add.
+ */
+  public synchronized void add(int location, Float1D v)
+    {
+    if( v!=null )
+      {
+      vv.add(location, v);
+      
+      if( vn!=null ) vn.add(new VectorNoise());
+             
+      switch(numPoints)
+        {
+        case 0:
+        case 1: break;
+        case 2: vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                cacheDirty = true;
+                break;
+        default:vc.add(location,new VectorCache());
+                cacheDirty = true;
+        }
+      
+      numPoints++;
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all occurrences of Point v from the List of Points to interpolate through.  
+ * 
+ * @param v The Point to remove.
+ * @return <code>true</code> if we have removed at least one Point.
+ */
+  public synchronized boolean remove(Float1D v)
+    {
+    int n = vv.indexOf(v);
+    boolean found = false;
+   
+    while( n>=0 ) 
+      {
+      vv.remove(n);
+     
+      if( vn!=null ) vn.remove(0);
+     
+      switch(numPoints)
+        {
+        case 0:
+        case 1:
+        case 2: break;
+        case 3: vc.removeAllElements();
+                break;
+        default:vc.remove(n);
+                cacheDirty=true;
+        }
+
+      numPoints--;
+      found = true;
+      n = vv.indexOf(v);
+      }
+   
+    return found;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes a location'th Point from the List of Points we interpolate through.
+ * 
+ * @param location index of the Point we want to remove. 
+ * @return <code>true</code> if location is valid, i.e. if 0<=location&lt;getNumPoints().
+ */
+  public synchronized boolean remove(int location)
+    {
+    if( location>=0 && location<numPoints ) 
+      {
+      vv.removeElementAt(location);
+      
+      if( vn!=null ) vn.remove(0);
+     
+      switch(numPoints)
+        {
+        case 0:
+        case 1: 
+        case 2: break;
+        case 3: vc.removeAllElements();
+                break;
+        default:vc.removeElementAt(location);
+        }
+
+      numPoints--;
+      cacheDirty = true; 
+      return true;
+      }
+
+   return false;
+   }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all Points.
+ */
+  public synchronized void removeAll()
+    {
+    numPoints = 0;
+    vv.removeAllElements();
+    vc.removeAllElements();
+    cacheDirty = false;
+   
+    if( vn!=null ) vn.removeAllElements();
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Writes the results of interpolation between the Points at time 'time' to the passed float buffer.
+ * <p>
+ * Since this is a 1-dimensional Interpolator, the resulting interpolated Float1D gets written
+ * to a single location in the buffer: buffer[offset]. 
+ * 
+ * @param buffer Float buffer we will write the resulting Float1D to.
+ * @param offset Offset in the buffer where to write the result.
+ * @param time Time of interpolation. Time=0.0 would return the first Point, Time=0.5 - the last,
+ *             time=1.0 - the first again, and time 0.1 would be 1/5 of the way between the first and the last Points.
+ */
+  public synchronized void interpolate(float[] buffer, int offset, float time)
+    {
+    switch(numPoints)
+      {
+      case 0: buffer[offset] = 0.0f;
+              break;
+      case 1: curr = vv.elementAt(0);
+              buffer[offset] = curr.x;
+              break;
+      case 2: curr = vv.elementAt(0);
+              next = vv.elementAt(1);
+             
+              if( mMode==MODE_LOOP || mMode==MODE_PATH ) time = (time>0.5f ? 2-2*time : 2*time);
+             
+              if( vn!=null )
+                {
+                time = noise(time,0);
+                }
+             
+              buffer[offset] = (next.x-curr.x)*time + curr.x;
+              break;
+      default:float t = time;
+            
+              switch(mMode)
+                {
+                case MODE_LOOP: time = time*numPoints;
+                                break;
+                case MODE_PATH: time = (time<=0.5f) ? 2*time*(numPoints-1) : 2*(1-time)*(numPoints-1);
+                                break;
+                case MODE_JUMP: time = time*(numPoints-1);
+                                break;
+                }
+      
+              int vecCurr = (int)time;
+              time = time-vecCurr;
+      
+              if( vecCurr>=0 && vecCurr<numPoints )
+                {
+                if( cacheDirty ) recomputeCache();  // recompute cache if we have added or remove vectors since last computation
+                else if( mVecCurr!= vecCurr )       // ...or if we have just passed a vector and the vector we are currently flying to has changed
+                  {
+                  int vecNext;   
+                  mVecCurr = vecCurr;
+                                
+                  switch(mMode)
+                    {
+                    case MODE_LOOP: vecNext = vecCurr==numPoints-1 ? 0:vecCurr+1; 
+                                    break;
+                    case MODE_PATH: if( t<0.5f ) vecNext = vecCurr==numPoints-1 ? numPoints-2: vecCurr+1;  
+                                    else         vecNext = vecCurr==0 ? 1 : vecCurr-1;  
+                                    break;
+                    case MODE_JUMP: vecNext = vecCurr==numPoints-1 ? 1:vecCurr+1;
+                                    break;
+                    default       : vecNext = 0;                
+                    }
+              
+                  next = vv.elementAt(vecNext);
+                  tmp2 = vc.elementAt(vecNext);
+              
+                  if( tmp2.vx!=next.x ) recomputeCache();
+                  }
+             
+                if( vn!=null )
+                  {
+                  time = noise(time,vecCurr);
+                  }
+            
+                tmp1 = vc.elementAt(vecCurr);
+                buffer[offset] = ((tmp1.ax*time+tmp1.bx)*time+tmp1.cx)*time+tmp1.dx;
+                break;
+                }
+        }
+     }  
+  
+  }
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
diff --git a/src/main/java/org/distorted/library/Interpolator2D.java b/src/main/java/org/distorted/library/Interpolator2D.java
new file mode 100644
index 0000000..764cbfe
--- /dev/null
+++ b/src/main/java/org/distorted/library/Interpolator2D.java
@@ -0,0 +1,532 @@
+package org.distorted.library;
+
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/** 
+* A 2-dimensional implementation of the Interpolator class to interpolate between a list 
+* of Float2Ds.
+*/
+
+public class Interpolator2D extends Interpolator 
+  {
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// the coefficients of the X(t), Y(t) polynomials: X(t) = ax*T^3 + bx*T^2 + cx*t + dx  etc.
+// (x,y) is the vector tangent to the path.
+// (vx,vy) is the original vector from vv (copied here so when interpolating we can see if it is 
+// still valid and if not - rebuild the Cache
+  
+  private class VectorCache
+    {
+    float ax, bx, cx, dx;
+    float ay, by, cy, dy;
+   
+    float x,y;
+    float vx,vy;
+    }
+  
+  private class VectorNoise
+    {    
+    float[] nx;
+    float[] ny;
+   
+    public VectorNoise()
+      {
+      nx = new float[NUM_NOISE]; 
+      nx[0] = mRnd.nextFloat();
+      for(int i=1; i<NUM_NOISE; i++) nx[i] = nx[i-1]+mRnd.nextFloat();
+      float sum = nx[NUM_NOISE-1] + mRnd.nextFloat();
+      for(int i=0; i<NUM_NOISE; i++) nx[i] /=sum;
+     
+      ny = new float[NUM_NOISE];
+      for(int i=0; i<NUM_NOISE; i++) ny[i] = mRnd.nextFloat()-0.5f;
+      }
+    }
+    
+  private Vector<VectorCache> vc;
+  private VectorCache tmp1, tmp2;
+   
+  private Vector<Float2D> vv;
+  private Float2D prev, curr, next;
+ 
+  private Vector<VectorNoise> vn;
+  private VectorNoise tmpN;
+  
+  private float mFactor;
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void createNoise()
+    {
+    if( vn==null )
+      {
+      vn = new Vector<VectorNoise>();
+      for(int i=0; i<numPoints; i++) vn.add(new VectorNoise());
+      }
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// no array bounds checking!
+  
+  private void vec(int c)
+    {
+    int p = c>0 ? c-1: numPoints-1;
+    int n = c<numPoints-1 ? c+1: 0;
+    
+    prev = vv.elementAt(p);
+    curr = vv.elementAt(c);
+    next = vv.elementAt(n);
+
+    tmp1 = vc.elementAt(c);
+    
+    float px = curr.x - prev.x;
+    float py = curr.y - prev.y;
+    float nx = next.x - curr.x;
+    float ny = next.y - curr.y;
+     
+    float d = nx*nx+ny*ny;
+    
+    if( d>0 )
+      {
+      float q = (float)Math.sqrt((px*px+py*py)/d);
+      
+      if( q>1 )
+        {
+        tmp1.x = nx+px/q;
+        tmp1.y = ny+py/q;
+        }
+      else
+        {
+        tmp1.x = px+nx*q;
+        tmp1.y = py+ny*q;
+        }
+      }
+    else
+      {
+      tmp1.x = 0.0f;
+      tmp1.y = 0.0f;
+      }
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private void recomputeCache()
+    {  
+    if( numPoints==1 )
+      {
+      tmp1= vc.elementAt(0);
+      curr= vv.elementAt(0);
+              
+      tmp1.ax = tmp1.ay = 0.0f;
+      tmp1.bx = tmp1.by = 0.0f;
+      tmp1.cx = curr.x;
+      tmp1.cy = curr.y;
+      tmp1.dx = tmp1.dy = 0.0f;
+      }
+    else if( numPoints==2 )
+      {
+      tmp1= vc.elementAt(0);
+      tmp2= vc.elementAt(1);
+      curr= vv.elementAt(0);
+      next= vv.elementAt(1);
+          
+      tmp1.ax = tmp1.ay = 0.0f;
+      tmp1.bx = tmp1.by = 0.0f;
+      tmp1.cx = next.x - curr.x;
+      tmp1.cy = next.y - curr.y;
+      tmp1.dx = curr.x;
+      tmp1.dy = curr.y;
+      
+      tmp2.ax = tmp2.ay = 0.0f;
+      tmp2.bx = tmp2.by = 0.0f;
+      tmp2.cx = curr.x - next.x;
+      tmp2.cy = curr.y - next.y;
+      tmp2.dx = next.x;
+      tmp2.dy = next.y;
+      }
+    else
+      {
+      int i, n;  
+         
+      for(i=0; i<numPoints; i++) vec(i);
+   
+      for(i=0; i<numPoints; i++)
+        {
+        n = i<numPoints-1 ? i+1:0;  
+      
+        tmp1= vc.elementAt(i);
+        tmp2= vc.elementAt(n);
+        curr= vv.elementAt(i);
+        next= vv.elementAt(n);
+      
+        tmp1.vx = curr.x;
+        tmp1.vy = curr.y;
+        
+        tmp1.ax =  2*curr.x +   tmp1.x - 2*next.x + tmp2.x;
+        tmp1.bx = -3*curr.x - 2*tmp1.x + 3*next.x - tmp2.x;
+        tmp1.cx = tmp1.x;
+        tmp1.dx = curr.x;
+      
+        tmp1.ay =  2*curr.y +   tmp1.y - 2*next.y + tmp2.y;
+        tmp1.by = -3*curr.y - 2*tmp1.y + 3*next.y - tmp2.y;
+        tmp1.cy = tmp1.y;
+        tmp1.dy = curr.y;
+        }
+      }
+    
+    cacheDirty = false;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float noise(float time,int vecNum)
+    {
+    float lower, upper, len;  
+    float d = time*(NUM_NOISE+1);
+    int index = (int)d;
+    if( index>=NUM_NOISE+1 ) index=NUM_NOISE;
+    tmpN = vn.elementAt(vecNum);
+   
+    float x = d-index;
+    x = x*x*(3-2*x);
+   
+    switch(index)
+      {
+      case 0        : mFactor = mNoise*tmpN.ny[0]*x;  
+                      return time + mNoise*(d*tmpN.nx[0]-time);                
+      case NUM_NOISE: mFactor= mNoise*tmpN.ny[NUM_NOISE-1]*(1-x);
+                      len = ((float)NUM_NOISE)/(NUM_NOISE+1);
+                      lower = len + mNoise*(tmpN.nx[NUM_NOISE-1]-len);  
+                      return (1.0f-lower)*(d-NUM_NOISE) + lower;
+      default       : float yb = tmpN.ny[index  ];
+                      float ya = tmpN.ny[index-1];
+                      mFactor  = mNoise*((yb-ya)*x+ya);
+   
+                      len = ((float)index)/(NUM_NOISE+1);
+                      lower = len + mNoise*(tmpN.nx[index-1]-len);   
+                      len = ((float)index+1)/(NUM_NOISE+1); 
+                      upper = len + mNoise*(tmpN.nx[index  ]-len);
+            
+                      return (upper-lower)*(d-index) + lower; 
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default constructor.
+ */
+  public Interpolator2D()
+    {
+    vv = new Vector<Float2D>();
+    vc = new Vector<VectorCache>();
+    vn = null;
+    numPoints = 0;
+    cacheDirty = false;
+    mMode = MODE_LOOP;
+    mDuration = 0;
+    mCount = 0.5f;
+    mNoise = 0.0f;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the location'th Float2D. 
+ *   
+ * @param location the index of the Point we are interested in.
+ * @return The Float2D, if 0<=location&lt;getNumPoints(), or null otherwise. 
+ */  
+  public synchronized Float2D getPoint(int location)
+    {
+    return (location>=0 && location<numPoints) ? vv.elementAt(location) : null;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resets the location'th Point.
+ * 
+ * @param location the index of the Point we are setting.
+ * @param x New value of its first float.
+ */
+  public synchronized void setPoint(int location, float x, float y)
+    {
+    if( location>=0 && location<numPoints )
+      {
+      curr = vv.elementAt(location);
+   
+      if( curr!=null )
+        {
+        curr.set(x,y);
+        cacheDirty=true;
+        }
+      }
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float2D to the end of our list of Points to interpolate through.
+ * <p>   
+ * Only a reference to the Point gets added to the List; this means that one can add a Point 
+ * here, and later on {@link Float2D#set(float,float)} it to some new value and the change 
+ * will be seamlessly reflected in the interpolated path.  
+ * <p>
+ * A Point can be added multiple times.
+ *   
+ * @param v The Point to add.
+ */  
+  public synchronized void add(Float2D v)
+    {
+    if( v!=null )
+      {
+      vv.add(v);
+     
+      if( vn!=null ) vn.add(new VectorNoise());
+       
+      switch(numPoints)
+        {
+        case 0:
+        case 1: break;
+        case 2: vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                break;
+        default:vc.add(new VectorCache());
+        }
+     
+      numPoints++;
+      cacheDirty = true;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float2D to the location'th place in our List of Points to interpolate through.  
+ *   
+ * @param location Index in our List to add the new Point at.
+ * @param v The Point to add.
+ */  
+  public synchronized void add(int location, Float2D v)
+    {
+    if( v!=null )
+      {
+      vv.add(location, v);
+      
+      if( vn!=null ) vn.add(new VectorNoise());
+      
+      switch(numPoints)
+        {
+        case 0:
+        case 1: break;
+        case 2: vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                break;
+        default:vc.add(location,new VectorCache());
+        }
+      
+      numPoints++;
+      cacheDirty = true;
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all occurrences of Point v from the List of Points to interpolate through.  
+ * 
+ * @param v The Point to remove.
+ * @return <code>true</code> if we have removed at least one Point.
+ */
+  public synchronized boolean remove(Float2D v)
+    {
+    int n = vv.indexOf(v);
+    boolean found = false;
+   
+    while( n>=0 ) 
+      {
+      vv.remove(n);
+     
+      if( vn!=null ) vn.remove(0);
+     
+      switch(numPoints)
+        {
+        case 0:
+        case 1: 
+        case 2: break;
+        case 3: vc.removeAllElements();
+                break;
+        default:vc.remove(n);
+        }
+     
+      numPoints--;
+      found = true;
+      n = vv.indexOf(v);
+      }
+   
+    if( found ) 
+      {
+      cacheDirty=true;
+      }
+   
+    return found;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes a location'th Point from the List of Points we interpolate through.
+ * 
+ * @param location index of the Point we want to remove. 
+ * @return <code>true</code> if location is valid, i.e. if 0<=location&lt;getNumPoints().
+ */
+  public synchronized boolean remove(int location)
+    {
+    if( location>=0 && location<numPoints ) 
+      {
+      vv.removeElementAt(location);
+      
+      if( vn!=null ) vn.remove(0);
+      
+      switch(numPoints)
+        {
+        case 0:
+        case 1: 
+        case 2: break;
+        case 3: vc.removeAllElements();
+                break;
+        default:vc.removeElementAt(location);
+        }
+
+      numPoints--;
+      cacheDirty = true; 
+      return true;
+      }
+
+   return false;
+   }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all Points.
+ */
+  public synchronized void removeAll()
+    {
+    numPoints = 0;
+    vv.removeAllElements();
+    vc.removeAllElements();
+    cacheDirty = false;
+   
+    if( vn!=null ) vn.removeAllElements();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Writes the results of interpolation between the Points at time 'time' to the passed float buffer.
+ * <p>
+ * Since this is a 2-dimensional Interpolator, the resulting interpolated Float2D gets written
+ * to two locations in the buffer: buffer[offset] and buffer[offset+1]. 
+ * 
+ * @param buffer Float buffer we will write the resulting Float2D to.
+ * @param offset Offset in the buffer where to write the result.
+ * @param time Time of interpolation. Time=0.0 would return the first Point, Time=0.5 - the last,
+ *             time=1.0 - the first again, and time 0.1 would be 1/5 of the way between the first and the last Points.
+ */  
+  public synchronized void interpolate(float[] buffer, int offset, float time)
+    {
+    switch(numPoints)
+      {
+      case 0: buffer[offset  ] = 0.0f;
+              buffer[offset+1] = 0.0f;
+              break;
+      case 1: curr = vv.elementAt(0);
+              buffer[offset  ] = curr.x;
+              buffer[offset+1] = curr.y;
+              break;
+      case 2: curr = vv.elementAt(0);
+              next = vv.elementAt(1);
+               
+              if( mMode==MODE_LOOP || mMode==MODE_PATH ) time = (time>0.5f ? 2-2*time : 2*time);
+             
+              if( vn!=null )
+                {
+                time = noise(time,0);
+              
+                float dx2 = next.x-curr.x;
+                float dy2 = next.y-curr.y;
+   
+                buffer[offset  ] = dx2*time + curr.x +dy2*mFactor;
+                buffer[offset+1] = dy2*time + curr.y -dx2*mFactor;
+                }
+              else
+                {
+                buffer[offset  ] = (next.x-curr.x)*time + curr.x;
+                buffer[offset+1] = (next.y-curr.y)*time + curr.y;
+                }
+              
+              break;
+      default:float t = time;
+        
+              switch(mMode)
+                {
+                case MODE_LOOP: time = time*numPoints;
+                                break;
+                case MODE_PATH: time = (time<=0.5f) ? 2*time*(numPoints-1) : 2*(1-time)*(numPoints-1);
+                                break;
+                case MODE_JUMP: time = time*(numPoints-1);
+                                break;
+                }
+            
+              int vecCurr = (int)time;
+              time = time-vecCurr;
+      
+              if( vecCurr>=0 && vecCurr<numPoints )
+                { 
+                if( cacheDirty ) recomputeCache();    // recompute cache if we have added or remove vectors since last computation
+                else if( mVecCurr!= vecCurr )         // ...or if we have just passed a vector and the vector we are currently flying to has changed
+                  {
+                  int vecNext;   
+                  mVecCurr = vecCurr;
+                                
+                  switch(mMode)
+                    {
+                    case MODE_LOOP: vecNext = vecCurr==numPoints-1 ? 0:vecCurr+1; 
+                                    break;
+                    case MODE_PATH: if( t<0.5f ) vecNext = vecCurr==numPoints-1 ? numPoints-2: vecCurr+1;  
+                                    else         vecNext = vecCurr==0 ? 1 : vecCurr-1;  
+                                    break;
+                    case MODE_JUMP: vecNext = vecCurr==numPoints-1 ? 1:vecCurr+1;
+                                    break;
+                    default       : vecNext = 0;                
+                    }
+              
+                  next = vv.elementAt(vecNext);
+                  tmp2 = vc.elementAt(vecNext);
+              
+                  if( tmp2.vx!=next.x || tmp2.vy!=next.y ) recomputeCache();
+                  }
+              
+                if( vn!=null )
+                  {
+                  time = noise(time,vecCurr);
+                  tmp1 = vc.elementAt(vecCurr);
+               
+                  float dx2 = (3*tmp1.ax*time+2*tmp1.bx)*time + tmp1.cx;
+                  float dy2 = (3*tmp1.ay*time+2*tmp1.by)*time + tmp1.cy;
+                 
+                  buffer[offset  ]= ((tmp1.ax*time+tmp1.bx)*time+tmp1.cx)*time+tmp1.dx +dy2*mFactor;
+                  buffer[offset+1]= ((tmp1.ay*time+tmp1.by)*time+tmp1.cy)*time+tmp1.dy -dx2*mFactor;
+                  } 
+                else
+                  {
+                  tmp1 = vc.elementAt(vecCurr);
+                  buffer[offset  ]= ((tmp1.ax*time+tmp1.bx)*time+tmp1.cx)*time+tmp1.dx;
+                  buffer[offset+1]= ((tmp1.ay*time+tmp1.by)*time+tmp1.cy)*time+tmp1.dy;
+                  }
+                
+                break;
+                }
+      }
+    }  
+  }
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
diff --git a/src/main/java/org/distorted/library/Interpolator3D.java b/src/main/java/org/distorted/library/Interpolator3D.java
new file mode 100644
index 0000000..5a0034f
--- /dev/null
+++ b/src/main/java/org/distorted/library/Interpolator3D.java
@@ -0,0 +1,650 @@
+package org.distorted.library;
+
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/** 
+* A 3-dimensional implementation of the Interpolator class to interpolate between a list 
+* of Float3Ds.
+*/
+
+public class Interpolator3D extends Interpolator 
+  {
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// the coefficients of the X(t), Y(t) and Z(t) polynomials: X(t) = ax*T^3 + bx*T^2 + cx*t + dx  etc.
+// (x,y,z) is the vector tangent to the path.
+// (vx,vy,vz) is the original vector from vv (copied here so when interpolating we can see if it is 
+// still valid and if not - rebuild the Cache
+  
+  private class VectorCache
+    {
+    float ax, bx, cx, dx;
+    float ay, by, cy, dy;
+    float az, bz, cz, dz;
+   
+    float x,y,z;
+    float vx,vy,vz;
+    }
+  
+  private class VectorNoise
+    {
+    float[] nx;
+    float[] ny;
+    float[] nz;
+   
+    public VectorNoise()
+      {
+      nx = new float[NUM_NOISE]; 
+      nx[0] = mRnd.nextFloat();
+      for(int i=1; i<NUM_NOISE; i++) nx[i] = nx[i-1]+mRnd.nextFloat();
+      float sum = nx[NUM_NOISE-1] + mRnd.nextFloat();
+      for(int i=0; i<NUM_NOISE; i++) nx[i] /=sum;
+     
+      ny = new float[NUM_NOISE];
+      for(int i=0; i<NUM_NOISE; i++) ny[i] = mRnd.nextFloat()-0.5f;
+     
+      nz = new float[NUM_NOISE];
+      for(int i=0; i<NUM_NOISE; i++) nz[i] = mRnd.nextFloat()-0.5f;  
+      }
+    }
+  
+  private Vector<VectorCache> vc;
+  private VectorCache tmp1, tmp2;
+
+  private Vector<Float3D> vv;
+  private Float3D prev, curr, next;
+  
+  private Vector<VectorNoise> vn;
+  private VectorNoise tmpN;
+  
+  private float mFactor1, mFactor2;  // used in Noise only. Those are noise factors; 1=noise of the (vec1X,vec1Y,vec1Z) vector; 2=noise of (vec2X,vec2Y,vec2Z)
+  private float vec1X,vec1Y,vec1Z;   // vector perpendicular to v(t) and in the same plane as v(t) and a(t) (for >2 points only, in case of 2 points this is calculated differently)
+  private float vec2X,vec2Y,vec2Z;   // vector perpendicular to v(t0 and to vec1.
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void createNoise()
+    {
+    if( vn==null )
+      {  
+      vn = new Vector<VectorNoise>();
+      for(int i=0; i<numPoints; i++) vn.add(new VectorNoise());
+      }
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// no array bounds checking!
+  
+  private void vec(int c)
+    {
+    int p = c>0 ? c-1: numPoints-1;
+    int n = c<numPoints-1 ? c+1: 0;
+    
+    prev = vv.elementAt(p);
+    curr = vv.elementAt(c);
+    next = vv.elementAt(n);
+
+    tmp1 = vc.elementAt(c);
+    
+    float px = curr.x - prev.x;
+    float py = curr.y - prev.y;
+    float pz = curr.z - prev.z;
+    float nx = next.x - curr.x;
+    float ny = next.y - curr.y;
+    float nz = next.z - curr.z;
+     
+    float d = nx*nx+ny*ny+nz*nz;
+    
+    if( d>0 )
+      {
+      float q = (float)Math.sqrt((px*px+py*py+pz*pz)/d);
+      
+      if( q>1 )
+        {
+        tmp1.x = nx+px/q;
+        tmp1.y = ny+py/q;
+        tmp1.z = nz+pz/q;
+        }
+      else
+        {
+        tmp1.x = px+nx*q;
+        tmp1.y = py+ny*q;
+        tmp1.z = pz+nz*q;
+        }
+      }
+    else
+      {
+      tmp1.x = 0.0f;
+      tmp1.y = 0.0f;
+      tmp1.z = 0.0f;  
+      }
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private void recomputeCache()
+    {  
+    if( numPoints==1 )
+      {
+      tmp1= vc.elementAt(0);
+      curr= vv.elementAt(0);
+        
+      tmp1.ax = tmp1.ay = tmp1.az = 0.0f;
+      tmp1.bx = tmp1.by = tmp1.bz = 0.0f;
+      tmp1.cx = curr.x;
+      tmp1.cy = curr.y;
+      tmp1.cz = curr.z;
+      tmp1.dx = tmp1.dy = tmp1.dz = 0.0f;
+      }
+    else if( numPoints==2 )
+      {
+      tmp1= vc.elementAt(0);
+      tmp2= vc.elementAt(1);
+      curr= vv.elementAt(0);
+      next= vv.elementAt(1);
+          
+      tmp1.ax = tmp1.ay = tmp1.az = 0.0f;
+      tmp1.bx = tmp1.by = tmp1.bz = 0.0f;
+      tmp1.cx = next.x - curr.x;
+      tmp1.cy = next.y - curr.y;
+      tmp1.cz = next.z - curr.z;
+      tmp1.dx = curr.x;
+      tmp1.dy = curr.y;
+      tmp1.dz = curr.z;
+      
+      tmp2.ax = tmp2.ay = tmp2.az = 0.0f;
+      tmp2.bx = tmp2.by = tmp2.bz = 0.0f;
+      tmp2.cx = curr.x - next.x;
+      tmp2.cy = curr.y - next.y;
+      tmp2.cz = curr.z - next.z;
+      tmp2.dx = next.x;
+      tmp2.dy = next.y;
+      tmp2.dz = next.z;
+      }
+    else
+      {
+      int i, n;  
+         
+      for(i=0; i<numPoints; i++) vec(i);
+   
+      for(i=0; i<numPoints; i++)
+        {
+        n = i<numPoints-1 ? i+1:0;  
+      
+        tmp1= vc.elementAt(i);
+        tmp2= vc.elementAt(n);
+        curr= vv.elementAt(i);
+        next= vv.elementAt(n);
+      
+        tmp1.vx = curr.x;
+        tmp1.vy = curr.y;
+        tmp1.vz = curr.z;
+        
+        tmp1.ax =  2*curr.x +   tmp1.x - 2*next.x + tmp2.x;
+        tmp1.bx = -3*curr.x - 2*tmp1.x + 3*next.x - tmp2.x;
+        tmp1.cx = tmp1.x;
+        tmp1.dx = curr.x;
+      
+        tmp1.ay =  2*curr.y +   tmp1.y - 2*next.y + tmp2.y;
+        tmp1.by = -3*curr.y - 2*tmp1.y + 3*next.y - tmp2.y;
+        tmp1.cy = tmp1.y;
+        tmp1.dy = curr.y;
+      
+        tmp1.az =  2*curr.z +   tmp1.z - 2*next.z + tmp2.z;
+        tmp1.bz = -3*curr.z - 2*tmp1.z + 3*next.z - tmp2.z;
+        tmp1.cz = tmp1.z;
+        tmp1.dz = curr.z;
+        }
+      }
+   
+    cacheDirty = false;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float noise(float time,int vecNum)
+    {
+    float lower, upper, len;  
+    float d = time*(NUM_NOISE+1);
+    int index = (int)d;
+    if( index>=NUM_NOISE+1 ) index=NUM_NOISE;
+    tmpN = vn.elementAt(vecNum);
+   
+    float t = d-index;
+    t = t*t*(3-2*t);
+   
+    switch(index)
+      {
+      case 0        : mFactor1 = mNoise*tmpN.ny[0]*t;
+                      mFactor2 = mNoise*tmpN.nz[0]*t;
+                      return time + mNoise*(d*tmpN.nx[0]-time);
+      case NUM_NOISE: mFactor1= mNoise*tmpN.ny[NUM_NOISE-1]*(1-t);
+                      mFactor2= mNoise*tmpN.nz[NUM_NOISE-1]*(1-t);
+                      len = ((float)NUM_NOISE)/(NUM_NOISE+1);
+                      lower = len + mNoise*(tmpN.nx[NUM_NOISE-1]-len);  
+                      return (1.0f-lower)*(d-NUM_NOISE) + lower;
+      default       : float ya,yb;
+                      yb = tmpN.ny[index  ];
+                      ya = tmpN.ny[index-1];
+                      mFactor1 = mNoise*((yb-ya)*t+ya);
+                      yb = tmpN.nz[index  ];
+                      ya = tmpN.nz[index-1];
+                      mFactor2 = mNoise*((yb-ya)*t+ya);
+   
+                      len = ((float)index)/(NUM_NOISE+1);
+                      lower = len + mNoise*(tmpN.nx[index-1]-len);   
+                      len = ((float)index+1)/(NUM_NOISE+1); 
+                      upper = len + mNoise*(tmpN.nx[index  ]-len);
+            
+                      return (upper-lower)*(d-index) + lower; 
+      }
+    }
+     
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// v is the speed vector (i.e. position p(t) differentiated by time)
+// a is the acceleration vector (differentiate once more)
+// now what we are doing is compute vec1{X,Y,Z} to be a vector perpendicular to v and in the same plane as both v and a.
+// vec2{X,Y,Z} would be (v)x(vec1).
+//  
+// vec1 = a-delta*v where delta = (v*a)/|v|^2   (see Gram-Schmidt)
+  
+  private void setUpVectors(float time,VectorCache vc)
+    {
+    if( vc!=null )
+      {
+      float vx = (3*vc.ax*time+2*vc.bx)*time+vc.cx;
+      float vy = (3*vc.ay*time+2*vc.by)*time+vc.cy;
+      float vz = (3*vc.az*time+2*vc.bz)*time+vc.cz;
+     
+      float ax = 6*vc.ax*time+2*vc.bx;
+      float ay = 6*vc.ay*time+2*vc.by;
+      float az = 6*vc.az*time+2*vc.bz;
+     
+      float v_sq = vx*vx+vy*vy+vz*vz;
+      float delta = (vx*ax+vy*ay+vz*az)/v_sq;
+     
+      vec1X = ax-delta*vx;
+      vec1Y = ay-delta*vy;
+      vec1Z = az-delta*vz;
+     
+      vec2X = vy*vec1Z-vz*vec1Y;
+      vec2Y = vz*vec1X-vx*vec1Z;
+      vec2Z = vx*vec1Y-vy*vec1X;
+     
+      float len1 = (float)Math.sqrt(v_sq/(vec1X*vec1X+vec1Y*vec1Y+vec1Z*vec1Z));
+      float len2 = (float)Math.sqrt(v_sq/(vec2X*vec2X+vec2Y*vec2Y+vec2Z*vec2Z));   
+     
+      vec1X*=len1;
+      vec1Y*=len1;
+      vec1Z*=len1;
+     
+      vec2X*=len2;
+      vec2Y*=len2;
+      vec2Z*=len2;
+      }
+    else
+      {
+      curr = vv.elementAt(0);
+      next = vv.elementAt(1); 
+     
+      float vx = (next.x-curr.x);
+      float vy = (next.y-curr.y);
+      float vz = (next.z-curr.z);
+     
+      float b = (float)Math.sqrt(vx*vx+vy*vy);
+     
+      if( b>0.0f )
+        {
+        vec1X = vx*vz/b;
+        vec1Y = vy*vz/b;
+        vec1Z = -b;
+      
+        vec2X = vy*vec1Z-vz*vec1Y;
+        vec2Y = vz*vec1X-vx*vec1Z;
+        vec2Z = vx*vec1Y-vy*vec1X;
+       
+        float len2 = (float)Math.sqrt((vx*vx+vy*vy+vz*vz)/(vec2X*vec2X+vec2Y*vec2Y+vec2Z*vec2Z));
+       
+        vec2X*=len2;
+        vec2Y*=len2;
+        vec2Z*=len2;
+        }
+      else
+        {
+        vec1X = vz;
+        vec1Y = 0.0f;
+        vec1Z = 0.0f;
+      
+        vec2X = 0.0f;
+        vec2Y = vz;
+        vec2Z = 0.0f;
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default constructor.
+ */
+  public Interpolator3D()
+    {
+    vv = new Vector<Float3D>();
+    vc = new Vector<VectorCache>();
+    vn = null;
+    numPoints = 0;
+    cacheDirty = false;
+    mMode = MODE_LOOP;
+    mDuration = 0;
+    mCount = 0.5f;
+    mNoise = 0.0f;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the location'th Float3D. 
+ *   
+ * @param location the index of the Point we are interested in.
+ * @return The Float3D, if 0<=location&lt;getNumPoints(), or null otherwise. 
+ */  
+  public synchronized Float3D getPoint(int location)
+    {
+    return (location>=0 && location<numPoints) ? vv.elementAt(location) : null;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resets the location'th Point.
+ * 
+ * @param location the index of the Point we are setting.
+ * @param x New value of its first float.
+ */
+  public synchronized void setPoint(int location, float x, float y, float z)
+    {
+    if( location>=0 && location<numPoints )
+      {
+      curr = vv.elementAt(location);
+   
+      if( curr!=null )
+        {
+        curr.set(x,y,z);
+        cacheDirty=true;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float3D to the end of our list of Points to interpolate through.
+ * <p>   
+ * Only a reference to the Point gets added to the List; this means that one can add a Point 
+ * here, and later on {@link Float3D#set(float,float,float)} it to some new value and the 
+ * change will be seamlessly reflected in the interpolated path.  
+ * <p>
+ * A Point can be added multiple times.
+ *   
+ * @param v The Point to add.
+ */    
+  public synchronized void add(Float3D v)
+    {
+    if( v!=null )
+      {
+      vv.add(v);
+        
+      if( vn!=null ) vn.add(new VectorNoise());
+       
+      switch(numPoints)
+        {
+        case 0: break;
+        case 1: setUpVectors(0.0f,null);
+                break;
+        case 2: vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                break;
+        default:vc.add(new VectorCache());
+        }
+
+      numPoints++;
+      cacheDirty = true;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float3D to the location'th place in our List of Points to interpolate through.  
+ *   
+ * @param location Index in our List to add the new Point at.
+ * @param v The Point to add.
+ */  
+  public synchronized void add(int location, Float3D v)
+    {
+    if( v!=null )
+      {
+      vv.add(location, v);
+      
+      if( vn!=null ) vn.add(new VectorNoise());
+      
+      switch(numPoints)
+        {
+        case 0: break;
+        case 1: setUpVectors(0.0f,null);
+                break;
+        case 2: vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                break;
+        default:vc.add(location,new VectorCache());
+        }
+
+      numPoints++;
+      cacheDirty = true;
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all occurrences of Point v from the List of Points to interpolate through.  
+ * 
+ * @param v The Point to remove.
+ * @return <code>true</code> if we have removed at least one Point.
+ */
+  public synchronized boolean remove(Float3D v)
+    {
+    int n = vv.indexOf(v);
+    boolean found = false;
+   
+    while( n>=0 ) 
+      {
+      vv.remove(n);
+     
+      if( vn!=null ) vn.remove(0);
+     
+      switch(numPoints)
+        {
+        case 0:
+        case 1: 
+        case 2: break;
+        case 3: vc.removeAllElements();
+                setUpVectors(0.0f,null);
+                break;
+        default:vc.remove(n);
+        }
+
+      numPoints--;
+      found = true;
+      n = vv.indexOf(v);
+      }
+   
+    if( found ) 
+      {
+      cacheDirty=true;
+      }
+   
+    return found;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes a location'th Point from the List of Points we interpolate through.
+ * 
+ * @param location index of the Point we want to remove. 
+ * @return <code>true</code> if location is valid, i.e. if 0<=location&lt;getNumPoints().
+ */
+  public synchronized boolean remove(int location)
+    {
+    if( location>=0 && location<numPoints ) 
+      {
+      vv.removeElementAt(location);
+       
+      if( vn!=null ) vn.remove(0);
+      
+      switch(numPoints)
+        {
+        case 0:
+        case 1: 
+        case 2: break;
+        case 3: vc.removeAllElements();
+                setUpVectors(0.0f,null);
+                break;
+        default:vc.removeElementAt(location);
+        }
+
+      numPoints--;
+      cacheDirty = true; 
+      return true;
+      }
+
+   return false;
+   }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all Points.
+ */
+  public synchronized void removeAll()
+    {
+    numPoints = 0;
+    vv.removeAllElements();
+    vc.removeAllElements();
+    cacheDirty = false;
+   
+    if( vn!=null ) vn.removeAllElements();
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Writes the results of interpolation between the Points at time 'time' to the passed float buffer.
+ * <p>
+ * Since this is a 3-dimensional Interpolator, the resulting interpolated Float3D gets written
+ * to three locations in the buffer: buffer[offset], buffer[offset+1] and buffer[offset+2]. 
+ * 
+ * @param buffer Float buffer we will write the resulting Float3D to.
+ * @param offset Offset in the buffer where to write the result.
+ * @param time Time of interpolation. Time=0.0 would return the first Point, Time=0.5 - the last,
+ *             time=1.0 - the first again, and time 0.1 would be 1/5 of the way between the first and the last Points.
+ */    
+  public synchronized void interpolate(float[] buffer, int offset, float time)
+    {  
+    switch(numPoints)
+      {
+      case 0: buffer[offset  ] = 0.0f;
+              buffer[offset+1] = 0.0f;
+              buffer[offset+2] = 0.0f;
+              break;
+      case 1: curr = vv.elementAt(0);
+              buffer[offset  ] = curr.x;
+              buffer[offset+1] = curr.y;
+              buffer[offset+2] = curr.z;
+              break;
+      case 2: curr = vv.elementAt(0);
+              next = vv.elementAt(1);
+             
+              if( mMode==MODE_LOOP || mMode==MODE_PATH ) time = (time>0.5f ? 2-2*time : 2*time);
+             
+              if( vn!=null )
+                {
+                time = noise(time,0);
+            
+                buffer[offset  ] = (next.x-curr.x)*time + curr.x + (vec1X*mFactor1 + vec2X*mFactor2);
+                buffer[offset+1] = (next.y-curr.y)*time + curr.y + (vec1Y*mFactor1 + vec2Y*mFactor2);
+                buffer[offset+2] = (next.z-curr.z)*time + curr.z + (vec1Z*mFactor1 + vec2Z*mFactor2); 
+                }
+              else
+                {
+                buffer[offset  ] = (next.x-curr.x)*time + curr.x;
+                buffer[offset+1] = (next.y-curr.y)*time + curr.y;
+                buffer[offset+2] = (next.z-curr.z)*time + curr.z;
+                }
+             
+              break;
+      default:float t = time;
+        
+              switch(mMode)
+                {
+                case MODE_LOOP: time = time*numPoints;
+                                break;
+                case MODE_PATH: time = (time<=0.5f) ? 2*time*(numPoints-1) : 2*(1-time)*(numPoints-1);
+                                break;
+                case MODE_JUMP: time = time*(numPoints-1);
+                                break;
+                }
+           
+              int vecCurr = (int)time;
+              time = time-vecCurr;
+      
+              if( vecCurr>=0 && vecCurr<numPoints )
+                {
+                if( cacheDirty ) recomputeCache();    // recompute cache if we have added or remove vectors since last computation
+                else if( mVecCurr!= vecCurr )         // ...or if we have just passed a vector and the vector we are currently flying to has changed
+                  {
+                  int vecNext;   
+                  mVecCurr = vecCurr;
+                       
+                  switch(mMode)
+                    {
+                    case MODE_LOOP: vecNext = vecCurr==numPoints-1 ? 0:vecCurr+1; 
+                                    break;
+                    case MODE_PATH: if( t<0.5f ) vecNext = vecCurr==numPoints-1 ? numPoints-2: vecCurr+1;  
+                                    else         vecNext = vecCurr==0 ? 1 : vecCurr-1;  
+                                    break;
+                    case MODE_JUMP: vecNext = vecCurr==numPoints-1 ? 1:vecCurr+1;
+                                    break;
+                    default       : vecNext = 0;                
+                    }
+              
+                  next = vv.elementAt(vecNext);
+                  tmp2 = vc.elementAt(vecNext);
+              
+                  if( tmp2.vx!=next.x || tmp2.vy!=next.y || tmp2.vz!=next.z ) recomputeCache();
+                  }
+            
+                tmp1 = vc.elementAt(vecCurr);
+               
+                if( vn!=null )
+                  {
+                  time = noise(time,vecCurr);
+              
+                  setUpVectors(time,tmp1);
+                 
+                  buffer[offset  ]= ((tmp1.ax*time+tmp1.bx)*time+tmp1.cx)*time+tmp1.dx + (vec1X*mFactor1 + vec2X*mFactor2);
+                  buffer[offset+1]= ((tmp1.ay*time+tmp1.by)*time+tmp1.cy)*time+tmp1.dy + (vec1Y*mFactor1 + vec2Y*mFactor2);
+                  buffer[offset+2]= ((tmp1.az*time+tmp1.bz)*time+tmp1.cz)*time+tmp1.dz + (vec1Z*mFactor1 + vec2Z*mFactor2);
+                  }
+                else
+                  {
+                  buffer[offset  ]= ((tmp1.ax*time+tmp1.bx)*time+tmp1.cx)*time+tmp1.dx;
+                  buffer[offset+1]= ((tmp1.ay*time+tmp1.by)*time+tmp1.cy)*time+tmp1.dy;
+                  buffer[offset+2]= ((tmp1.az*time+tmp1.bz)*time+tmp1.cz)*time+tmp1.dz;
+                  }
+               
+                break;
+                }
+       }
+     }  
+
+  }
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
diff --git a/src/main/java/org/distorted/library/Interpolator4D.java b/src/main/java/org/distorted/library/Interpolator4D.java
new file mode 100644
index 0000000..1a052c6
--- /dev/null
+++ b/src/main/java/org/distorted/library/Interpolator4D.java
@@ -0,0 +1,752 @@
+package org.distorted.library;
+
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/** 
+* A 4-dimensional implementation of the Interpolator class to interpolate between a list 
+* of Float4Ds.
+*/
+
+public class Interpolator4D extends Interpolator 
+  {
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// the coefficients of the X(t), Y(t), Z(t), W(t) polynomials: X(t) = ax*T^3 + bx*T^2 + cx*t + dx  etc.
+// (x,y,z,w) is the vector tangent to the path.
+// (vx,vy,vz,vw) is the original vector from vv (copied here so when interpolating we can see if it is 
+// still valid and if not - rebuild the Cache
+  
+  private class VectorCache
+    {
+    float ax, bx, cx, dx;
+    float ay, by, cy, dy;
+    float az, bz, cz, dz;
+    float aw, bw, cw, dw;
+   
+    float x,y,z,w;
+    float vx,vy,vz,vw;
+    }
+  
+  private class VectorNoise
+    {
+    float[] nx;
+    float[] ny;
+    float[] nz;
+    float[] nw;
+   
+    public VectorNoise()
+      {
+      nx = new float[NUM_NOISE]; 
+      nx[0] = mRnd.nextFloat();
+      for(int i=1; i<NUM_NOISE; i++) nx[i] = nx[i-1] + mRnd.nextFloat();
+      float sum = nx[NUM_NOISE-1] + mRnd.nextFloat();
+      for(int i=0; i<NUM_NOISE; i++) nx[i] /=sum;
+     
+      ny = new float[NUM_NOISE];
+      for(int i=0; i<NUM_NOISE; i++) ny[i] = mRnd.nextFloat()-0.5f;
+     
+      nz = new float[NUM_NOISE];
+      for(int i=0; i<NUM_NOISE; i++) nz[i] = mRnd.nextFloat()-0.5f;
+     
+      nw = new float[NUM_NOISE];
+      for(int i=0; i<NUM_NOISE; i++) nw[i] = mRnd.nextFloat()-0.5f;  
+      }
+    }
+  
+  private Vector<VectorCache> vc;
+  private VectorCache tmp1, tmp2;
+
+  private Vector<Float4D> vv;
+  private Float4D prev, curr, next;
+  
+  private Vector<VectorNoise> vn;
+  private VectorNoise tmpN;
+  
+  private float mFactor1, mFactor2, mFactor3; // used in Noise only. Those are noise factors; 1=noise of the (vec1X,vec1Y,vec1Z,vec1W) vector; 2=noise of (vec2X,vec2Y,vec2Z,vec2W) and same for vec3.
+  private float vec1X,vec1Y,vec1Z,vec1W;      // vector perpendicular to v(t) and in the same plane as v(t) and a(t) (for >2 points only, in case of 2 points this is calculated differently)
+  private float vec2X,vec2Y,vec2Z,vec2W;      // vector perpendicular to v(t) and to vec1.
+  private float vec3X,vec3Y,vec3Z,vec3W;      // vector perpendicular to v(t) and to vec1.
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void createNoise()
+    {
+    if( vn==null )
+      {  
+      vn = new Vector<VectorNoise>();
+      for(int i=0; i<numPoints; i++) vn.add(new VectorNoise());
+      }
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// no array bounds checking!
+  
+  private void vec(int c)
+    {
+    int p = c>0 ? c-1: numPoints-1;
+    int n = c<numPoints-1 ? c+1: 0;
+    
+    prev = vv.elementAt(p);
+    curr = vv.elementAt(c);
+    next = vv.elementAt(n);
+
+    tmp1 = vc.elementAt(c);
+    
+    float px = curr.x - prev.x;
+    float py = curr.y - prev.y;
+    float pz = curr.z - prev.z;
+    float pw = curr.w - prev.w;
+    float nx = next.x - curr.x;
+    float ny = next.y - curr.y;
+    float nz = next.z - curr.z;
+    float nw = next.w - curr.w;
+     
+    float d = nx*nx+ny*ny+nz*nz+nw*nw;
+    
+    if( d>0 )
+      {
+      float q = (float)Math.sqrt((px*px+py*py+pz*pz+pw*pw)/d);
+      
+      if( q>1 )
+        {
+        tmp1.x = nx+px/q;
+        tmp1.y = ny+py/q;
+        tmp1.z = nz+pz/q;
+        tmp1.w = nw+pw/q;
+        }
+      else
+        {
+        tmp1.x = px+nx*q;
+        tmp1.y = py+ny*q;
+        tmp1.z = pz+nz*q;
+        tmp1.w = pw+nw*q;
+        }
+      }
+    else
+      {
+      tmp1.x = 0.0f;
+      tmp1.y = 0.0f;
+      tmp1.z = 0.0f;  
+      tmp1.w = 0.0f;
+      }
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private void recomputeCache()
+    {  
+    if( numPoints==1 )
+      {
+      tmp1= vc.elementAt(0);
+      curr= vv.elementAt(0);
+        
+      tmp1.ax = tmp1.ay = tmp1.az = tmp1.aw = 0.0f;
+      tmp1.bx = tmp1.by = tmp1.bz = tmp1.bw = 0.0f;
+      tmp1.cx = curr.x;
+      tmp1.cy = curr.y;
+      tmp1.cz = curr.z;
+      tmp1.cw = curr.w;
+      tmp1.dx = tmp1.dy = tmp1.dz = tmp1.dw = 0.0f;
+      }
+    else if( numPoints==2 )
+      {
+      tmp1= vc.elementAt(0);
+      tmp2= vc.elementAt(1);
+      curr= vv.elementAt(0);
+      next= vv.elementAt(1);
+      
+      tmp1.ax = tmp1.ay = tmp1.az = tmp1.aw = 0.0f;
+      tmp1.bx = tmp1.by = tmp1.bz = tmp1.bw = 0.0f;
+      tmp1.cx = next.x - curr.x;
+      tmp1.cy = next.y - curr.y;
+      tmp1.cz = next.z - curr.z;
+      tmp1.cw = next.w - curr.w;
+      tmp1.dx = curr.x;
+      tmp1.dy = curr.y;
+      tmp1.dz = curr.z;
+      tmp1.dw = curr.w;
+      
+      tmp2.ax = tmp2.ay = tmp2.az = tmp2.aw = 0.0f;
+      tmp2.bx = tmp2.by = tmp2.bz = tmp2.bw = 0.0f;
+      tmp2.cx = curr.x - next.x;
+      tmp2.cy = curr.y - next.y;
+      tmp2.cz = curr.z - next.z;
+      tmp2.cw = curr.w - next.w;
+      tmp2.dx = next.x;
+      tmp2.dy = next.y;
+      tmp2.dz = next.z;
+      tmp2.dw = next.w;
+      }
+    else
+      {
+      int i, n;  
+      
+      for(i=0; i<numPoints; i++) vec(i);
+   
+      for(i=0; i<numPoints; i++)
+        {
+        n = i<numPoints-1 ? i+1:0;  
+      
+        tmp1= vc.elementAt(i);
+        tmp2= vc.elementAt(n);
+        curr= vv.elementAt(i);
+        next= vv.elementAt(n);
+      
+        tmp1.vx = curr.x;
+        tmp1.vy = curr.y;
+        tmp1.vz = curr.z;
+        tmp1.vw = curr.w;
+        
+        tmp1.ax =  2*curr.x +   tmp1.x - 2*next.x + tmp2.x;
+        tmp1.bx = -3*curr.x - 2*tmp1.x + 3*next.x - tmp2.x;
+        tmp1.cx = tmp1.x;
+        tmp1.dx = curr.x;
+      
+        tmp1.ay =  2*curr.y +   tmp1.y - 2*next.y + tmp2.y;
+        tmp1.by = -3*curr.y - 2*tmp1.y + 3*next.y - tmp2.y;
+        tmp1.cy = tmp1.y;
+        tmp1.dy = curr.y;
+      
+        tmp1.az =  2*curr.z +   tmp1.z - 2*next.z + tmp2.z;
+        tmp1.bz = -3*curr.z - 2*tmp1.z + 3*next.z - tmp2.z;
+        tmp1.cz = tmp1.z;
+        tmp1.dz = curr.z;
+        
+        tmp1.aw =  2*curr.w +   tmp1.w - 2*next.w + tmp2.w;
+        tmp1.bw = -3*curr.w - 2*tmp1.w + 3*next.w - tmp2.w;
+        tmp1.cw = tmp1.w;
+        tmp1.dw = curr.w;
+        }
+      }
+   
+    cacheDirty = false;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float noise(float time,int vecNum)
+    {
+    float lower, upper, len;  
+    float d = time*(NUM_NOISE+1);
+    int index = (int)d;
+    if( index>=NUM_NOISE+1 ) index=NUM_NOISE;
+    tmpN = vn.elementAt(vecNum);
+   
+    float t = d-index;
+    t = t*t*(3-2*t);
+   
+    switch(index)
+      {
+      case 0        : mFactor1 = mNoise*tmpN.ny[0]*t;
+                      mFactor2 = mNoise*tmpN.nz[0]*t;
+                      mFactor3 = mNoise*tmpN.nw[0]*t;
+                      return time + mNoise*(d*tmpN.nx[0]-time);
+      case NUM_NOISE: mFactor1= mNoise*tmpN.ny[NUM_NOISE-1]*(1-t);
+                      mFactor2= mNoise*tmpN.nz[NUM_NOISE-1]*(1-t);
+                      mFactor3= mNoise*tmpN.nw[NUM_NOISE-1]*(1-t);
+                      len = ((float)NUM_NOISE)/(NUM_NOISE+1);
+                      lower = len + mNoise*(tmpN.nx[NUM_NOISE-1]-len);  
+                      return (1.0f-lower)*(d-NUM_NOISE) + lower;
+      default       : float ya,yb;
+                      yb = tmpN.ny[index  ];
+                      ya = tmpN.ny[index-1];
+                      mFactor1 = mNoise*((yb-ya)*t+ya);
+                      yb = tmpN.nz[index  ];
+                      ya = tmpN.nz[index-1];
+                      mFactor2 = mNoise*((yb-ya)*t+ya);
+                      yb = tmpN.nw[index  ];
+                      ya = tmpN.nw[index-1];
+                      mFactor3 = mNoise*((yb-ya)*t+ya);
+   
+                      len = ((float)index)/(NUM_NOISE+1);
+                      lower = len + mNoise*(tmpN.nx[index-1]-len);   
+                      len = ((float)index+1)/(NUM_NOISE+1); 
+                      upper = len + mNoise*(tmpN.nx[index  ]-len);
+            
+                      return (upper-lower)*(d-index) + lower; 
+      }
+    }
+     
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// v is the speed vector (i.e. position p(t) differentiated by time)
+// a is the acceleration vector (differentiate once more)
+//
+// Now we construct orthogonal basis with Gram-Schmidt:  
+// vec1 = a-delta*v where delta = (v*a)/|v|^2
+// vec2 = (0,0,1,0) - coeff1*(vx,vy,vz,vw) - coeff2*(vec1x,vec1y,vec1z,vec1w)                                     where coeff1 = vz/|v|^2, coeff2 = vec1Z/|vec1|^2
+// vec3 = (0,0,0,1) - coeff1*(vx,vy,vz,vw) - coeff2*(vec1x,vec1y,vec1z,vec1w) - coeff3*(vec2x,vec2y,vec2z,vec2w)  where coeff1 = vw/|v|^2, coeff2 = vec1W/|vec1|^2, coeff3 = vec2W/|vec2|^2
+    
+  private void setUpVectors(float time,VectorCache vc)
+    {
+    if( vc!=null )
+      {
+      float vx = (3*vc.ax*time+2*vc.bx)*time+vc.cx;
+      float vy = (3*vc.ay*time+2*vc.by)*time+vc.cy;
+      float vz = (3*vc.az*time+2*vc.bz)*time+vc.cz;
+      float vw = (3*vc.aw*time+2*vc.bw)*time+vc.cw;
+     
+      float ax = 6*vc.ax*time+2*vc.bx;
+      float ay = 6*vc.ay*time+2*vc.by;
+      float az = 6*vc.az*time+2*vc.bz;
+      float aw = 6*vc.aw*time+2*vc.bw;
+     
+      float v_sq = vx*vx+vy*vy+vz*vz+vw*vw;
+      float delta = (vx*ax+vy*ay+vz*az*vw*vw)/v_sq;
+     
+      vec1X = ax-delta*vx;
+      vec1Y = ay-delta*vy;
+      vec1Z = az-delta*vz;
+      vec1W = aw-delta*vw;
+     
+      float vec1_sq = vec1X*vec1X+vec1Y*vec1Y+vec1Z*vec1Z+vec1W*vec1W;
+     
+      // construct vec2 and vec3. Cross product does not work in 4th dimension!
+      float coeff21 = vz/v_sq;
+      float coeff22 = vec1Z/vec1_sq;
+      vec2X = 0.0f - coeff21*vx - coeff22*vec1X;
+      vec2Y = 0.0f - coeff21*vy - coeff22*vec1Y;
+      vec2Z = 1.0f - coeff21*vz - coeff22*vec1Z;
+      vec2W = 0.0f - coeff21*vw - coeff22*vec1W;
+     
+      float vec2_sq = vec2X*vec2X+vec2Y*vec2Y+vec2Z*vec2Z+vec2W*vec2W;
+      float coeff31 = vw/v_sq;
+      float coeff32 = vec1W/vec1_sq;
+      float coeff33 = vec2W/vec2_sq;
+      vec2X = 0.0f - coeff31*vx - coeff32*vec1X - coeff33*vec2X;
+      vec2Y = 0.0f - coeff31*vy - coeff32*vec1Y - coeff33*vec2Y;
+      vec2Z = 0.0f - coeff31*vz - coeff32*vec1Z - coeff33*vec2Z;
+      vec2W = 1.0f - coeff31*vw - coeff32*vec1W - coeff33*vec2W;
+     
+      float vec3_sq = vec3X*vec3X+vec3Y*vec3Y+vec3Z*vec3Z+vec3W*vec3W;
+     
+      float len1 = (float)Math.sqrt(v_sq/vec1_sq);   
+      float len2 = (float)Math.sqrt(v_sq/vec2_sq);   
+      float len3 = (float)Math.sqrt(v_sq/vec3_sq);
+     
+      vec1X*=len1;
+      vec1Y*=len1;
+      vec1Z*=len1;
+      vec1W*=len1;
+     
+      vec2X*=len2;
+      vec2Y*=len2;
+      vec2Z*=len2;
+      vec2W*=len2;
+     
+      vec3X*=len3;
+      vec3Y*=len3;
+      vec3Z*=len3;
+      vec3W*=len3;
+      }
+    else
+      {
+      curr = vv.elementAt(0);
+      next = vv.elementAt(1); 
+     
+      float vx = (next.x-curr.x);
+      float vy = (next.y-curr.y);
+      float vz = (next.z-curr.z);
+      float vw = (next.w-curr.w);
+     
+      float b = (float)Math.sqrt(vx*vx+vy*vy+vz*vz);
+     
+      if( b>0.0f )
+        {
+        vec1X = vx*vw/b;
+        vec1Y = vy*vw/b;
+        vec1Z = vz*vw/b;
+        vec1W = -b;
+      
+        float v_sq = vx*vx+vy*vy+vz*vz+vw*vw;
+        float vec1_sq = vec1X*vec1X+vec1Y*vec1Y+vec1Z*vec1Z+vec1W*vec1W;
+     
+        // construct vec2 and vec3. Cross product does not work in 4th dimension!
+        float coeff21 = vz/v_sq;
+        float coeff22 = vec1Z/vec1_sq;
+        vec2X = 0.0f - coeff21*vx - coeff22*vec1X;
+        vec2Y = 0.0f - coeff21*vy - coeff22*vec1Y;
+        vec2Z = 1.0f - coeff21*vz - coeff22*vec1Z;
+        vec2W = 0.0f - coeff21*vw - coeff22*vec1W;
+     
+        float vec2_sq = vec2X*vec2X+vec2Y*vec2Y+vec2Z*vec2Z+vec2W*vec2W;
+        float coeff31 = vw/v_sq;
+        float coeff32 = vec1W/vec1_sq;
+        float coeff33 = vec2W/vec2_sq;
+        vec2X = 0.0f - coeff31*vx - coeff32*vec1X - coeff33*vec2X;
+        vec2Y = 0.0f - coeff31*vy - coeff32*vec1Y - coeff33*vec2Y;
+        vec2Z = 0.0f - coeff31*vz - coeff32*vec1Z - coeff33*vec2Z;
+        vec2W = 1.0f - coeff31*vw - coeff32*vec1W - coeff33*vec2W;
+     
+        float vec3_sq = vec3X*vec3X+vec3Y*vec3Y+vec3Z*vec3Z+vec3W*vec3W;
+     
+        float len1 = (float)Math.sqrt(v_sq/vec1_sq);    
+        float len2 = (float)Math.sqrt(v_sq/vec2_sq);    
+        float len3 = (float)Math.sqrt(v_sq/vec3_sq);
+     
+        vec1X*=len1;
+        vec1Y*=len1;
+        vec1Z*=len1;
+        vec1W*=len1;
+     
+        vec2X*=len2;
+        vec2Y*=len2;
+        vec2Z*=len2;
+        vec2W*=len2;
+     
+        vec3X*=len3;
+        vec3Y*=len3;
+        vec3Z*=len3;
+        vec3W*=len3;
+        }
+      else
+        {
+        vec1X = vw;
+        vec1Y = 0.0f;
+        vec1Z = 0.0f;
+        vec1W = 0.0f;
+      
+        vec2X = 0.0f;
+        vec2Y = vw;
+        vec2Z = 0.0f;
+        vec2W = 0.0f;
+      
+        vec3X = 0.0f;
+        vec3Y = 0.0f;
+        vec3Z = vw;
+        vec3W = 0.0f;
+        }
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default constructor.
+ */
+  public Interpolator4D()
+    {
+    vv = new Vector<Float4D>();
+    vc = new Vector<VectorCache>();
+    vn = null;
+    numPoints = 0;
+    cacheDirty = false;
+    mMode = MODE_LOOP;
+    mDuration = 0;
+    mCount = 0.5f;
+    mNoise = 0.0f;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the location'th Float4D. 
+ *   
+ * @param location the index of the Point we are interested in.
+ * @return The Float4D, if 0<=location&lt;getNumPoints(), or null otherwise. 
+ */  
+  public synchronized Float4D getPoint(int location)
+    {
+    return (location>=0 && location<numPoints) ? vv.elementAt(location) : null;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resets the location'th Point.
+ * 
+ * @param location the index of the Point we are setting.
+ * @param x New value of its first float.
+ */
+  public synchronized void setPoint(int location, float x, float y, float z, float w)
+    {
+    if( location>=0 && location<numPoints )
+      {
+      curr = vv.elementAt(location);
+   
+      if( curr!=null )
+        {
+        curr.set(x,y,z,w);
+        cacheDirty=true;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float4D to the end of our list of Points to interpolate through.
+ * <p>   
+ * Only a reference to the Point gets added to the List; this means that one can add a Point 
+ * here, and later on {@link Float4D#set(float,float,float,float)} it to some new value and 
+ * the change will be seamlessly reflected in the interpolated path.  
+ * <p>
+ * A Point can be added multiple times.
+ *   
+ * @param v The Point to add.
+ */    
+  public synchronized void add(Float4D v)
+    {
+    if( v!=null )
+      {
+      vv.add(v);
+        
+      if( vn!=null ) vn.add(new VectorNoise());
+       
+       switch(numPoints)
+         {
+         case 0: break;
+         case 1: setUpVectors(0.0f,null);
+                 break;
+         case 2: vc.add(new VectorCache());
+                 vc.add(new VectorCache());
+                 vc.add(new VectorCache());
+                 break;
+         default:vc.add(new VectorCache());
+         }
+
+       numPoints++;
+       cacheDirty = true;
+       }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float4D to the location'th place in our List of Points to interpolate through.  
+ *   
+ * @param location Index in our List to add the new Point at.
+ * @param v The Float4D to add.
+ */  
+  public synchronized void add(int location, Float4D v)
+    {
+    if( v!=null )
+      {
+      vv.add(location, v);
+      
+      if( vn!=null ) vn.add(new VectorNoise());
+      
+      switch(numPoints)
+        {
+        case 0: break;
+        case 1: setUpVectors(0.0f,null);
+                break;
+        case 2: vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                break;
+        default:vc.add(location,new VectorCache());
+        }
+
+      numPoints++;
+      cacheDirty = true;
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all occurrences of Point v from the List of Points to interpolate through.  
+ * 
+ * @param v The Point to remove.
+ * @return <code>true</code> if we have removed at least one Point.
+ */
+  public synchronized boolean remove(Float4D v)
+    {
+    int n = vv.indexOf(v);
+    boolean found = false;
+   
+    while( n>=0 ) 
+      {
+      vv.remove(n);
+     
+      if( vn!=null ) vn.remove(0);
+     
+      switch(numPoints)
+        {
+        case 0:
+        case 1: 
+        case 2: break;
+        case 3: vc.removeAllElements();
+                setUpVectors(0.0f,null);
+                break;
+        default:vc.remove(n);
+        }
+
+      numPoints--;
+      found = true;
+      n = vv.indexOf(v);
+      }
+   
+    if( found ) 
+      {
+      cacheDirty=true;
+      }
+   
+    return found;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes a location'th Point from the List of Points we interpolate through.
+ * 
+ * @param location index of the Point we want to remove. 
+ * @return <code>true</code> if location is valid, i.e. if 0<=location&lt;getNumPoints().
+ */
+  public synchronized boolean remove(int location)
+    {
+    if( location>=0 && location<numPoints ) 
+      {
+      vv.removeElementAt(location);
+       
+      if( vn!=null ) vn.remove(0);
+      
+      switch(numPoints)
+        {
+        case 0:
+        case 1: 
+        case 2: break;
+        case 3: vc.removeAllElements();
+                setUpVectors(0.0f,null);
+                break;
+        default:vc.removeElementAt(location);
+        }
+
+      numPoints--;
+      cacheDirty = true; 
+      return true;
+      }
+
+    return false;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all Points.
+ */
+  public synchronized void removeAll()
+    {
+    numPoints = 0;
+    vv.removeAllElements();
+    vc.removeAllElements();
+    cacheDirty = false;
+   
+    if( vn!=null ) vn.removeAllElements();
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Writes the results of interpolation between the Points at time 'time' to the passed float buffer.
+ * <p>
+ * Since this is a 4-dimensional Interpolator, the resulting interpolated Float4D gets written
+ * to four locations in the buffer: buffer[offset], buffer[offset+1], buffer[offset+2] and buffer[offset+3]. 
+ * 
+ * @param buffer Float buffer we will write the resulting Float4D to.
+ * @param offset Offset in the buffer where to write the result.
+ * @param time Time of interpolation. Time=0.0 would return the first Point, Time=0.5 - the last,
+ *             time=1.0 - the first again, and time 0.1 would be 1/5 of the way between the first and the last Points.
+ */    
+  public synchronized void interpolate(float[] buffer, int offset, float time)
+    {  
+    switch(numPoints)
+      {
+      case 0: buffer[offset  ] = 0.0f;
+              buffer[offset+1] = 0.0f;
+              buffer[offset+2] = 0.0f;
+              buffer[offset+3] = 0.0f;
+              break;
+      case 1: curr = vv.elementAt(0);
+              buffer[offset  ] = curr.x;
+              buffer[offset+1] = curr.y;
+              buffer[offset+2] = curr.z;
+              buffer[offset+3] = curr.w;
+              break;
+      case 2: curr = vv.elementAt(0);
+              next = vv.elementAt(1);
+            
+              if( mMode==MODE_LOOP || mMode==MODE_PATH ) time = (time>0.5f ? 2-2*time : 2*time);
+             
+              if( vn!=null )
+                {
+                time = noise(time,0);
+            
+                buffer[offset  ] = (next.x-curr.x)*time + curr.x + (vec1X*mFactor1 + vec2X*mFactor2 + vec3X*mFactor3);
+                buffer[offset+1] = (next.y-curr.y)*time + curr.y + (vec1Y*mFactor1 + vec2Y*mFactor2 + vec3Y*mFactor3);
+                buffer[offset+2] = (next.z-curr.z)*time + curr.z + (vec1Z*mFactor1 + vec2Z*mFactor2 + vec3Z*mFactor3);
+                buffer[offset+3] = (next.w-curr.w)*time + curr.w + (vec1W*mFactor1 + vec2W*mFactor2 + vec3W*mFactor3); 
+                }
+              else
+                {
+                buffer[offset  ] = (next.x-curr.x)*time + curr.x;
+                buffer[offset+1] = (next.y-curr.y)*time + curr.y;
+                buffer[offset+2] = (next.z-curr.z)*time + curr.z;
+                buffer[offset+3] = (next.w-curr.w)*time + curr.w;
+                }
+                
+              break;
+      default:float t = time;
+        
+              switch(mMode)
+                {
+                case MODE_LOOP: time = time*numPoints;
+                                break;
+                case MODE_PATH: time = (time<=0.5f) ? 2*time*(numPoints-1) : 2*(1-time)*(numPoints-1);
+                                break;
+                case MODE_JUMP: time = time*(numPoints-1);
+                                break;
+                }
+     
+              int vecCurr = (int)time;
+              time = time-vecCurr;
+      
+              if( vecCurr>=0 && vecCurr<numPoints )
+                {
+                if( cacheDirty ) recomputeCache();    // recompute cache if we have added or remove vectors since last computation
+                else if( mVecCurr!= vecCurr )         // ...or if we have just passed a vector and the vector we are currently flying to has changed
+                  {
+                  int vecNext;   
+                  mVecCurr = vecCurr;
+                       
+                  switch(mMode)
+                    {
+                    case MODE_LOOP: vecNext = vecCurr==numPoints-1 ? 0:vecCurr+1; 
+                                    break;
+                    case MODE_PATH: if( t<0.5f ) vecNext = vecCurr==numPoints-1 ? numPoints-2: vecCurr+1;  
+                                    else         vecNext = vecCurr==0 ? 1 : vecCurr-1;  
+                                    break;
+                    case MODE_JUMP: vecNext = vecCurr==numPoints-1 ? 1:vecCurr+1;
+                                    break;
+                    default       : vecNext = 0;                
+                    }
+     
+                  next = vv.elementAt(vecNext);
+                  tmp2 = vc.elementAt(vecNext);
+              
+                  if( tmp2.vx!=next.x || tmp2.vy!=next.y || tmp2.vz!=next.z || tmp2.vw!=next.w ) recomputeCache();
+                  }
+            
+                tmp1 = vc.elementAt(vecCurr);
+               
+                if( vn!=null )
+                  {
+                  time = noise(time,vecCurr);
+              
+                  setUpVectors(time,tmp1);
+                 
+                  buffer[offset  ]= ((tmp1.ax*time+tmp1.bx)*time+tmp1.cx)*time+tmp1.dx + (vec1X*mFactor1 + vec2X*mFactor2 + vec3X*mFactor3);
+                  buffer[offset+1]= ((tmp1.ay*time+tmp1.by)*time+tmp1.cy)*time+tmp1.dy + (vec1Y*mFactor1 + vec2Y*mFactor2 + vec3Y*mFactor3);
+                  buffer[offset+2]= ((tmp1.az*time+tmp1.bz)*time+tmp1.cz)*time+tmp1.dz + (vec1Z*mFactor1 + vec2Z*mFactor2 + vec3Z*mFactor3);
+                  buffer[offset+3]= ((tmp1.aw*time+tmp1.bw)*time+tmp1.cw)*time+tmp1.dw + (vec1W*mFactor1 + vec2W*mFactor2 + vec3W*mFactor3);
+                  }
+                else
+                  {
+                  buffer[offset  ]= ((tmp1.ax*time+tmp1.bx)*time+tmp1.cx)*time+tmp1.dx;
+                  buffer[offset+1]= ((tmp1.ay*time+tmp1.by)*time+tmp1.cy)*time+tmp1.dy;
+                  buffer[offset+2]= ((tmp1.az*time+tmp1.bz)*time+tmp1.cz)*time+tmp1.dz;
+                  buffer[offset+3]= ((tmp1.aw*time+tmp1.bw)*time+tmp1.cw)*time+tmp1.dw;
+                  }
+ 
+                break;
+                }
+      }
+    }  
+
+  }
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
diff --git a/src/main/java/org/distorted/library/InterpolatorQuat.java b/src/main/java/org/distorted/library/InterpolatorQuat.java
new file mode 100644
index 0000000..c4f6255
--- /dev/null
+++ b/src/main/java/org/distorted/library/InterpolatorQuat.java
@@ -0,0 +1,373 @@
+package org.distorted.library;
+
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/** 
+* A 4-dimensional implementation of the Interpolator class to interpolate between a list 
+* of Float4Ds.
+* Here, the Points are assumed to be Quaternions - thus we do the Spherical Linear Interpolation, aka
+* SLERP. Noise not supported (yet?).
+*/
+
+public class InterpolatorQuat extends Interpolator 
+  {
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// omega, sinOmega, cosOmega - angle between pair of quaternions, its sinus and cosinus.
+//  
+// (vx,vy,vz,vw) is the original vector from vv (copied here so when interpolating we can see if it is 
+// still valid and if not - rebuild the Cache
+  
+  private class VectorCache
+    {
+    float omega, sinOmega,cosOmega;
+    float vx,vy,vz,vw;
+    }
+  
+  private Vector<VectorCache> vc;
+  private VectorCache tmp1, tmp2;
+
+  private Vector<Float4D> vv;
+  private Float4D curr, next;
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//Abramowitz / Stegun
+
+  private static float arcCos(float x)
+    {
+    if( x<0 )
+      return 3.14159265358979f - (float)Math.sqrt(1+x)*(1.5707288f + 0.2121144f*x + 0.074261f*x*x + 0.0187293f*x*x*x);
+     
+    return (float)Math.sqrt(1-x)*(1.5707288f - 0.2121144f*x + 0.074261f*x*x - 0.0187293f*x*x*x);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Quaternion Interpolator doesn't support noise
+  
+  synchronized void createNoise()
+    {
+
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private void recomputeCache()
+    {  
+    if( numPoints>=2 )
+      {
+      int i, n;  
+     
+      for(i=0; i<numPoints; i++)
+        {
+        n = i<numPoints-1 ? i+1:0;  
+      
+        tmp1= vc.elementAt(i);
+        tmp2= vc.elementAt(n);
+        curr= vv.elementAt(i);
+        next= vv.elementAt(n);
+      
+        tmp1.vx = curr.x;
+        tmp1.vy = curr.y;
+        tmp1.vz = curr.z;
+        tmp1.vw = curr.w;
+    	
+        tmp1.cosOmega = curr.x*next.x + curr.y*next.y + curr.z*next.z + curr.w*next.w;
+      	
+        if( tmp1.cosOmega<0 && n!=0 )  // do not invert the last quaternion even if we'd have to go the long way around!
+          {
+          tmp1.cosOmega = -tmp1.cosOmega;
+          next.x = -next.x;
+          next.y = -next.y;
+          next.z = -next.z;
+          next.w = -next.w;
+          }
+      	
+        tmp1.sinOmega = (float)Math.sqrt(1-tmp1.cosOmega*tmp1.cosOmega);
+        tmp1.omega = arcCos(tmp1.cosOmega);
+        }
+      }
+   
+    cacheDirty = false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default constructor.
+ */
+  public InterpolatorQuat()
+    {
+    vv = new Vector<Float4D>();
+    vc = new Vector<VectorCache>();
+    numPoints = 0;
+    cacheDirty = false;
+    mMode = MODE_LOOP;
+    mDuration = 0;
+    mCount = 0.5f;
+    mNoise = 0.0f;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the location'th Float4D. 
+ *   
+ * @param location the index of the Point we are interested in.
+ * @return The Float4D, if 0<=location&lt;getNumPoints(), or null otherwise. 
+ */  
+  public synchronized Float4D getPoint(int location)
+    {
+    return (location>=0 && location<numPoints) ? vv.elementAt(location) : null;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resets the location'th Point.
+ * 
+ * @param location the index of the Point we are setting.
+ * @param x New value of its first float.
+ */
+  public synchronized void setPoint(int location, float x, float y, float z, float w)
+    {
+    if( location>=0 && location<numPoints )
+      {
+      curr = vv.elementAt(location);
+   
+      if( curr!=null )
+        {
+        curr.set(x,y,z,w);
+        cacheDirty=true;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float4D to the end of our list of Points to interpolate through.
+ * <p>   
+ * Only a reference to the Point gets added to the List; this means that one can add a Point 
+ * here, and later on {@link Float4D#set(float,float,float,float)} it to some new value and 
+ * the change will be seamlessly reflected in the interpolated path.  
+ * <p>
+ * A Point can be added multiple times.
+ *   
+ * @param v The Point to add.
+ */    
+  public synchronized void add(Float4D v)
+    {
+    if( v!=null )
+      {
+      vv.add(v);
+      
+      switch(numPoints)
+         {
+         case 0: 
+         case 1: vc.add(new VectorCache());
+                 vc.add(new VectorCache());
+        	     break;
+         default:vc.add(new VectorCache());
+         }
+
+       numPoints++;
+       cacheDirty = true;
+       }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new Float4D to the location'th place in our List of Points to interpolate through.  
+ *   
+ * @param location Index in our List to add the new Point at.
+ * @param v The Float4D to add.
+ */  
+  public synchronized void add(int location, Float4D v)
+    {
+    if( v!=null )
+      {
+      vv.add(location, v);
+      
+      switch(numPoints)
+        {
+        case 0: 
+        case 1: vc.add(new VectorCache());
+                vc.add(new VectorCache());
+                break;
+        default:vc.add(location,new VectorCache());
+        }
+
+      numPoints++;
+      cacheDirty = true;
+      }
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all occurrences of Point v from the List of Points to interpolate through.  
+ * 
+ * @param v The Point to remove.
+ * @return <code>true</code> if we have removed at least one Point.
+ */
+  public synchronized boolean remove(Float4D v)
+    {
+    int n = vv.indexOf(v);
+    boolean found = false;
+   
+    while( n>=0 ) 
+      {
+      vv.remove(n);
+     
+      switch(numPoints)
+        {
+        case 0:
+        case 1: break;
+        case 2: vc.removeAllElements();
+                break;
+        default:vc.remove(n);
+        }
+
+      numPoints--;
+      found = true;
+      n = vv.indexOf(v);
+      }
+   
+    if( found ) 
+      {
+      cacheDirty=true;
+      }
+   
+    return found;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes a location'th Point from the List of Points we interpolate through.
+ * 
+ * @param location index of the Point we want to remove. 
+ * @return <code>true</code> if location is valid, i.e. if 0<=location&lt;getNumPoints().
+ */
+  public synchronized boolean remove(int location)
+    {
+    if( location>=0 && location<numPoints ) 
+      {
+      vv.removeElementAt(location);
+      
+      switch(numPoints)
+        {
+        case 0: 
+        case 1: break;
+        case 2: vc.removeAllElements();
+                break;
+        default:vc.removeElementAt(location);
+        }
+
+      numPoints--;
+      cacheDirty = true; 
+      return true;
+      }
+
+    return false;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all Points.
+ */
+  public synchronized void removeAll()
+    {
+    numPoints = 0;
+    vv.removeAllElements();
+    vc.removeAllElements();
+    cacheDirty = false;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Writes the results of interpolation between the Points at time 'time' to the passed float buffer.
+ * Interpolation is done using the spherical linear algorithm, aka SLERP.
+ * <p>
+ * Since this is a 4-dimensional Interpolator, the resulting interpolated Float4D gets written
+ * to four locations in the buffer: buffer[offset], buffer[offset+1], buffer[offset+2] and buffer[offset+3]. 
+ * 
+ * @param buffer Float buffer we will write the resulting Float4D to.
+ * @param offset Offset in the buffer where to write the result.
+ * @param time Time of interpolation. Time=0.0 would return the first Point, Time=0.5 - the last,
+ *             time=1.0 - the first again, and time 0.1 would be 1/5 of the way between the first and the last Points.
+ */    
+  public synchronized void interpolate(float[] buffer, int offset, float time)
+    {  
+    switch(numPoints)
+      {
+      case 0: buffer[offset  ] = 0.0f;
+              buffer[offset+1] = 0.0f;
+              buffer[offset+2] = 0.0f;
+              buffer[offset+3] = 0.0f;
+              break;
+      case 1: curr = vv.elementAt(0);
+              buffer[offset  ] = curr.x; 
+              buffer[offset+1] = curr.y;
+              buffer[offset+2] = curr.z;
+              buffer[offset+3] = curr.w;
+              break;
+      default:float t = time;
+              float scale0, scale1;
+  
+              if( mMode==MODE_JUMP ) time = time*(numPoints-1);
+              else if( mMode==MODE_PATH || numPoints==2 ) time = (time<=0.5f) ? 2*time*(numPoints-1) : 2*(1-time)*(numPoints-1);
+              else time = time*numPoints;
+              
+              int vecNext, vecCurr = (int)time;
+              time = time-vecCurr;
+      
+              if( vecCurr>=0 && vecCurr<numPoints )
+                {
+                if( cacheDirty ) recomputeCache();    // recompute cache if we have added or remove vectors since last computation
+                   
+                switch(mMode)
+                  {
+                  case MODE_LOOP: vecNext = vecCurr==numPoints-1 ? 0:vecCurr+1; 
+                                  break;
+                  case MODE_PATH: if( t<0.5f ) vecNext = vecCurr+1;  
+                                  else         vecNext = vecCurr==0 ? 1 : vecCurr-1;  
+                                  break;
+                  case MODE_JUMP: vecNext = vecCurr==numPoints-1 ? 1:vecCurr+1;
+                                  break;
+                  default       : vecNext = 0;                
+                  }
+     
+                curr = vv.elementAt(vecCurr);
+                next = vv.elementAt(vecNext);
+                tmp1 = vc.elementAt(vecCurr);
+                tmp2 = vc.elementAt(vecNext);
+              
+                if( tmp2.vx!=next.x || tmp2.vy!=next.y || tmp2.vz!=next.z || tmp2.vw!=next.w ) recomputeCache();
+               
+                if( tmp1.sinOmega==0 )
+                  {
+                  scale0 = 0f;
+                  scale1 = 1f;
+                  }
+                else if( tmp1.cosOmega < 0.99 ) 
+                  {
+                  scale0 = (float)Math.sin( (1f-time)*tmp1.omega ) / tmp1.sinOmega;
+                  scale1 = (float)Math.sin(     time *tmp1.omega ) / tmp1.sinOmega;
+                  }
+                else 
+                  {
+                  scale0 = 1f-time;
+                  scale1 = time;
+                  }
+
+                buffer[offset  ] = scale0*curr.x + scale1*next.x;
+                buffer[offset+1] = scale0*curr.y + scale1*next.y; 
+                buffer[offset+2] = scale0*curr.z + scale1*next.z; 
+                buffer[offset+3] = scale0*curr.w + scale1*next.w; 
+                
+                break;
+                }
+      }
+    }  
+
+  }
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
diff --git a/src/main/java/org/distorted/library/exception/FragmentCompilationException.java b/src/main/java/org/distorted/library/exception/FragmentCompilationException.java
new file mode 100644
index 0000000..8c4fa49
--- /dev/null
+++ b/src/main/java/org/distorted/library/exception/FragmentCompilationException.java
@@ -0,0 +1,60 @@
+package org.distorted.library.exception;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ *  Thrown by {@link org.distorted.library.Distorted#onSurfaceCreated(android.opengl.GLSurfaceView)} 
+ *  if compilation of the fragment shader fails for some other reason than too many uniforms.
+ *  <p>
+ *  This can happen on older OpenGL ES 2.0 devices if they, say, do not support variable loops in the shaders.
+ *  Theoretically should never happen on devices supporting at least OpenGL ES 3.0.
+ */
+
+@SuppressWarnings("serial")
+public class FragmentCompilationException extends Exception 
+  {
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default empty constructor  
+ */
+  public FragmentCompilationException() 
+    {
+   
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor with a message describing why compilation failed.  
+ *   
+ * @param detailMessage Message describing why compilation failed
+ */
+  public FragmentCompilationException(String detailMessage) 
+    {
+    super(detailMessage);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *  
+ * @param throwable The parent Throwable.
+ */
+  public FragmentCompilationException(Throwable throwable) 
+    {
+    super(throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *   
+ * @param detailMessage Message describing why compilation failed
+ * @param throwable The parent Throwable.
+ */
+  public FragmentCompilationException(String detailMessage, Throwable throwable) 
+    {
+    super(detailMessage, throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+  }
diff --git a/src/main/java/org/distorted/library/exception/FragmentUniformsException.java b/src/main/java/org/distorted/library/exception/FragmentUniformsException.java
new file mode 100644
index 0000000..c640677
--- /dev/null
+++ b/src/main/java/org/distorted/library/exception/FragmentUniformsException.java
@@ -0,0 +1,84 @@
+package org.distorted.library.exception;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ *  Thrown by {@link org.distorted.library.Distorted#onSurfaceCreated(android.opengl.GLSurfaceView)} 
+ *  if compilation of the fragment shader fails because of too many uniforms there, i.e. because
+ *  we have set {@link org.distorted.library.Distorted#setMaxFragment(int)} to too high value.
+ */
+
+@SuppressWarnings("serial")
+public class FragmentUniformsException extends Exception 
+  {
+  private int max=0;
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default empty constructor  
+ */   
+  public FragmentUniformsException() 
+    {
+   
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor with a message describing why compilation failed.  
+ *   
+ * @param detailMessage Message describing why compilation failed
+ */  
+  public FragmentUniformsException(String detailMessage) 
+    {
+    super(detailMessage);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *  
+ * @param throwable The parent Throwable.
+ */ 
+  public FragmentUniformsException(Throwable throwable) 
+    {
+    super(throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *   
+ * @param detailMessage Message describing why compilation failed
+ * @param throwable The parent Throwable.
+ */  
+  public FragmentUniformsException(String detailMessage, Throwable throwable) 
+    {
+    super(detailMessage, throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor with a message describing why compilation failed and integer holding the maximum
+ * number of uniforms in Fragment Shader supported by current hardware.   
+ *   
+ * @param detailMessage Message describing why compilation failed
+ * @param m maximum number of uniforms in Fragment Shader supported by current hardware.   
+ */   
+  public FragmentUniformsException(String detailMessage, int m) 
+    {
+    super(detailMessage);
+    max = m;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Gets the maximum number of uniforms in fragment shader supported by current hardware.
+ * 
+ * @return Maximum number of uniforms in fragment shader supported by current hardware.   
+ */
+  public int getMax()
+    {
+    return max;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+  }
diff --git a/src/main/java/org/distorted/library/exception/LinkingException.java b/src/main/java/org/distorted/library/exception/LinkingException.java
new file mode 100644
index 0000000..aeb3f31
--- /dev/null
+++ b/src/main/java/org/distorted/library/exception/LinkingException.java
@@ -0,0 +1,58 @@
+package org.distorted.library.exception;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ *  Thrown by {@link org.distorted.library.Distorted#onSurfaceCreated(android.opengl.GLSurfaceView)} 
+ *  if linking of the Shaders fails.
+ *  <p>
+ *  Theoretically this should never happen.
+ */
+@SuppressWarnings("serial")
+public class LinkingException extends Exception 
+  {
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default empty constructor  
+ */      
+  public LinkingException() 
+    {
+   
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor with a message describing why linking failed.  
+ *   
+ * @param detailMessage Message describing why linking failed
+ */    
+  public LinkingException(String detailMessage) 
+    {
+    super(detailMessage);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *  
+ * @param throwable The parent Throwable.
+ */  
+  public LinkingException(Throwable throwable) 
+    {
+    super(throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *   
+ * @param detailMessage Message describing why linking failed
+ * @param throwable The parent Throwable.
+ */      
+  public LinkingException(String detailMessage, Throwable throwable) 
+    {
+    super(detailMessage, throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+  }
diff --git a/src/main/java/org/distorted/library/exception/VertexCompilationException.java b/src/main/java/org/distorted/library/exception/VertexCompilationException.java
new file mode 100644
index 0000000..945f1a2
--- /dev/null
+++ b/src/main/java/org/distorted/library/exception/VertexCompilationException.java
@@ -0,0 +1,60 @@
+package org.distorted.library.exception;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ *  Thrown by {@link org.distorted.library.Distorted#onSurfaceCreated(android.opengl.GLSurfaceView)} 
+ *  if compilation of the vertex shader fails for some other reason than too many uniforms.
+ *  <p>
+ *  This can happen on older OpenGL ES 2.0 devices if they, say, do not support variable loops in the shaders.
+ *  Theoretically should never happen on devices supporting at least OpenGL ES 3.0.
+ */
+
+@SuppressWarnings("serial")
+public class VertexCompilationException extends Exception 
+  {
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default empty constructor  
+ */   
+  public VertexCompilationException() 
+    {
+   
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor with a message describing why compilation failed.  
+ *   
+ * @param detailMessage Message describing why compilation failed
+ */  
+  public VertexCompilationException(String detailMessage) 
+    {
+    super(detailMessage);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *  
+ * @param throwable The parent Throwable.
+ */ 
+  public VertexCompilationException(Throwable throwable) 
+    {
+    super(throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *   
+ * @param detailMessage Message describing why compilation failed
+ * @param throwable The parent Throwable.
+ */  
+  public VertexCompilationException(String detailMessage, Throwable throwable) 
+    {
+    super(detailMessage, throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+  }
diff --git a/src/main/java/org/distorted/library/exception/VertexUniformsException.java b/src/main/java/org/distorted/library/exception/VertexUniformsException.java
new file mode 100644
index 0000000..99d2eb0
--- /dev/null
+++ b/src/main/java/org/distorted/library/exception/VertexUniformsException.java
@@ -0,0 +1,84 @@
+package org.distorted.library.exception;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ *  Thrown by {@link org.distorted.library.Distorted#onSurfaceCreated(android.opengl.GLSurfaceView)} 
+ *  if compilation of the Vertex Shader fails because of too many uniforms there, i.e. because
+ *  we have set {@link org.distorted.library.Distorted#setMaxVertex(int)} to too high value.
+ */
+
+@SuppressWarnings("serial")
+public class VertexUniformsException extends Exception 
+  {
+  private int max=0;
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Default empty constructor  
+ */      
+  public VertexUniformsException() 
+    {
+   
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor with a message describing why compilation failed.  
+ *   
+ * @param detailMessage Message describing why compilation failed
+ */   
+  public VertexUniformsException(String detailMessage) 
+    {
+    super(detailMessage);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *  
+ * @param throwable The parent Throwable.
+ */ 
+  public VertexUniformsException(Throwable throwable) 
+    {
+    super(throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor necessary to make Chained Exceptions working.
+ *   
+ * @param detailMessage Message describing why compilation failed
+ * @param throwable The parent Throwable.
+ */    
+  public VertexUniformsException(String detailMessage, Throwable throwable) 
+    {
+    super(detailMessage, throwable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructor with a message describing why compilation failed and integer holding the maximum
+ * number of uniforms in Vertex Shader supported by current hardware.   
+ *   
+ * @param detailMessage Message describing why compilation failed
+ * @param m maximum number of uniforms in Vertex Shader supported by current hardware.   
+ */     
+  public VertexUniformsException(String detailMessage, int m) 
+    {
+    super(detailMessage);
+    max = m;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Gets the maximum number of uniforms in Vertex Shader supported by current hardware.
+ * 
+ * @return Maximum number of uniforms in Vertex Shader supported by current hardware.   
+ */
+  public int getMax()
+    {
+    return max;  
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+  }
diff --git a/src/main/res/raw/main_fragment_shader.glsl b/src/main/res/raw/main_fragment_shader.glsl
new file mode 100644
index 0000000..2da68a6
--- /dev/null
+++ b/src/main/res/raw/main_fragment_shader.glsl
@@ -0,0 +1,119 @@
+
+precision highp float;
+  
+uniform sampler2D u_Texture;    // The input texture.
+    
+varying vec3 v_Position;		// Interpolated position for this fragment.
+varying vec4 v_Color;          	// This is the color from the vertex shader interpolated across the triangle per fragment.
+varying vec3 v_Normal;         	// Interpolated normal for this fragment.
+varying vec2 v_TexCoordinate;   // Interpolated texture coordinate per fragment.
+
+uniform int fNumEffects;                // total number of fragment effects
+
+#if NUM_FRAGMENT>0
+uniform int fType[NUM_FRAGMENT];        // their types.
+uniform vec3 fUniforms[3*NUM_FRAGMENT]; // i-th effect is 3 consecutive vec3's: [3*i], [3*i+1], [3*i+2]. first 4 floats are the Interpolated values,
+                                        // next 5 describe the Region, i.e. area over which the effect is active.
+                                        // Important note: here the Region is written in a different order than in the Vertex shader.
+ 
+const vec3 LUMI = vec3( 0.2125, 0.7154, 0.0721 );                                        
+ 
+//////////////////////////////////////////////////////////////////////////////////////////////
+// macroblocks
+
+void macroblock(float degree, int effect, inout vec2 tex)
+  {
+  vec2 one = vec2(1.0,1.0);  
+  vec2 a = degree*(fUniforms[effect].yz-one)+one;
+  tex = ( max((1.0-degree)*tex,floor(tex*a)) + degree*vec2(0.5,0.5) ) / a;
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// change the RGB values
+
+void chroma(float degree, int effect, inout vec4 color)
+  {
+  color.rgb = mix(color.rgb, vec3(fUniforms[effect].yz,fUniforms[effect+1].x), degree*fUniforms[effect].x); 
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// change the transparency level
+
+void alpha(float degree, int effect, inout vec4 color)
+  {
+  color.a *= (degree*(fUniforms[effect].x-1.0)+1.0); 
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// change the brightness level
+
+void brightness(float degree, int effect, inout vec4 color)
+  {
+  color.rgb = mix(vec3(0.0,0.0,0.0), color.rgb, degree*(fUniforms[effect].x-1.0)+1.0 ); 
+  }
+  
+//////////////////////////////////////////////////////////////////////////////////////////////
+// change the contrast level
+
+void contrast(float degree, int effect, inout vec4 color)
+  {
+  color.rgb = mix(vec3(0.5,0.5,0.5), color.rgb, degree*(fUniforms[effect].x-1.0)+1.0 ); 
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// change the saturation level
+
+void saturation(float degree, int effect, inout vec4 color)
+  {
+  float luminance = dot(LUMI,color.rgb);
+  color.rgb = mix(vec3(luminance,luminance,luminance), color.rgb, degree*(fUniforms[effect].x-1.0)+1.0 ); 
+  }
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+void main()                    		
+  {  
+  vec2 tex = v_TexCoordinate;
+  vec4 col = v_Color;
+  vec2 diff;
+  float pointDegree;
+  
+#if NUM_FRAGMENT>0  
+  for(int i=0; i<fNumEffects; i++)
+    {
+    diff = (v_Position.xy - fUniforms[3*i+2].yz)/fUniforms[3*i+1].yz;
+    pointDegree = max(0.0,1.0-dot(diff,diff));
+  
+    //switch(fType[i])
+    //  {
+    //  case MACROBLOCK        : macroblock(sign(pointDegree),3*i,tex); break;
+    //  case CHROMA            : chroma    (sign(pointDegree),3*i,col); break;
+    //  case SMOOTH_CHROMA     : chroma    (     pointDegree ,3*i,col); break;
+    //  case ALPHA             : alpha     (sign(pointDegree),3*i,col); break;
+    //  case SMOOTH_ALPHA      : alpha     (     pointDegree ,3*i,col); break;
+    //  case BRIGHTNESS        : brightness(sign(pointDegree),3*i,col); break;
+    //  case SMOOTH_BRIGHTNESS : brightness(     pointDegree ,3*i,col); break;
+    //  case CONTRAST          : contrast  (sign(pointDegree),3*i,col); break;
+    //  case SMOOTH_CONTRAST   : contrast  (     pointDegree ,3*i,col); break;
+    //  case SATURATION        : saturation(sign(pointDegree),3*i,col); break;
+    //  case SMOOTH_SATURATION : saturation(     pointDegree ,3*i,col); break;
+    //  }
+    
+         if( fType[i]==MACROBLOCK        ) macroblock(sign(pointDegree),3*i,tex);
+    else if( fType[i]==CHROMA            ) chroma    (sign(pointDegree),3*i,col);
+    else if( fType[i]==SMOOTH_CHROMA     ) chroma    (     pointDegree ,3*i,col);
+    else if( fType[i]==ALPHA             ) alpha     (sign(pointDegree),3*i,col);
+    else if( fType[i]==SMOOTH_ALPHA      ) alpha     (     pointDegree ,3*i,col);
+    else if( fType[i]==BRIGHTNESS        ) brightness(sign(pointDegree),3*i,col);
+    else if( fType[i]==SMOOTH_BRIGHTNESS ) brightness(     pointDegree ,3*i,col);
+    else if( fType[i]==CONTRAST          ) contrast  (sign(pointDegree),3*i,col);
+    else if( fType[i]==SMOOTH_CONTRAST   ) contrast  (     pointDegree ,3*i,col);
+    else if( fType[i]==SATURATION        ) saturation(sign(pointDegree),3*i,col);
+    else if( fType[i]==SMOOTH_SATURATION ) saturation(     pointDegree ,3*i,col);
+    }
+#endif
+ 
+  gl_FragColor = (col * 0.5 * (v_Normal.z+1.0) * texture2D(u_Texture, tex));
+  }
\ No newline at end of file
diff --git a/src/main/res/raw/main_vertex_shader.glsl b/src/main/res/raw/main_vertex_shader.glsl
new file mode 100644
index 0000000..9dfbff7
--- /dev/null
+++ b/src/main/res/raw/main_vertex_shader.glsl
@@ -0,0 +1,332 @@
+uniform vec3 u_bmpD;            // half of object width x half of object height X half the depth; 
+                                // point (0,0,0) is the center of the object
+
+uniform float u_Depth;          // max absolute value of v.z ; beyond that the vertex would be culled by the near or far planes.
+                                // I read OpenGL ES has a built-in uniform variable gl_DepthRange.near = n, .far = f, .diff = f-n so maybe u_Depth is redundant
+                                // Update: this struct is only available in fragment shaders
+                                
+uniform mat4 u_MVPMatrix;		// A constant representing the combined model/view/projection matrix.      		       
+uniform mat4 u_MVMatrix;		// A constant representing the combined model/view matrix.       		
+		 
+attribute vec3 a_Position;		// Per-vertex position information we will pass in.   				
+attribute vec4 a_Color;			// Per-vertex color information we will pass in. 				
+attribute vec3 a_Normal;		// Per-vertex normal information we will pass in.      
+attribute vec2 a_TexCoordinate; // Per-vertex texture coordinate information we will pass in. 		
+		  
+varying vec3 v_Position;		//      		
+varying vec4 v_Color;			// Those will be passed into the fragment shader.          		
+varying vec3 v_Normal;			//  
+varying vec2 v_TexCoordinate;   //  		
+
+uniform int vNumEffects;                  // total number of vertex effects
+
+#if NUM_VERTEX>0
+uniform int vType[NUM_VERTEX];            // their types.
+uniform vec3 vUniforms[3*NUM_VERTEX];     // i-th effect is 3 consecutive vec3's: [3*i], [3*i+1], [3*i+2]. first 3 float are the Interpolated values,
+                                          // next 4 are the Region, next 2 are the Point. 
+#endif
+
+#if NUM_VERTEX>0
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Deform the whole shape of the bitmap by force V 
+// 
+// If the point of application (Sx,Sy) is on the edge of the bitmap, then:
+// a) ignore Vz
+// b) change shape of the whole bitmap in the following way:
+//    Suppose the upper-left corner of the bitmap rectangle is point L, upper-right - R, force vector V is applied to point M on the upper edge,
+//    length of the bitmap = w, height = h, |LM| = Wl, |MR| = Wr, force vector V=(Vx,Vy). Also let H = h/(h+Vy)
+//
+//    Let now L' and R' be points such that vec(LL') = Wr/w * vec(V) and vec(RR') = Wl/w * vec(V)
+//    now let Vl be a point on the line segment L --> M+vec(V) such that Vl(y) = L'(y)
+//    and let Vr be a point on the line segment R --> M+vec(V) such that Vr(y) = R'(y)
+//    
+//    Now define points Fl and Fr, the points L and R will be moved to under force V, with Fl(y)=L'(y) and Fr(y)=R'(y) and |VrFr|/|VrR'| = |VlFl|/|VlL'| = H
+//    Now notice that |VrR'| = |VlL'| = Wl*Wr / w   ( a little geometric puzzle! )
+//
+//    Then points L,R under force V move by vectors vec(Fl), vec(Fr) where
+//    vec(Fl) = (Wr/w) * [ (Vx+Wl)-Wl*H, Vy ] = (Wr/w) * [ Wl*Vy / (h+Vy) + Vx, Vy ]
+//    vec(Fr) = (Wl/w) * [ (Vx-Wr)+Wr*H, Vy ] = (Wl/w) * [-Wr*Vy / (h+Vy) + Vx, Vy ]
+//
+//    Lets now denote M+vec(v) = M'. The line segment LMR gets distorted to the curve Fl-M'-Fr. Let's now arbitrarilly decide that:
+//    a) at point Fl the curve has to be parallel to line LM'
+//    b) at point M' - to line LR
+//    c) at point Fr - to line M'R
+//
+//    Now if Fl=(flx,fly) , M'=(mx,my) , Fr=(frx,fry); direction vector at Fl is (vx,vy) and at M' is (+c,0) where +c is some positive constant, then 
+//    the parametric equations of the Fl--->M' section of the curve (which has to satisfy (X(0),Y(0)) = Fl, (X(1),Y(1))=M', (X'(0),Y'(0)) = (vx,vy), (X'(1),Y'(1)) = (+c,0)) is
+//
+//    X(t) = ( (mx-flx)-vx )t^2 + vx*t + flx                                  (*)
+//    Y(t) = ( vy - 2(my-fly) )t^3 + ( 3(my-fly) -2vy )t^2 + vy*t + fly
+//
+//    Here we have to have X'(1) = 2(mx-flx)-vx which is positive <==> vx<2(mx-flx). We also have to have vy<2(my-fly) so that Y'(t)>0 (this is a must otherwise we have local loops!) 
+//    Similarly for the Fr--->M' part of the curve we have the same equation except for the fact that this time we have to have X'(1)<0 so now we have to have vx>2(mx-flx).
+//
+//    If we are stretching the left or right edge of the bitmap then the only difference is that we have to have (X'(1),Y'(1)) = (0,+-c) with + or - c depending on which part of the curve
+//    we are tracing. Then the parametric equation is
+//
+//    X(t) = ( vx - 2(mx-flx) )t^3 + ( 3(mx-flx) -2vx )t^2 + vx*t + flx
+//    Y(t) = ( (my-fly)-vy )t^2 + vy*t + fly
+//
+//    If we are dragging the top edge:    
+//
+//    Now point (x,u_bmpD.x) on the top edge will move by vector (X(t),Y(t)) where those functions are given by (*) and
+//    t =  x < dSx ? (u_bmpD.x+x)/(u_bmpD.x+dSx) : (u_bmpD.x-x)/(u_bmpD.x-dSx)   
+//    Any point (x,y) will move by vector (a*X(t),a*Y(t)) where a is (y+u_bmpD.y)/(2*u_bmpD.y)
+  
+void deform(in int effect, inout vec4 v)
+  {
+  vec2 p = vUniforms[effect+2].yz;  
+  vec2 w = vUniforms[effect].xy;    // w = vec(MM')
+  vec2 vert_vec, horz_vec; 
+  vec2 signXY = sign(p-v.xy);  
+  vec2 time = (u_bmpD.xy+signXY*v.xy)/(u_bmpD.xy+signXY*p);
+  vec2 factorV = vec2(0.5,0.5) + sign(p)*v.xy/(4.0*u_bmpD.xy);
+  vec2 factorD = (u_bmpD.xy-signXY*p)/(2.0*u_bmpD.xy);
+  vec2 vert_d = factorD.x*w;
+  vec2 horz_d = factorD.y*w;
+  vec2 corr = 0.33 / ( 1.0 + (4.0*u_bmpD.x*u_bmpD.x)/dot(w,w) ) * (p+w+signXY*u_bmpD.xy); // .x = the vector tangent to X(t) at Fl = 0.3*vec(LM')  (or vec(RM') if signXY.x=-1). 
+                                                                                       // .y = the vector tangent to X(t) at Fb = 0.3*vec(BM')  (or vec(TM') if signXY.y=-1)
+                                                                                       // the scalar: make the length of the speed vectors at Fl and Fr be 0 when force vector 'w' is zero 
+  vert_vec.x = ( w.x-vert_d.x-corr.x )*time.x*time.x + corr.x*time.x + vert_d.x;
+  horz_vec.y = (-w.y+horz_d.y+corr.y )*time.y*time.y - corr.y*time.y - horz_d.y;
+  vert_vec.y = (-3.0*vert_d.y+2.0*w.y )*time.x*time.x*time.x + (-3.0*w.y+5.0*vert_d.y )*time.x*time.x - vert_d.y*time.x - vert_d.y;
+  horz_vec.x = ( 3.0*horz_d.x-2.0*w.x )*time.y*time.y*time.y + ( 3.0*w.x-5.0*horz_d.x )*time.y*time.y + horz_d.x*time.y + horz_d.x;  
+  
+  v.xy += (factorV.y*vert_vec + factorV.x*horz_vec);  
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Let (v.x,v.y) be point P (the current vertex). 
+// Let vPoint[effect].xy be point S (the center of effect)
+// Let vPoint[effect].xy + vRegion[effect].xy be point O (the center of the Region circle)
+// Let X be the point where the halfline SP meets a) if region is non-null, the region circle b) otherwise, the edge of the bitmap. 
+//
+// If P is inside the Region, this function returns |PX|/||SX|, aka the 'degree' of point P. Otherwise, it returns 0. 
+//
+// We compute the point where half-line from S to P intersects the edge of the bitmap. If that's inside the circle, end. If not, we solve the 
+// the triangle with vertices at O, P and the point of intersection with the circle we are looking for X.
+// We know the lengths |PO|, |OX| and the angle OPX , because cos(OPX) = cos(180-OPS) = -cos(OPS) = -PS*PO/(|PS|*|PO|)
+// then from the law of cosines PX^2 + PO^2 - 2*PX*PO*cos(OPX) = OX^2 so 
+// PX = -a + sqrt(a^2 + OX^2 - PO^2) where a = PS*PO/|PS| but we are really looking for d = |PX|/(|PX|+|PS|) = 1/(1+ (|PS|/|PX|) ) and
+// |PX|/|PS| = -b + sqrt(b^2 + (OX^2-PO^2)/PS^2) where b=PS*PO/|PS|^2 which can be computed with only one sqrt.
+//
+// the trick below is the if-less version of the
+// 
+// t = dx<0.0 ? (u_bmpD.x-v.x) / (u_bmpD.x-ux) : (u_bmpD.x+v.x) / (u_bmpD.x+ux);
+// h = dy<0.0 ? (u_bmpD.y-v.y) / (u_bmpD.y-uy) : (u_bmpD.y+v.y) / (u_bmpD.y+uy);
+// d = min(t,h);      
+//
+// float d = min(-ps.x/(sign(ps.x)*u_bmpD.x+p.x),-ps.y/(sign(ps.y)*u_bmpD.y+p.y))+1.0;    
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// return degree of the point as defined by the bitmap rectangle
+   
+float degree_bitmap(in vec2 S, in vec2 PS)
+  {
+  return min(-PS.x/(sign(PS.x)*u_bmpD.x+S.x),-PS.y/(sign(PS.y)*u_bmpD.y+S.y))+1.0;    
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// return degree of the point as defined by the Region
+// Currently only supports circles; .xy = vector from center of effect to the center of the circle, .z = radius
+      
+float degree_region(in vec3 region, in vec2 PS)
+  {
+  vec2 PO  = PS + region.xy;
+  float D = region.z*region.z-dot(PO,PO);      // D = |OX|^2 - |PO|^2
+  float ps_sq = dot(PS,PS);
+  float DOT  = dot(PS,PO)/ps_sq;
+  
+  return max(sign(D),0.0) / (1.0 + 1.0/(sqrt(DOT*DOT+D/ps_sq)-DOT));  // if D<=0 (i.e p is outside the Region) return 0.
+  }
+   
+//////////////////////////////////////////////////////////////////////////////////////////////
+// return min(degree_bitmap,degree_region). Just like degree_region, currently only supports circles.
+    
+float degree(in vec3 region, in vec2 S, in vec2 PS)
+  {
+  vec2 PO  = PS + region.xy;
+  float D = region.z*region.z-dot(PO,PO);      // D = |OX|^2 - |PO|^2
+  float E = min(-PS.x/(sign(PS.x)*u_bmpD.x+S.x),-PS.y/(sign(PS.y)*u_bmpD.y+S.y))+1.0;    
+  float ps_sq = dot(PS,PS);
+  float DOT  = dot(PS,PO)/ps_sq;
+  
+  return max(sign(D),0.0) * min(1.0/(1.0 + 1.0/(sqrt(DOT*DOT+D/ps_sq)-DOT)),E);  // if D<=0 (i.e p is outside the Region) return 0.
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Distort effect
+//
+// Point (Px,Py) gets moved by vector (Wx,Wy,Wz) where Wx/Wy = Vx/Vy i.e. Wx=aVx and Wy=aVy where 
+// a=Py/Sy (N --> when (Px,Py) is above (Sx,Sy)) or a=Px/Sx (W) or a=(w-Px)/(w-Sx) (E) or a=(h-Py)/(h-Sy) (S) 
+// It remains to be computed which of the N,W,E or S case we have: answer: a = min[ Px/Sx , Py/Sy , (w-Px)/(w-Sx) , (h-Py)/(h-Sy) ]
+// Computations above are valid for screen (0,0)x(w,h) but here we have (-w/2,-h/2)x(w/2,h/2)
+//  
+// the vertical part
+// Let |(v.x,v.y),(ux,uy)| = |PS|, ux-v.x=dx,uy-v.y=dy, f(x) (0<=x<=|SX|) be the shape of the side of the bubble.
+// H(v.x,v.y) = |PS|>|SX| ? 0 : f(|PX|)
+// N(v.x,v.y) = |PS|>|SX| ? (0,0,1) : ( -(dx/|PS|)sin(beta), -(dy/|PS|)sin(beta), cos(beta) ) where tan(beta) is f'(|PX|) 
+// ( i.e. normalize( dx, dy, -|PS|/f'(|PX|))         
+//
+// Now we also have to take into account the effect horizontal move by V=(u_dVx[i],u_dVy[i]) will have on the normal vector.
+// Solution: 
+// 1. Decompose the V into two subcomponents, one parallel to SX and another perpendicular.
+// 2. Convince yourself (draw!) that the perpendicular component has no effect on normals.
+// 3. The parallel component changes the length of |SX| by the factor of a=(|SX|-|Vpar|)/|SX| (where the length can be negative depending on the direction)   
+// 4. that in turn leaves the x and y parts of the normal unchanged and multiplies the z component by a!
+//
+// |Vpar| = (u_dVx[i]*dx - u_dVy[i]*dy) / sqrt(ps_sq) = (Vx*dx-Vy*dy)/ sqrt(ps_sq)  (-Vy because y is inverted)
+// a =  (|SX| - |Vpar|)/|SX| = 1 - |Vpar|/((sqrt(ps_sq)/(1-d)) = 1 - (1-d)*|Vpar|/sqrt(ps_sq) = 1-(1-d)*(Vx*dx-Vy*dy)/ps_sq 
+//
+// Side of the bubble
+// 
+// choose from one of the three bubble shapes: the cone, the thin bubble and the thick bubble          
+// Case 1: 
+// f(t) = t, i.e. f(x) = uz * x/|SX|   (a cone)
+// -|PS|/f'(|PX|) = -|PS|*|SX|/uz but since ps_sq=|PS|^2 and d=|PX|/|SX| then |PS|*|SX| = ps_sq/(1-d)
+// so finally -|PS|/f'(|PX|) = -ps_sq/(uz*(1-d))
+//                    
+// Case 2: 
+// f(t) = 3t^2 - 2t^3 --> f(0)=0, f'(0)=0, f'(1)=0, f(1)=1 (the bell curve)
+// here we have t = x/|SX| which makes f'(|PX|) = 6*uz*|PS|*|PX|/|SX|^3.
+// so -|PS|/f'(|PX|) = (-|SX|^3)/(6uz|PX|) =  (-|SX|^2) / (6*uz*d) but
+// d = |PX|/|SX| and ps_sq = |PS|^2 so |SX|^2 = ps_sq/(1-d)^2
+// so finally -|PS|/f'(|PX|) = -ps_sq/ (6uz*d*(1-d)^2)
+//                  
+// Case 3:
+// f(t) = 3t^4-8t^3+6t^2 would be better as this safisfies f(0)=0, f'(0)=0, f'(1)=0, f(1)=1 and f(0.5)=0.7 and f'(t)= t(t-1)^2 >=0 for t>=0
+// so this produces a fuller, thicker bubble!
+// then -|PS|/f'(|PX|) = (-|PS|*|SX)) / (12uz*d*(d-1)^2) but |PS|*|SX| = ps_sq/(1-d) (see above!) 
+// so finally -|PS|/f'(|PX|) = -ps_sq/ (12uz*d*(1-d)^3)  
+//
+// Now, new requirement: we have to be able to add up normal vectors, i.e. distort already distorted surfaces.
+// If a surface is given by z = f(x,y), then the normal vector at (x0,y0) is given by (df/dx (x0,y0), df/dy (x0,y0), 1 ).
+// so if we have two surfaces defined by f1(x,y) and f2(x,y) with their normals expressed as (f1x,f1y,1) and (f2x,f2y,1) 
+// then the normal to g = f1+f2 is simply given by (f1x+f2x,f1y+f2y,1), i.e. if the third component is 1, then we can simply
+// add up the first and second components.
+//
+// Thus we actually want to compute N(v.x,v.y) = a*(-(dx/|PS|)*f'(|PX|), -(dy/|PS|)*f'(|PX|), 1) and keep adding the first two components. 
+// (a is the horizontal part)
+        
+void distort(in int effect, inout vec4 v, inout vec4 n)
+  {
+  vec2 point = vUniforms[effect+2].yz;
+  vec2 ps = point-v.xy;
+  float d = degree(vUniforms[effect+1],point,ps);
+  vec2 w = vec2(vUniforms[effect].x, -vUniforms[effect].y);
+  float dt = dot(ps,ps);
+  float uz = vUniforms[effect].z; // height of the bubble
+     
+  //v.z += uz*d;                                                                                // cone
+  //b = -(uz*(1.0-d)) / (dt + (1.0-d)*dot(w,ps) + (sign(dt)-1.0) );                             //
+        
+  //v.z += uz*d*d*(3.0-2.0*d);                                                                  // thin bubble
+  //b = -(6.0*uz*d*(1.0-d)*(1.0-d)) / (dt + (1.0-d)*dot(w,ps) + (sign(dt)-1.0) );               //
+        
+  v.z += uz*d*d*(3.0*d*d -8.0*d +6.0);                                                          // thick bubble
+  float b = -(12.0*uz*d*(1.0-d)*(1.0-d)*(1.0-d)) / (dt + (1.0-d)*dot(w,ps) + (sign(dt)-1.0) );  // the last part - (sign-1) is to avoid b being a NaN when ps=(0,0)
+                
+  v.xy += d*w;  
+  n.xy += b*ps;
+  }
+ 
+//////////////////////////////////////////////////////////////////////////////////////////////
+// sink effect
+// Pull P=(v.x,v.y) towards S=vPoint[effect] with P' = P + (1-h)d(S-P)
+// when h>1 we are pushing points away from S: P' = P + (1/h-1)d(S-P)
+ 
+void sink(in int effect,inout vec4 v)
+  {
+  vec2 point = vUniforms[effect+2].yz;
+  vec2 ps = point-v.xy;
+  float h = vUniforms[effect].x;
+  float t = degree(vUniforms[effect+1],point,ps) * (1.0-h)/max(1.0,h);                                                                        
+  
+  v.xy += t*ps;           
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Swirl 
+//
+// Let d be the degree of the current vertex V with respect to center of the effect S and Region vRegion.
+// This effect rotates the current vertex V by vInterpolated.x radians clockwise around the circle dilated 
+// by (1-d) around the center of the effect S.
+
+void swirl(in int effect, inout vec4 P)
+  {
+  vec2 S  = vUniforms[effect+2].yz;
+  vec2 PS = S-P.xy;
+  vec3 SO = vUniforms[effect+1];
+  float d1_circle = degree_region(SO,PS);
+  float d1_bitmap = degree_bitmap(S,PS);
+  float sinA = vUniforms[effect].y;                            // sin(A) precomputed in EffectListVertex.postprocess                                         
+  float cosA = vUniforms[effect].z;                            // cos(A) precomputed in EffectListVertex.postprocess  
+  vec2 PS2 = vec2( PS.x*cosA+PS.y*sinA,-PS.x*sinA+PS.y*cosA ); // vector PS rotated by A radians clockwise around S.                               
+  vec3 SG = (1.0-d1_circle)*SO;                                // coordinates of the dilated circle P is going to get rotated around
+  float d2 = max(0.0,degree(SG,S,PS2));                        // make it a max(0,deg) because when S=left edge of the bitmap, otherwise
+                                                               // some points end up with d2<0 and they disappear off view.
+  P.xy += min(d1_circle,d1_bitmap)*(PS - PS2/(1.0-d2));        // if d2=1 (i.e P=S) we should have P unchanged. How to do it?
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Wave
+//
+
+void wave(in int effect, inout vec4 P)
+  {
+
+  }
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Clamp v.z to (-u_Depth,u_Depth) with the following function:
+// define h to be, say, 0.7; let H=u_Depth
+//      if v.z < -hH then v.z = (-(1-h)^2 * H^2)/(v.z+(2h-1)H) -H   (function satisfying f(-hH)=-hH, f'(-hH)=1, lim f(x) = -H)
+// else if v.z >  hH then v.z = (-(1-h)^2 * H^2)/(v.z-(2h-1)H) +H   (function satisfying f(+hH)=+hH, f'(+hH)=1, lim f(x) = +H)
+// else v.z = v.z  
+	
+void restrict(inout float v)
+  {
+  const float h = 0.7;
+  float signV = 2.0*max(0.0,sign(v))-1.0;
+  float c = ((1.0-h)*(h-1.0)*u_Depth*u_Depth)/(v-signV*(2.0*h-1.0)*u_Depth) +signV*u_Depth;
+  float b = max(0.0,sign(abs(v)-h*u_Depth));
+  
+  v = b*c+(1.0-b)*v; // Avoid branching: if abs(v)>h*u_Depth, then v=c; otherwise v=v.
+  }                
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+  		  
+void main()                                                 	
+  {              
+  vec4 v = vec4( 2.0*u_bmpD*a_Position,1.0 );
+  vec4 n = vec4(a_Normal,0.0);
+
+#if NUM_VERTEX>0
+  for(int i=0; i<vNumEffects; i++)
+    {
+    //switch(vType[i])
+    //  {
+    //  case DISTORT: distort(3*i,v,n); break;
+    //  case DEFORM : deform(3*i,v)   ; break;
+    //  case SINK   : sink(3*i,v)     ; break;
+    //  case SWIRL  : swirl(3*i,v)    ; break;
+    //  case WAVE   : wave(3*i,v)     ; break;
+    //  }
+        
+         if( vType[i]==DISTORT) distort(3*i,v,n);
+    else if( vType[i]==DEFORM ) deform(3*i,v);
+    else if( vType[i]==SINK   ) sink(3*i,v);
+    else if( vType[i]==SWIRL  ) swirl(3*i,v);
+    else if( vType[i]==WAVE   ) wave(3*i,v);   
+    }
+ 
+  restrict(v.z);  
+#endif
+   
+  v_Position      = vec3(u_MVMatrix*v);           
+  v_Color         = a_Color;              
+  v_TexCoordinate = a_TexCoordinate;                                         
+  v_Normal        = normalize(vec3(u_MVMatrix*n));
+  gl_Position     = u_MVPMatrix*v;      
+  }                               
\ No newline at end of file
