commit 421c2728c5a3ae547fae70091c7561b36ed05b30
Author: Leszek Koltunski <leszek@distoretedandroid.org>
Date:   Thu Dec 15 16:02:31 2016 +0000

    Change of names.

diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
index 7b79f57..3a7e7f7 100644
--- a/src/main/java/org/distorted/library/Distorted.java
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -74,7 +74,7 @@ public class Distorted
    */
   public static final int CLONE_FRAGMENT= 0x8;
   /**
-   * When creating an instance of a DistortedObjectTree from another instance, clone the children Nodes.
+   * When creating an instance of a DistortedTree 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.   
@@ -274,6 +274,13 @@ public class Distorted
     return readTextFileFromRawResource( c, R.raw.main_fragment_shader);
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static boolean isInitialized()
+    {
+    return mInitialized;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * When OpenGL context gets created, you need to call this method so that the library can initialise its internal data structures.
@@ -328,7 +335,7 @@ public class Distorted
     GLES20.glEnableVertexAttribArray(mNormalH);
     GLES20.glEnableVertexAttribArray(mTextureCoordH);
    
-    DistortedObjectTree.reset();
+    DistortedTree.reset();
     EffectMessageSender.startSending();
     }
 
@@ -341,25 +348,13 @@ public class Distorted
     {
     DistortedTexture.onDestroy();
     DistortedFramebuffer.onDestroy();
-    DistortedObjectTree.onDestroy();
+    DistortedTree.onDestroy();
     EffectQueue.onDestroy();
-    DistortedEffectQueues.onDestroy();
+    DistortedEffects.onDestroy();
     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;  
-    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
diff --git a/src/main/java/org/distorted/library/DistortedEffectQueues.java b/src/main/java/org/distorted/library/DistortedEffectQueues.java
deleted file mode 100644
index 438c74d..0000000
--- a/src/main/java/org/distorted/library/DistortedEffectQueues.java
+++ /dev/null
@@ -1,759 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-import android.opengl.GLES20;
-
-import org.distorted.library.message.EffectListener;
-import org.distorted.library.type.Data1D;
-import org.distorted.library.type.Data2D;
-import org.distorted.library.type.Data3D;
-import org.distorted.library.type.Data4D;
-import org.distorted.library.type.Data5D;
-import org.distorted.library.type.Static3D;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Class containing {@link EffectTypes#LENGTH} queues, each a class derived from EffectQueue.
- * <p>
- * The queues hold actual effects to be applied to a given (DistortedTexture,GridObject) combo.
- */
-public class DistortedEffectQueues
-  {
-  private static long mNextID =0;
-  private long mID;
-
-  private EffectQueueMatrix    mM;
-  private EffectQueueFragment  mF;
-  private EffectQueueVertex    mV;
-
-  private boolean matrixCloned, vertexCloned, fragmentCloned;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-  private void initializeEffectLists(DistortedEffectQueues d, int flags)
-    {
-    if( (flags & Distorted.CLONE_MATRIX) != 0 )
-      {
-      mM = d.mM;
-      matrixCloned = true;
-      }
-    else
-      {
-      mM = new EffectQueueMatrix(mID);
-      matrixCloned = false;
-      }
-    
-    if( (flags & Distorted.CLONE_VERTEX) != 0 )
-      {
-      mV = d.mV;
-      vertexCloned = true;
-      }
-    else
-      {
-      mV = new EffectQueueVertex(mID);
-      vertexCloned = false;
-      }
-    
-    if( (flags & Distorted.CLONE_FRAGMENT) != 0 )
-      {
-      mF = d.mF;
-      fragmentCloned = true;
-      }
-    else
-      {
-      mF = new EffectQueueFragment(mID);
-      fragmentCloned = false;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  void drawPriv(long currTime, DistortedTexture tex, GridObject grid, DistortedFramebuffer df)
-    {
-    GLES20.glViewport(0, 0, df.mWidth, df.mHeight);
-
-    float halfZ = tex.mHalfX*grid.zFactor;
-
-    mM.compute(currTime);
-    mM.send(df,tex.mHalfX,tex.mHalfY,halfZ);
-      
-    mV.compute(currTime);
-    mV.send(tex.mHalfX,tex.mHalfY,halfZ);
-        
-    mF.compute(currTime);
-    mF.send(tex.mHalfX,tex.mHalfY);
-
-    grid.draw();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  void drawNoEffectsPriv(DistortedTexture tex, GridObject grid, DistortedFramebuffer df)
-    {
-    GLES20.glViewport(0, 0, df.mWidth, df.mHeight);
-
-    mM.sendZero(df,tex.mHalfX,tex.mHalfY,tex.mHalfX*grid.zFactor);
-    mV.sendZero();
-    mF.sendZero();
-
-    grid.draw();
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  private void releasePriv()
-    {
-    if( !matrixCloned  ) mM.abortAll(false);
-    if( !vertexCloned  ) mV.abortAll(false);
-    if( !fragmentCloned) mF.abortAll(false);
-
-    mM = null;
-    mV = null;
-    mF = null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onDestroy()
-    {
-    mNextID = 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create empty effect queue.
- */
-  public DistortedEffectQueues()
-    {
-    mID = mNextID++;
-    initializeEffectLists(this,0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- * <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_VERTEX | CLONE_MATRIX.
- */
-  public DistortedEffectQueues(DistortedEffectQueues dc, int flags)
-    {
-    mID = mNextID++;
-    initializeEffectLists(dc,flags);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Draw the (texture,grid) object to the Framebuffer passed.
- * <p>
- * Must be called from a thread holding OpenGL Context
- *
- * @param currTime Current time, in milliseconds.
- * @param df       Framebuffer to render this to.
- */
-  public void draw(long currTime, DistortedTexture tex, GridObject grid, DistortedFramebuffer df)
-    {
-    tex.createTexture();
-    df.createFBO();
-    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, df.fboIds[0]);
-    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex.mTextureDataH[0]);
-    drawPriv(currTime, tex, grid, df);
-    DistortedFramebuffer.deleteAllMarked();
-    DistortedTexture.deleteAllMarked();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Releases all resources. After this call, the queue should not be used anymore.
- */
-  public synchronized void delete()
-    {
-    releasePriv();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns unique ID of this instance.
- *
- * @return ID of the object.
- */
-  public long getID()
-      {
-      return mID;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Adds the calling class to the list of Listeners that get notified each time some event happens 
- * to one of the Effects in those queues. Nothing will happen if 'el' is already in the list.
- * 
- * @param el A class implementing the EffectListener interface that wants to get notifications.
- */
-  public void registerForMessages(EffectListener el)
-    {
-    mV.registerForMessages(el);
-    mF.registerForMessages(el);
-    mM.registerForMessages(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 deregisterForMessages(EffectListener el)
-    {
-    mV.deregisterForMessages(el);
-    mF.deregisterForMessages(el);
-    mM.deregisterForMessages(el);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Aborts all Effects.
- * @return Number of effects aborted.
- */
-  public int abortAllEffects()
-      {
-      return mM.abortAll(true) + mV.abortAll(true) + mF.abortAll(true);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Aborts all Effects of a given type, for example all MATRIX Effects.
- * 
- * @param type one of the constants defined in {@link EffectTypes}
- * @return Number of effects aborted.
- */
-  public int abortEffects(EffectTypes type)
-    {
-    switch(type)
-      {
-      case MATRIX  : return mM.abortAll(true);
-      case VERTEX  : return mV.abortAll(true);
-      case FRAGMENT: return mF.abortAll(true);
-      default      : return 0;
-      }
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Aborts a single Effect.
- * 
- * @param id ID of the Effect we want to abort.
- * @return number of Effects aborted. Always either 0 or 1.
- */
-  public int abortEffect(long id)
-    {
-    int type = (int)(id&EffectTypes.MASK);
-
-    if( type==EffectTypes.MATRIX.type   ) return mM.removeByID(id>>EffectTypes.LENGTH);
-    if( type==EffectTypes.VERTEX.type   ) return mV.removeByID(id>>EffectTypes.LENGTH);
-    if( type==EffectTypes.FRAGMENT.type ) return mF.removeByID(id>>EffectTypes.LENGTH);
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Abort all Effects of a given name, for example all rotations.
- * 
- * @param name one of the constants defined in {@link EffectNames}
- * @return number of Effects aborted.
- */
-  public int abortEffects(EffectNames name)
-    {
-    switch(name.getType())
-      {
-      case MATRIX  : return mM.removeByType(name);
-      case VERTEX  : return mV.removeByType(name);
-      case FRAGMENT: return mF.removeByType(name);
-      default      : return 0;
-      }
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * 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)
-    {
-    int type = (int)(id&EffectTypes.MASK);
-
-    if( type==EffectTypes.MATRIX.type   )  return mM.printByID(id>>EffectTypes.LENGTH);
-    if( type==EffectTypes.VERTEX.type   )  return mV.printByID(id>>EffectTypes.LENGTH);
-    if( type==EffectTypes.FRAGMENT.type )  return mF.printByID(id>>EffectTypes.LENGTH);
-
-    return false;
-    }
-   
-///////////////////////////////////////////////////////////////////////////////////////////////////   
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Individual effect functions.
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Matrix-based effects
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Moves the Object by a (possibly changing in time) vector.
- * 
- * @param vector 3-dimensional Data which at any given time will return a Static3D
- *               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(Data3D vector)
-    {   
-    return mM.add(EffectNames.MOVE,vector);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Scales the Object by (possibly changing in time) 3D scale factors.
- * 
- * @param scale 3-dimensional Data which at any given time returns a Static3D
- *              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(Data3D scale)
-    {   
-    return mM.add(EffectNames.SCALE,scale);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Scales the Object by one uniform, constant factor in all 3 dimensions. Convenience function.
- *
- * @param scale The factor to scale all 3 dimensions with.
- * @return      ID of the effect added, or -1 if we failed to add one.
- */
-  public long scale(float scale)
-    {
-    return mM.add(EffectNames.SCALE, new Static3D(scale,scale,scale));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Rotates the Object by 'angle' degrees around the center.
- * Static axis of rotation is given by the last parameter.
- *
- * @param angle  Angle that we want to rotate the Object to. Unit: degrees
- * @param axis   Axis of rotation
- * @param center Coordinates of the Point we are rotating around.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long rotate(Data1D angle, Static3D axis, Data3D center )
-    {   
-    return mM.add(EffectNames.ROTATE, angle, axis, center);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Rotates the Object by 'angle' degrees around the center.
- * Here both angle and axis can dynamically change.
- *
- * @param angleaxis Combined 4-tuple representing the (angle,axisX,axisY,axisZ).
- * @param center    Coordinates of the Point we are rotating around.
- * @return          ID of the effect added, or -1 if we failed to add one.
- */
-  public long rotate(Data4D angleaxis, Data3D center)
-    {
-    return mM.add(EffectNames.ROTATE, angleaxis, center);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Rotates the Object by quaternion.
- *
- * @param quaternion The quaternion describing the rotation.
- * @param center     Coordinates of the Point we are rotating around.
- * @return           ID of the effect added, or -1 if we failed to add one.
- */
-  public long quaternion(Data4D quaternion, Data3D center )
-    {
-    return mM.add(EffectNames.QUATERNION,quaternion,center);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Shears the Object.
- *
- * @param shear   The 3-tuple of shear factors. The first controls level
- *                of shearing in the X-axis, second - Y-axis and the third -
- *                Z-axis. Each is the tangens of the shear angle, i.e 0 -
- *                no shear, 1 - shear by 45 degrees (tan(45deg)=1) etc.
- * @param center  Center of shearing, i.e. the point which stays unmoved.
- * @return        ID of the effect added, or -1 if we failed to add one.
- */
-  public long shear(Data3D shear, Data3D center)
-    {
-    return mM.add(EffectNames.SHEAR, shear, center);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Fragment-based effects  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes a certain sub-region of the Object smoothly change all three of its RGB components.
- *        
- * @param blend  1-dimensional Data that returns the level of blend a given pixel will be
- *               mixed with the next parameter 'color': pixel = (1-level)*pixel + level*color.
- *               Valid range: <0,1>
- * @param color  Color to mix. (1,0,0) is RED.
- * @param region Region this Effect is limited to.
- * @param smooth If true, the level of 'blend' will smoothly fade out towards the edges of the region.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long chroma(Data1D blend, Data3D color, Data4D region, boolean smooth)
-    {
-    return mF.add( smooth? EffectNames.SMOOTH_CHROMA:EffectNames.CHROMA, blend, color, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes the whole Object smoothly change all three of its RGB components.
- *
- * @param blend  1-dimensional Data that returns the level of blend a given pixel will be
- *               mixed with the next parameter 'color': pixel = (1-level)*pixel + level*color.
- *               Valid range: <0,1>
- * @param color  Color to mix. (1,0,0) is RED.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long chroma(Data1D blend, Data3D color)
-    {
-    return mF.add(EffectNames.CHROMA, blend, color);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes a certain sub-region of the Object smoothly change its transparency level.
- *        
- * @param alpha  1-dimensional Data that returns the level of transparency we want to have at any given
- *               moment: pixel.a *= alpha.
- *               Valid range: <0,1>
- * @param region Region this Effect is limited to. 
- * @param smooth If true, the level of 'alpha' will smoothly fade out towards the edges of the region.
- * @return       ID of the effect added, or -1 if we failed to add one. 
- */
-  public long alpha(Data1D alpha, Data4D region, boolean smooth)
-    {
-    return mF.add( smooth? EffectNames.SMOOTH_ALPHA:EffectNames.ALPHA, alpha, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes the whole Object smoothly change its transparency level.
- *
- * @param alpha  1-dimensional Data that returns the level of transparency we want to have at any
- *               given moment: pixel.a *= alpha.
- *               Valid range: <0,1>
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long alpha(Data1D alpha)
-    {
-    return mF.add(EffectNames.ALPHA, alpha);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes a certain sub-region of the Object smoothly change its brightness level.
- *        
- * @param brightness 1-dimensional Data that returns the level of brightness we want to have
- *                   at any given moment. Valid range: <0,infinity)
- * @param region     Region this Effect is limited to.
- * @param smooth     If true, the level of 'brightness' will smoothly fade out towards the edges of the region.
- * @return           ID of the effect added, or -1 if we failed to add one.
- */
-  public long brightness(Data1D brightness, Data4D region, boolean smooth)
-    {
-    return mF.add( smooth ? EffectNames.SMOOTH_BRIGHTNESS: EffectNames.BRIGHTNESS, brightness, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes the whole Object smoothly change its brightness level.
- *
- * @param brightness 1-dimensional Data that returns the level of brightness we want to have
- *                   at any given moment. Valid range: <0,infinity)
- * @return           ID of the effect added, or -1 if we failed to add one.
- */
-  public long brightness(Data1D brightness)
-    {
-    return mF.add(EffectNames.BRIGHTNESS, brightness);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes a certain sub-region of the Object smoothly change its contrast level.
- *        
- * @param contrast 1-dimensional Data that returns the level of contrast we want to have
- *                 at any given moment. Valid range: <0,infinity)
- * @param region   Region this Effect is limited to.
- * @param smooth   If true, the level of 'contrast' will smoothly fade out towards the edges of the region.
- * @return         ID of the effect added, or -1 if we failed to add one.
- */
-  public long contrast(Data1D contrast, Data4D region, boolean smooth)
-    {
-    return mF.add( smooth ? EffectNames.SMOOTH_CONTRAST:EffectNames.CONTRAST, contrast, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes the whole Object smoothly change its contrast level.
- *
- * @param contrast 1-dimensional Data that returns the level of contrast we want to have
- *                 at any given moment. Valid range: <0,infinity)
- * @return         ID of the effect added, or -1 if we failed to add one.
- */
-  public long contrast(Data1D contrast)
-    {
-    return mF.add(EffectNames.CONTRAST, contrast);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes a certain sub-region of the Object smoothly change its saturation level.
- *        
- * @param saturation 1-dimensional Data that returns the level of saturation we want to have
- *                   at any given moment. Valid range: <0,infinity)
- * @param region     Region this Effect is limited to.
- * @param smooth     If true, the level of 'saturation' will smoothly fade out towards the edges of the region.
- * @return           ID of the effect added, or -1 if we failed to add one.
- */
-  public long saturation(Data1D saturation, Data4D region, boolean smooth)
-    {
-    return mF.add( smooth ? EffectNames.SMOOTH_SATURATION:EffectNames.SATURATION, saturation, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Makes the whole Object smoothly change its saturation level.
- *
- * @param saturation 1-dimensional Data that returns the level of saturation we want to have
- *                   at any given moment. Valid range: <0,infinity)
- * @return           ID of the effect added, or -1 if we failed to add one.
- */
-  public long saturation(Data1D saturation)
-    {
-    return mF.add(EffectNames.SATURATION, saturation);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Vertex-based effects  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Distort a (possibly changing in time) part of the Object by a (possibly changing in time) vector of force.
- *
- * @param vector 3-dimensional Vector which represents the force the Center of the Effect is
- *               currently being dragged with.
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @param region Region that masks the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long distort(Data3D vector, Data3D center, Data4D region)
-    {  
-    return mV.add(EffectNames.DISTORT, vector, center, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Distort the whole Object by a (possibly changing in time) vector of force.
- *
- * @param vector 3-dimensional Vector which represents the force the Center of the Effect is
- *               currently being dragged with.
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long distort(Data3D vector, Data3D center)
-    {
-    return mV.add(EffectNames.DISTORT, vector, center, null);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Deform the shape of the whole Object with a (possibly changing in time) vector of force applied to
- * a (possibly changing in time) point on the Object.
- *
- * @param vector Vector of force that deforms the shape of the whole Object.
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @param region Region that masks the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long deform(Data3D vector, Data3D center, Data4D region)
-    {
-    return mV.add(EffectNames.DEFORM, vector, center, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Deform the shape of the whole Object with a (possibly changing in time) vector of force applied to
- * a (possibly changing in time) point on the Object.
- *     
- * @param vector Vector of force that deforms the shape of the whole Object.
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long deform(Data3D vector, Data3D center)
-    {  
-    return mV.add(EffectNames.DEFORM, vector, center, null);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////  
-/**
- * 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 sink   The current degree of the Effect.
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @param region Region that masks the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long sink(Data1D sink, Data3D center, Data4D region)
-    {
-    return mV.add(EffectNames.SINK, sink, center, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * 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 sink   The current degree of the Effect.
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long sink(Data1D sink, Data3D center)
-    {
-    return mV.add(EffectNames.SINK, sink, center);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Pull all points around the center of the Effect towards a line passing through the center
- * (that's if degree>=1) or push them away from the line (degree<=1)
- *
- * @param pinch  The current degree of the Effect + angle the line forms with X-axis
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @param region Region that masks the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long pinch(Data2D pinch, Data3D center, Data4D region)
-    {
-    return mV.add(EffectNames.PINCH, pinch, center, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Pull all points around the center of the Effect towards a line passing through the center
- * (that's if degree>=1) or push them away from the line (degree<=1)
- *
- * @param pinch  The current degree of the Effect + angle the line forms with X-axis
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long pinch(Data2D pinch, Data3D center)
-    {
-    return mV.add(EffectNames.PINCH, pinch, center);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////  
-/**
- * Rotate part of the Object around the Center of the Effect by a certain angle.
- *
- * @param swirl  The angle of Swirl (in degrees). Positive values swirl clockwise.
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @param region Region that masks the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long swirl(Data1D swirl, Data3D center, Data4D region)
-    {    
-    return mV.add(EffectNames.SWIRL, swirl, center, region);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Rotate the whole Object around the Center of the Effect by a certain angle.
- *
- * @param swirl  The angle of Swirl (in degrees). Positive values swirl clockwise.
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long swirl(Data1D swirl, Data3D center)
-    {
-    return mV.add(EffectNames.SWIRL, swirl, center);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Directional, sinusoidal wave effect.
- *
- * @param wave   A 5-dimensional data structure describing the wave: first member is the amplitude,
- *               second is the wave length, third is the phase (i.e. when phase = PI/2, the sine
- *               wave at the center does not start from sin(0), but from sin(PI/2) ) and the next two
- *               describe the 'direction' of the wave.
- *               <p>
- *               Wave direction is defined to be a 3D vector of length 1. To define such vectors, we
- *               need 2 floats: thus the third member is the angle Alpha (in degrees) which the vector
- *               forms with the XY-plane, and the fourth is the angle Beta (again in degrees) which
- *               the projection of the vector to the XY-plane forms with the Y-axis (counterclockwise).
- *               <p>
- *               <p>
- *               Example1: if Alpha = 90, Beta = 90, (then V=(0,0,1) ) and the wave acts 'vertically'
- *               in the X-direction, i.e. cross-sections of the resulting surface with the XZ-plane
- *               will be sine shapes.
- *               <p>
- *               Example2: if Alpha = 90, Beta = 0, the again V=(0,0,1) and the wave is 'vertical',
- *               but this time it waves in the Y-direction, i.e. cross sections of the surface and the
- *               YZ-plane with be sine shapes.
- *               <p>
- *               Example3: if Alpha = 0 and Beta = 45, then V=(sqrt(2)/2, -sqrt(2)/2, 0) and the wave
- *               is entirely 'horizontal' and moves point (x,y,0) in direction V by whatever is the
- *               value if sin at this point.
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long wave(Data5D wave, Data3D center)
-    {
-    return mV.add(EffectNames.WAVE, wave, center, null);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Directional, sinusoidal wave effect.
- *
- * @param wave   see {@link DistortedEffectQueues#wave(Data5D,Data3D)}
- * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
- * @param region Region that masks the Effect.
- * @return       ID of the effect added, or -1 if we failed to add one.
- */
-  public long wave(Data5D wave, Data3D center, Data4D region)
-    {
-    return mV.add(EffectNames.WAVE, wave, center, region);
-    }
-  }
diff --git a/src/main/java/org/distorted/library/DistortedEffects.java b/src/main/java/org/distorted/library/DistortedEffects.java
new file mode 100644
index 0000000..b22dd75
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedEffects.java
@@ -0,0 +1,759 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library;
+
+import android.opengl.GLES20;
+
+import org.distorted.library.message.EffectListener;
+import org.distorted.library.type.Data1D;
+import org.distorted.library.type.Data2D;
+import org.distorted.library.type.Data3D;
+import org.distorted.library.type.Data4D;
+import org.distorted.library.type.Data5D;
+import org.distorted.library.type.Static3D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Class containing {@link EffectTypes#LENGTH} queues, each a class derived from EffectQueue.
+ * <p>
+ * The queues hold actual effects to be applied to a given (DistortedTexture,GridObject) combo.
+ */
+public class DistortedEffects
+  {
+  private static long mNextID =0;
+  private long mID;
+
+  private EffectQueueMatrix    mM;
+  private EffectQueueFragment  mF;
+  private EffectQueueVertex    mV;
+
+  private boolean matrixCloned, vertexCloned, fragmentCloned;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+  private void initializeEffectLists(DistortedEffects d, int flags)
+    {
+    if( (flags & Distorted.CLONE_MATRIX) != 0 )
+      {
+      mM = d.mM;
+      matrixCloned = true;
+      }
+    else
+      {
+      mM = new EffectQueueMatrix(mID);
+      matrixCloned = false;
+      }
+    
+    if( (flags & Distorted.CLONE_VERTEX) != 0 )
+      {
+      mV = d.mV;
+      vertexCloned = true;
+      }
+    else
+      {
+      mV = new EffectQueueVertex(mID);
+      vertexCloned = false;
+      }
+    
+    if( (flags & Distorted.CLONE_FRAGMENT) != 0 )
+      {
+      mF = d.mF;
+      fragmentCloned = true;
+      }
+    else
+      {
+      mF = new EffectQueueFragment(mID);
+      fragmentCloned = false;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  void drawPriv(long currTime, DistortedTexture tex, GridObject grid, DistortedFramebuffer df)
+    {
+    GLES20.glViewport(0, 0, df.mWidth, df.mHeight);
+
+    float halfZ = tex.mHalfX*grid.zFactor;
+
+    mM.compute(currTime);
+    mM.send(df,tex.mHalfX,tex.mHalfY,halfZ);
+      
+    mV.compute(currTime);
+    mV.send(tex.mHalfX,tex.mHalfY,halfZ);
+        
+    mF.compute(currTime);
+    mF.send(tex.mHalfX,tex.mHalfY);
+
+    grid.draw();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  void drawNoEffectsPriv(DistortedTexture tex, GridObject grid, DistortedFramebuffer df)
+    {
+    GLES20.glViewport(0, 0, df.mWidth, df.mHeight);
+
+    mM.sendZero(df,tex.mHalfX,tex.mHalfY,tex.mHalfX*grid.zFactor);
+    mV.sendZero();
+    mF.sendZero();
+
+    grid.draw();
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  private void releasePriv()
+    {
+    if( !matrixCloned  ) mM.abortAll(false);
+    if( !vertexCloned  ) mV.abortAll(false);
+    if( !fragmentCloned) mF.abortAll(false);
+
+    mM = null;
+    mV = null;
+    mF = null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void onDestroy()
+    {
+    mNextID = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create empty effect queue.
+ */
+  public DistortedEffects()
+    {
+    mID = mNextID++;
+    initializeEffectLists(this,0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ * <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_VERTEX | CLONE_MATRIX.
+ */
+  public DistortedEffects(DistortedEffects dc, int flags)
+    {
+    mID = mNextID++;
+    initializeEffectLists(dc,flags);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Draw the (texture,grid) object to the Framebuffer passed.
+ * <p>
+ * Must be called from a thread holding OpenGL Context
+ *
+ * @param currTime Current time, in milliseconds.
+ * @param df       Framebuffer to render this to.
+ */
+  public void draw(long currTime, DistortedTexture tex, GridObject grid, DistortedFramebuffer df)
+    {
+    tex.createTexture();
+    df.createFBO();
+    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, df.fboIds[0]);
+    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex.mTextureDataH[0]);
+    drawPriv(currTime, tex, grid, df);
+    DistortedFramebuffer.deleteAllMarked();
+    DistortedTexture.deleteAllMarked();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Releases all resources. After this call, the queue should not be used anymore.
+ */
+  public synchronized void delete()
+    {
+    releasePriv();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns unique ID of this instance.
+ *
+ * @return ID of the object.
+ */
+  public long getID()
+      {
+      return mID;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds the calling class to the list of Listeners that get notified each time some event happens 
+ * to one of the Effects in those queues. Nothing will happen if 'el' is already in the list.
+ * 
+ * @param el A class implementing the EffectListener interface that wants to get notifications.
+ */
+  public void registerForMessages(EffectListener el)
+    {
+    mV.registerForMessages(el);
+    mF.registerForMessages(el);
+    mM.registerForMessages(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 deregisterForMessages(EffectListener el)
+    {
+    mV.deregisterForMessages(el);
+    mF.deregisterForMessages(el);
+    mM.deregisterForMessages(el);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts all Effects.
+ * @return Number of effects aborted.
+ */
+  public int abortAllEffects()
+      {
+      return mM.abortAll(true) + mV.abortAll(true) + mF.abortAll(true);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts all Effects of a given type, for example all MATRIX Effects.
+ * 
+ * @param type one of the constants defined in {@link EffectTypes}
+ * @return Number of effects aborted.
+ */
+  public int abortEffects(EffectTypes type)
+    {
+    switch(type)
+      {
+      case MATRIX  : return mM.abortAll(true);
+      case VERTEX  : return mV.abortAll(true);
+      case FRAGMENT: return mF.abortAll(true);
+      default      : return 0;
+      }
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts a single Effect.
+ * 
+ * @param id ID of the Effect we want to abort.
+ * @return number of Effects aborted. Always either 0 or 1.
+ */
+  public int abortEffect(long id)
+    {
+    int type = (int)(id&EffectTypes.MASK);
+
+    if( type==EffectTypes.MATRIX.type   ) return mM.removeByID(id>>EffectTypes.LENGTH);
+    if( type==EffectTypes.VERTEX.type   ) return mV.removeByID(id>>EffectTypes.LENGTH);
+    if( type==EffectTypes.FRAGMENT.type ) return mF.removeByID(id>>EffectTypes.LENGTH);
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Abort all Effects of a given name, for example all rotations.
+ * 
+ * @param name one of the constants defined in {@link EffectNames}
+ * @return number of Effects aborted.
+ */
+  public int abortEffects(EffectNames name)
+    {
+    switch(name.getType())
+      {
+      case MATRIX  : return mM.removeByType(name);
+      case VERTEX  : return mV.removeByType(name);
+      case FRAGMENT: return mF.removeByType(name);
+      default      : return 0;
+      }
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * 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)
+    {
+    int type = (int)(id&EffectTypes.MASK);
+
+    if( type==EffectTypes.MATRIX.type   )  return mM.printByID(id>>EffectTypes.LENGTH);
+    if( type==EffectTypes.VERTEX.type   )  return mV.printByID(id>>EffectTypes.LENGTH);
+    if( type==EffectTypes.FRAGMENT.type )  return mF.printByID(id>>EffectTypes.LENGTH);
+
+    return false;
+    }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Individual effect functions.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Matrix-based effects
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Moves the Object by a (possibly changing in time) vector.
+ * 
+ * @param vector 3-dimensional Data which at any given time will return a Static3D
+ *               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(Data3D vector)
+    {   
+    return mM.add(EffectNames.MOVE,vector);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Scales the Object by (possibly changing in time) 3D scale factors.
+ * 
+ * @param scale 3-dimensional Data which at any given time returns a Static3D
+ *              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(Data3D scale)
+    {   
+    return mM.add(EffectNames.SCALE,scale);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Scales the Object by one uniform, constant factor in all 3 dimensions. Convenience function.
+ *
+ * @param scale The factor to scale all 3 dimensions with.
+ * @return      ID of the effect added, or -1 if we failed to add one.
+ */
+  public long scale(float scale)
+    {
+    return mM.add(EffectNames.SCALE, new Static3D(scale,scale,scale));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotates the Object by 'angle' degrees around the center.
+ * Static axis of rotation is given by the last parameter.
+ *
+ * @param angle  Angle that we want to rotate the Object to. Unit: degrees
+ * @param axis   Axis of rotation
+ * @param center Coordinates of the Point we are rotating around.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long rotate(Data1D angle, Static3D axis, Data3D center )
+    {   
+    return mM.add(EffectNames.ROTATE, angle, axis, center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotates the Object by 'angle' degrees around the center.
+ * Here both angle and axis can dynamically change.
+ *
+ * @param angleaxis Combined 4-tuple representing the (angle,axisX,axisY,axisZ).
+ * @param center    Coordinates of the Point we are rotating around.
+ * @return          ID of the effect added, or -1 if we failed to add one.
+ */
+  public long rotate(Data4D angleaxis, Data3D center)
+    {
+    return mM.add(EffectNames.ROTATE, angleaxis, center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotates the Object by quaternion.
+ *
+ * @param quaternion The quaternion describing the rotation.
+ * @param center     Coordinates of the Point we are rotating around.
+ * @return           ID of the effect added, or -1 if we failed to add one.
+ */
+  public long quaternion(Data4D quaternion, Data3D center )
+    {
+    return mM.add(EffectNames.QUATERNION,quaternion,center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Shears the Object.
+ *
+ * @param shear   The 3-tuple of shear factors. The first controls level
+ *                of shearing in the X-axis, second - Y-axis and the third -
+ *                Z-axis. Each is the tangens of the shear angle, i.e 0 -
+ *                no shear, 1 - shear by 45 degrees (tan(45deg)=1) etc.
+ * @param center  Center of shearing, i.e. the point which stays unmoved.
+ * @return        ID of the effect added, or -1 if we failed to add one.
+ */
+  public long shear(Data3D shear, Data3D center)
+    {
+    return mM.add(EffectNames.SHEAR, shear, center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Fragment-based effects  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Object smoothly change all three of its RGB components.
+ *        
+ * @param blend  1-dimensional Data that returns the level of blend a given pixel will be
+ *               mixed with the next parameter 'color': pixel = (1-level)*pixel + level*color.
+ *               Valid range: <0,1>
+ * @param color  Color to mix. (1,0,0) is RED.
+ * @param region Region this Effect is limited to.
+ * @param smooth If true, the level of 'blend' will smoothly fade out towards the edges of the region.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long chroma(Data1D blend, Data3D color, Data4D region, boolean smooth)
+    {
+    return mF.add( smooth? EffectNames.SMOOTH_CHROMA:EffectNames.CHROMA, blend, color, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes the whole Object smoothly change all three of its RGB components.
+ *
+ * @param blend  1-dimensional Data that returns the level of blend a given pixel will be
+ *               mixed with the next parameter 'color': pixel = (1-level)*pixel + level*color.
+ *               Valid range: <0,1>
+ * @param color  Color to mix. (1,0,0) is RED.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long chroma(Data1D blend, Data3D color)
+    {
+    return mF.add(EffectNames.CHROMA, blend, color);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Object smoothly change its transparency level.
+ *        
+ * @param alpha  1-dimensional Data that returns the level of transparency we want to have at any given
+ *               moment: pixel.a *= alpha.
+ *               Valid range: <0,1>
+ * @param region Region this Effect is limited to. 
+ * @param smooth If true, the level of 'alpha' will smoothly fade out towards the edges of the region.
+ * @return       ID of the effect added, or -1 if we failed to add one. 
+ */
+  public long alpha(Data1D alpha, Data4D region, boolean smooth)
+    {
+    return mF.add( smooth? EffectNames.SMOOTH_ALPHA:EffectNames.ALPHA, alpha, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes the whole Object smoothly change its transparency level.
+ *
+ * @param alpha  1-dimensional Data that returns the level of transparency we want to have at any
+ *               given moment: pixel.a *= alpha.
+ *               Valid range: <0,1>
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long alpha(Data1D alpha)
+    {
+    return mF.add(EffectNames.ALPHA, alpha);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Object smoothly change its brightness level.
+ *        
+ * @param brightness 1-dimensional Data that returns the level of brightness we want to have
+ *                   at any given moment. Valid range: <0,infinity)
+ * @param region     Region this Effect is limited to.
+ * @param smooth     If true, the level of 'brightness' will smoothly fade out towards the edges of the region.
+ * @return           ID of the effect added, or -1 if we failed to add one.
+ */
+  public long brightness(Data1D brightness, Data4D region, boolean smooth)
+    {
+    return mF.add( smooth ? EffectNames.SMOOTH_BRIGHTNESS: EffectNames.BRIGHTNESS, brightness, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes the whole Object smoothly change its brightness level.
+ *
+ * @param brightness 1-dimensional Data that returns the level of brightness we want to have
+ *                   at any given moment. Valid range: <0,infinity)
+ * @return           ID of the effect added, or -1 if we failed to add one.
+ */
+  public long brightness(Data1D brightness)
+    {
+    return mF.add(EffectNames.BRIGHTNESS, brightness);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Object smoothly change its contrast level.
+ *        
+ * @param contrast 1-dimensional Data that returns the level of contrast we want to have
+ *                 at any given moment. Valid range: <0,infinity)
+ * @param region   Region this Effect is limited to.
+ * @param smooth   If true, the level of 'contrast' will smoothly fade out towards the edges of the region.
+ * @return         ID of the effect added, or -1 if we failed to add one.
+ */
+  public long contrast(Data1D contrast, Data4D region, boolean smooth)
+    {
+    return mF.add( smooth ? EffectNames.SMOOTH_CONTRAST:EffectNames.CONTRAST, contrast, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes the whole Object smoothly change its contrast level.
+ *
+ * @param contrast 1-dimensional Data that returns the level of contrast we want to have
+ *                 at any given moment. Valid range: <0,infinity)
+ * @return         ID of the effect added, or -1 if we failed to add one.
+ */
+  public long contrast(Data1D contrast)
+    {
+    return mF.add(EffectNames.CONTRAST, contrast);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes a certain sub-region of the Object smoothly change its saturation level.
+ *        
+ * @param saturation 1-dimensional Data that returns the level of saturation we want to have
+ *                   at any given moment. Valid range: <0,infinity)
+ * @param region     Region this Effect is limited to.
+ * @param smooth     If true, the level of 'saturation' will smoothly fade out towards the edges of the region.
+ * @return           ID of the effect added, or -1 if we failed to add one.
+ */
+  public long saturation(Data1D saturation, Data4D region, boolean smooth)
+    {
+    return mF.add( smooth ? EffectNames.SMOOTH_SATURATION:EffectNames.SATURATION, saturation, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Makes the whole Object smoothly change its saturation level.
+ *
+ * @param saturation 1-dimensional Data that returns the level of saturation we want to have
+ *                   at any given moment. Valid range: <0,infinity)
+ * @return           ID of the effect added, or -1 if we failed to add one.
+ */
+  public long saturation(Data1D saturation)
+    {
+    return mF.add(EffectNames.SATURATION, saturation);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Vertex-based effects  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Distort a (possibly changing in time) part of the Object by a (possibly changing in time) vector of force.
+ *
+ * @param vector 3-dimensional Vector which represents the force the Center of the Effect is
+ *               currently being dragged with.
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @param region Region that masks the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long distort(Data3D vector, Data3D center, Data4D region)
+    {  
+    return mV.add(EffectNames.DISTORT, vector, center, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Distort the whole Object by a (possibly changing in time) vector of force.
+ *
+ * @param vector 3-dimensional Vector which represents the force the Center of the Effect is
+ *               currently being dragged with.
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long distort(Data3D vector, Data3D center)
+    {
+    return mV.add(EffectNames.DISTORT, vector, center, null);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Deform the shape of the whole Object with a (possibly changing in time) vector of force applied to
+ * a (possibly changing in time) point on the Object.
+ *
+ * @param vector Vector of force that deforms the shape of the whole Object.
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @param region Region that masks the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long deform(Data3D vector, Data3D center, Data4D region)
+    {
+    return mV.add(EffectNames.DEFORM, vector, center, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Deform the shape of the whole Object with a (possibly changing in time) vector of force applied to
+ * a (possibly changing in time) point on the Object.
+ *     
+ * @param vector Vector of force that deforms the shape of the whole Object.
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long deform(Data3D vector, Data3D center)
+    {  
+    return mV.add(EffectNames.DEFORM, vector, center, null);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * 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 sink   The current degree of the Effect.
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @param region Region that masks the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long sink(Data1D sink, Data3D center, Data4D region)
+    {
+    return mV.add(EffectNames.SINK, sink, center, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * 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 sink   The current degree of the Effect.
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long sink(Data1D sink, Data3D center)
+    {
+    return mV.add(EffectNames.SINK, sink, center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Pull all points around the center of the Effect towards a line passing through the center
+ * (that's if degree>=1) or push them away from the line (degree<=1)
+ *
+ * @param pinch  The current degree of the Effect + angle the line forms with X-axis
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @param region Region that masks the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long pinch(Data2D pinch, Data3D center, Data4D region)
+    {
+    return mV.add(EffectNames.PINCH, pinch, center, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Pull all points around the center of the Effect towards a line passing through the center
+ * (that's if degree>=1) or push them away from the line (degree<=1)
+ *
+ * @param pinch  The current degree of the Effect + angle the line forms with X-axis
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long pinch(Data2D pinch, Data3D center)
+    {
+    return mV.add(EffectNames.PINCH, pinch, center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Rotate part of the Object around the Center of the Effect by a certain angle.
+ *
+ * @param swirl  The angle of Swirl (in degrees). Positive values swirl clockwise.
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @param region Region that masks the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long swirl(Data1D swirl, Data3D center, Data4D region)
+    {    
+    return mV.add(EffectNames.SWIRL, swirl, center, region);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Rotate the whole Object around the Center of the Effect by a certain angle.
+ *
+ * @param swirl  The angle of Swirl (in degrees). Positive values swirl clockwise.
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long swirl(Data1D swirl, Data3D center)
+    {
+    return mV.add(EffectNames.SWIRL, swirl, center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Directional, sinusoidal wave effect.
+ *
+ * @param wave   A 5-dimensional data structure describing the wave: first member is the amplitude,
+ *               second is the wave length, third is the phase (i.e. when phase = PI/2, the sine
+ *               wave at the center does not start from sin(0), but from sin(PI/2) ) and the next two
+ *               describe the 'direction' of the wave.
+ *               <p>
+ *               Wave direction is defined to be a 3D vector of length 1. To define such vectors, we
+ *               need 2 floats: thus the third member is the angle Alpha (in degrees) which the vector
+ *               forms with the XY-plane, and the fourth is the angle Beta (again in degrees) which
+ *               the projection of the vector to the XY-plane forms with the Y-axis (counterclockwise).
+ *               <p>
+ *               <p>
+ *               Example1: if Alpha = 90, Beta = 90, (then V=(0,0,1) ) and the wave acts 'vertically'
+ *               in the X-direction, i.e. cross-sections of the resulting surface with the XZ-plane
+ *               will be sine shapes.
+ *               <p>
+ *               Example2: if Alpha = 90, Beta = 0, the again V=(0,0,1) and the wave is 'vertical',
+ *               but this time it waves in the Y-direction, i.e. cross sections of the surface and the
+ *               YZ-plane with be sine shapes.
+ *               <p>
+ *               Example3: if Alpha = 0 and Beta = 45, then V=(sqrt(2)/2, -sqrt(2)/2, 0) and the wave
+ *               is entirely 'horizontal' and moves point (x,y,0) in direction V by whatever is the
+ *               value if sin at this point.
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long wave(Data5D wave, Data3D center)
+    {
+    return mV.add(EffectNames.WAVE, wave, center, null);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Directional, sinusoidal wave effect.
+ *
+ * @param wave   see {@link DistortedEffects#wave(Data5D,Data3D)}
+ * @param center 3-dimensional Data that, at any given time, returns the Center of the Effect.
+ * @param region Region that masks the Effect.
+ * @return       ID of the effect added, or -1 if we failed to add one.
+ */
+  public long wave(Data5D wave, Data3D center, Data4D region)
+    {
+    return mV.add(EffectNames.WAVE, wave, center, region);
+    }
+  }
diff --git a/src/main/java/org/distorted/library/DistortedFramebuffer.java b/src/main/java/org/distorted/library/DistortedFramebuffer.java
index 66f727b..fa0d075 100644
--- a/src/main/java/org/distorted/library/DistortedFramebuffer.java
+++ b/src/main/java/org/distorted/library/DistortedFramebuffer.java
@@ -31,7 +31,7 @@ import java.util.LinkedList;
  * <p>
  * User is able to create either Framebuffers from objects already constructed outside
  * of the library (the first constructor; primary use case: the screen) or an offscreen
- * FBOs (used by the DistortedObjectTree, but also can be used by external users of the library)
+ * FBOs (used by the DistortedTree, but also can be used by external users of the library)
  * <p>
  * Also, keep all objects created in a static LinkedList. The point: we need to be able to mark
  * Framebuffers for deletion, and delete all marked objects later at a convenient time (that's
diff --git a/src/main/java/org/distorted/library/DistortedObjectTree.java b/src/main/java/org/distorted/library/DistortedObjectTree.java
deleted file mode 100644
index 2dbcd96..0000000
--- a/src/main/java/org/distorted/library/DistortedObjectTree.java
+++ /dev/null
@@ -1,495 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-import android.opengl.GLES20;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Class which represents a Node in a Tree of (Texture,EffectQueue,Grid) set.
- *  
- * Having organized such sets into a Tree, we can then render any Node to any Framebuffer.
- * That recursively renders the set held in the Node and all its children.
- */
-public class DistortedObjectTree
-  {
-  private static HashMap<ArrayList<Long>,NodeData> mMapNodeID = new HashMap<>();
-  private static long mNextNodeID =0;
-
-  private GridObject mGrid;
-  private DistortedEffectQueues mQueues;
-  private DistortedTexture mTexture;
-  private NodeData mData;
-
-  private DistortedObjectTree mParent;
-  private ArrayList<DistortedObjectTree> mChildren;
-  private int[] mNumChildren;  // ==mChildren.length(), but we only create mChildren if the first one gets added
-
-  private class NodeData
-    {
-    long ID;
-    int numPointingNodes;
-    DistortedFramebuffer mDF;
-    int numRendered;
-
-    NodeData(long id)
-      {
-      ID              = id;
-      numPointingNodes= 1;
-      mDF             = null;
-      numRendered     = 0;
-      }
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static synchronized void onDestroy()
-    {
-    mNextNodeID = 0;
-    mMapNodeID.clear();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  private void drawRecursive(long currTime, DistortedFramebuffer df)
-    {
-    mTexture.createTexture();
-
-    if( mNumChildren[0]<=0 )
-      {
-      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture.mTextureDataH[0]);
-      }
-    else
-      {
-      mData.mDF.createFBO();
-
-      if( mData.numRendered==0 )
-        {
-        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mData.mDF.fboIds[0]);
-
-        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-        GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
-      
-        if( mTexture.mBitmapSet[0] )
-          {
-          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture.mTextureDataH[0]);
-          mQueues.drawNoEffectsPriv(mTexture, mGrid, mData.mDF);
-          }
-      
-        synchronized(this)
-          {
-          for(int i=0; i<mNumChildren[0]; i++)
-            {
-            mChildren.get(i).drawRecursive(currTime, mData.mDF);
-            }
-          }
-        }
-
-      mData.numRendered++;
-      mData.numRendered %= mData.numPointingNodes;
-
-      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, df.fboIds[0]);
-      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mData.mDF.texIds[0]);
-      }
-    
-    mQueues.drawPriv(currTime, mTexture, mGrid, df);
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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<>();
-     
-    ret.add( mTexture.getID() );
-    DistortedObjectTree 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)
-    {
-    boolean otherNodesPoint = (mData.numPointingNodes>1);
-
-    if( otherNodesPoint ) mData.numPointingNodes--;
-    else                  mMapNodeID.remove(oldList);
-
-    NodeData newData = mMapNodeID.get(newList);
-    
-    if( newData==null )
-      {
-      if( otherNodesPoint )  mData = new NodeData(++mNextNodeID);
-      else                   mData.ID = ++mNextNodeID;  // numPointingNodes must be 1 already
-
-      if( newList.size()>1 )
-        {
-        if( mData.mDF==null )
-          mData.mDF = new DistortedFramebuffer(mTexture.getWidth(), mTexture.getHeight());
-        }
-      else
-        {
-        if( mData.mDF!=null )
-          {
-          mData.mDF.markForDeletion();
-          mData.mDF = null;
-          }
-        else
-          {
-          android.util.Log.e("DistortedObjectTree", "adjustNodeData: impossible situation??");
-          }
-        }
-
-      mMapNodeID.put(newList, mData);
-      }
-    else
-      {
-      newData.numPointingNodes++;
-      mData = newData;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////  
-// this will be called on startup and every time OpenGL context has been lost
-
-  static void reset()
-    {
-    NodeData tmp;   
-     
-    for(ArrayList<Long> key: mMapNodeID.keySet())
-      {
-      tmp = mMapNodeID.get(key);
-          
-      if( tmp.mDF != null )
-        {
-    	  tmp.mDF.reset();
-        tmp.numRendered = 0;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Debug - print all the Node IDs
-
-  void debug(int depth)
-    {
-    String tmp="";
-    int i;
-
-    for(i=0; i<depth; i++) tmp +="   ";
-    tmp += (mData.ID+" (nodes: "+mData.numPointingNodes+")");
-
-    android.util.Log.e("NODE", tmp);
-
-    for(i=0; i<mNumChildren[0]; i++)
-      mChildren.get(i).debug(depth+1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Debug - print contents of the HashMap
-
-  static void debugMap()
-    {
-    NodeData tmp;
-
-    for(ArrayList<Long> key: mMapNodeID.keySet())
-      {
-      tmp = mMapNodeID.get(key);
-
-      android.util.Log.e("NODE", "key="+key+" NodeID: "+tmp.ID);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Constructs new Node of the Tree.
- *     
- * @param texture DistortedTexture to put into the new Node.
- * @param queues  DistortedEffectQueues to put into the new Node.
- * @param grid GridObject to put into the new Node.
- */
-  public DistortedObjectTree(DistortedTexture texture, DistortedEffectQueues queues, GridObject grid)
-    {
-    mTexture= texture;
-    mQueues = queues;
-    mGrid   = grid;
-    mParent = null;
-    mChildren = null;
-    mNumChildren = new int[1];
-    mNumChildren[0] = 0;
-   
-    ArrayList<Long> list = new ArrayList<>();
-    list.add(mTexture.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 DistortedObjectTree 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 DistortedObjectTree(DistortedObjectTree node, int flags)
-    {
-    mParent = null;
-    mTexture= new DistortedTexture(node.mTexture,flags);
-    mQueues = new DistortedEffectQueues(node.mQueues, flags);
-    mGrid   = node.mGrid;
-
-    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.
- */
-  public synchronized void attach(DistortedObjectTree node)
-    {
-    ArrayList<Long> prev = generateIDList(); 
-   
-    if( mChildren==null ) mChildren = new ArrayList<>(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 texture DistortedTexture to initialize our child Node with.
- * @param queues DistortedEffectQueues to initialize our child Node with.
- * @param grid GridObject to initialize our child Node with.
- * @return the newly constructed child Node, or null if we couldn't allocate resources.
- */
-  public synchronized DistortedObjectTree attach(DistortedTexture texture, DistortedEffectQueues queues, GridObject grid)
-    {
-    ArrayList<Long> prev = generateIDList(); 
-      
-    if( mChildren==null ) mChildren = new ArrayList<>(2);
-    DistortedObjectTree node = new DistortedObjectTree(texture,queues,grid);
-    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(DistortedObjectTree 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 queues DistortedEffectQueues to remove.
- * @return <code>true</code> if the child was successfully removed.
- */
-  public synchronized boolean detach(DistortedEffectQueues queues)
-    {
-    long id = queues.getID();
-    DistortedObjectTree node;
-   
-    for(int i=0; i<mNumChildren[0]; i++)
-      {
-      node = mChildren.get(i);
-     
-      if( node.mQueues.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 Framebuffer passed.
- * <p>
- * Must be called from a thread holding OpenGL Context
- *
- * @param currTime Current time, in milliseconds.
- * @param df       Framebuffer to render this to.
- */
-  public void draw(long currTime, DistortedFramebuffer df)
-    {
-    df.createFBO();
-    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, df.fboIds[0]);
-    drawRecursive(currTime,df);
-    DistortedFramebuffer.deleteAllMarked();
-    DistortedTexture.deleteAllMarked();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the DistortedEffectQueues object that's in the Node.
- * 
- * @return The DistortedEffectQueues contained in the Node.
- */
-  public DistortedEffectQueues getQueues()
-    {
-    return mQueues;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the DistortedTexture object that's in the Node.
- *
- * @return The DistortedTexture contained in the Node.
- */
-  public DistortedTexture getTexture()
-    {
-    return mTexture;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  }
-
diff --git a/src/main/java/org/distorted/library/DistortedTexture.java b/src/main/java/org/distorted/library/DistortedTexture.java
index 4333d99..502000c 100644
--- a/src/main/java/org/distorted/library/DistortedTexture.java
+++ b/src/main/java/org/distorted/library/DistortedTexture.java
@@ -50,7 +50,7 @@ public class DistortedTexture
 // upper-left corner. Everywhere else the origin is in the lower-left corner. Thus we have to flip.
 // The alternative solution, namely inverting the y-coordinate of the TexCoord does not really work-
 // i.e. it works only in case of rendering directly to the screen, but if we render to an FBO and
-// then take the FBO and render to screen, (DistortedObjectTree does so!) things get inverted as textures
+// then take the FBO and render to screen, (DistortedTree does so!) things get inverted as textures
 // created from FBO have their origins in the lower-left... Mindfuck!
 
   private static Bitmap flipBitmap(Bitmap src)
diff --git a/src/main/java/org/distorted/library/DistortedTree.java b/src/main/java/org/distorted/library/DistortedTree.java
new file mode 100644
index 0000000..f7f880c
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedTree.java
@@ -0,0 +1,495 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.opengl.GLES20;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Class which represents a Node in a Tree of (Texture,EffectQueue,Grid) set.
+ *  
+ * Having organized such sets into a Tree, we can then render any Node to any Framebuffer.
+ * That recursively renders the set held in the Node and all its children.
+ */
+public class DistortedTree
+  {
+  private static HashMap<ArrayList<Long>,NodeData> mMapNodeID = new HashMap<>();
+  private static long mNextNodeID =0;
+
+  private GridObject mGrid;
+  private DistortedEffects mQueues;
+  private DistortedTexture mTexture;
+  private NodeData mData;
+
+  private DistortedTree mParent;
+  private ArrayList<DistortedTree> mChildren;
+  private int[] mNumChildren;  // ==mChildren.length(), but we only create mChildren if the first one gets added
+
+  private class NodeData
+    {
+    long ID;
+    int numPointingNodes;
+    DistortedFramebuffer mDF;
+    int numRendered;
+
+    NodeData(long id)
+      {
+      ID              = id;
+      numPointingNodes= 1;
+      mDF             = null;
+      numRendered     = 0;
+      }
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized void onDestroy()
+    {
+    mNextNodeID = 0;
+    mMapNodeID.clear();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  private void drawRecursive(long currTime, DistortedFramebuffer df)
+    {
+    mTexture.createTexture();
+
+    if( mNumChildren[0]<=0 )
+      {
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture.mTextureDataH[0]);
+      }
+    else
+      {
+      mData.mDF.createFBO();
+
+      if( mData.numRendered==0 )
+        {
+        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mData.mDF.fboIds[0]);
+
+        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+        GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+      
+        if( mTexture.mBitmapSet[0] )
+          {
+          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture.mTextureDataH[0]);
+          mQueues.drawNoEffectsPriv(mTexture, mGrid, mData.mDF);
+          }
+      
+        synchronized(this)
+          {
+          for(int i=0; i<mNumChildren[0]; i++)
+            {
+            mChildren.get(i).drawRecursive(currTime, mData.mDF);
+            }
+          }
+        }
+
+      mData.numRendered++;
+      mData.numRendered %= mData.numPointingNodes;
+
+      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, df.fboIds[0]);
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mData.mDF.texIds[0]);
+      }
+    
+    mQueues.drawPriv(currTime, mTexture, mGrid, df);
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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<>();
+     
+    ret.add( mTexture.getID() );
+    DistortedTree 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)
+    {
+    boolean otherNodesPoint = (mData.numPointingNodes>1);
+
+    if( otherNodesPoint ) mData.numPointingNodes--;
+    else                  mMapNodeID.remove(oldList);
+
+    NodeData newData = mMapNodeID.get(newList);
+    
+    if( newData==null )
+      {
+      if( otherNodesPoint )  mData = new NodeData(++mNextNodeID);
+      else                   mData.ID = ++mNextNodeID;  // numPointingNodes must be 1 already
+
+      if( newList.size()>1 )
+        {
+        if( mData.mDF==null )
+          mData.mDF = new DistortedFramebuffer(mTexture.getWidth(), mTexture.getHeight());
+        }
+      else
+        {
+        if( mData.mDF!=null )
+          {
+          mData.mDF.markForDeletion();
+          mData.mDF = null;
+          }
+        else
+          {
+          android.util.Log.e("DistortedTree", "adjustNodeData: impossible situation??");
+          }
+        }
+
+      mMapNodeID.put(newList, mData);
+      }
+    else
+      {
+      newData.numPointingNodes++;
+      mData = newData;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+// this will be called on startup and every time OpenGL context has been lost
+
+  static void reset()
+    {
+    NodeData tmp;   
+     
+    for(ArrayList<Long> key: mMapNodeID.keySet())
+      {
+      tmp = mMapNodeID.get(key);
+          
+      if( tmp.mDF != null )
+        {
+    	  tmp.mDF.reset();
+        tmp.numRendered = 0;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Debug - print all the Node IDs
+
+  void debug(int depth)
+    {
+    String tmp="";
+    int i;
+
+    for(i=0; i<depth; i++) tmp +="   ";
+    tmp += (mData.ID+" (nodes: "+mData.numPointingNodes+")");
+
+    android.util.Log.e("NODE", tmp);
+
+    for(i=0; i<mNumChildren[0]; i++)
+      mChildren.get(i).debug(depth+1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Debug - print contents of the HashMap
+
+  static void debugMap()
+    {
+    NodeData tmp;
+
+    for(ArrayList<Long> key: mMapNodeID.keySet())
+      {
+      tmp = mMapNodeID.get(key);
+
+      android.util.Log.e("NODE", "key="+key+" NodeID: "+tmp.ID);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructs new Node of the Tree.
+ *     
+ * @param texture DistortedTexture to put into the new Node.
+ * @param queues  DistortedEffects to put into the new Node.
+ * @param grid GridObject to put into the new Node.
+ */
+  public DistortedTree(DistortedTexture texture, DistortedEffects queues, GridObject grid)
+    {
+    mTexture= texture;
+    mQueues = queues;
+    mGrid   = grid;
+    mParent = null;
+    mChildren = null;
+    mNumChildren = new int[1];
+    mNumChildren[0] = 0;
+   
+    ArrayList<Long> list = new ArrayList<>();
+    list.add(mTexture.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 DistortedTree 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 DistortedTree(DistortedTree node, int flags)
+    {
+    mParent = null;
+    mTexture= new DistortedTexture(node.mTexture,flags);
+    mQueues = new DistortedEffects(node.mQueues, flags);
+    mGrid   = node.mGrid;
+
+    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.
+ */
+  public synchronized void attach(DistortedTree node)
+    {
+    ArrayList<Long> prev = generateIDList(); 
+   
+    if( mChildren==null ) mChildren = new ArrayList<>(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 texture DistortedTexture to initialize our child Node with.
+ * @param queues DistortedEffects to initialize our child Node with.
+ * @param grid GridObject to initialize our child Node with.
+ * @return the newly constructed child Node, or null if we couldn't allocate resources.
+ */
+  public synchronized DistortedTree attach(DistortedTexture texture, DistortedEffects queues, GridObject grid)
+    {
+    ArrayList<Long> prev = generateIDList(); 
+      
+    if( mChildren==null ) mChildren = new ArrayList<>(2);
+    DistortedTree node = new DistortedTree(texture,queues,grid);
+    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(DistortedTree 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 queues DistortedEffects to remove.
+ * @return <code>true</code> if the child was successfully removed.
+ */
+  public synchronized boolean detach(DistortedEffects queues)
+    {
+    long id = queues.getID();
+    DistortedTree node;
+   
+    for(int i=0; i<mNumChildren[0]; i++)
+      {
+      node = mChildren.get(i);
+     
+      if( node.mQueues.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 Framebuffer passed.
+ * <p>
+ * Must be called from a thread holding OpenGL Context
+ *
+ * @param currTime Current time, in milliseconds.
+ * @param df       Framebuffer to render this to.
+ */
+  public void draw(long currTime, DistortedFramebuffer df)
+    {
+    df.createFBO();
+    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, df.fboIds[0]);
+    drawRecursive(currTime,df);
+    DistortedFramebuffer.deleteAllMarked();
+    DistortedTexture.deleteAllMarked();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the DistortedEffects object that's in the Node.
+ * 
+ * @return The DistortedEffects contained in the Node.
+ */
+  public DistortedEffects getEffects()
+    {
+    return mQueues;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the DistortedTexture object that's in the Node.
+ *
+ * @return The DistortedTexture contained in the Node.
+ */
+  public DistortedTexture getTexture()
+    {
+    return mTexture;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  }
+
diff --git a/src/main/java/org/distorted/library/GridObject.java b/src/main/java/org/distorted/library/GridObject.java
index 3c72773..cf771fb 100644
--- a/src/main/java/org/distorted/library/GridObject.java
+++ b/src/main/java/org/distorted/library/GridObject.java
@@ -36,7 +36,7 @@ public abstract class GridObject
    protected FloatBuffer mGridPositions,mGridNormals,mGridTexture;
 
    final float zFactor; // strange workaround for the fact that we need to somehow store the 'depth'
-                        // of the Grid. Used in DistortedEffectQueues. See DistortedTexture.getDepth().
+                        // of the Grid. Used in DistortedEffects. See DistortedTexture.getDepth().
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
