commit 4e2382f38ef83d34bb60f7ce02fdf2d4ab37fb17
Author: Leszek Koltunski <leszek@distorted.org>
Date:   Mon Dec 12 23:47:59 2016 +0000

    Beginnings of split of DistortedObject into two separate classes: DistortedEffectQueues and DistortedTexture.
    
    Still does not compile, but pushing already.

diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
index f324600..e1c6b1e 100644
--- a/src/main/java/org/distorted/library/Distorted.java
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -354,7 +354,7 @@ public class Distorted
     GLES20.glEnableVertexAttribArray(mNormalH);
     GLES20.glEnableVertexAttribArray(mTextureCoordH);
    
-    DistortedObject.reset();
+    DistortedTexture.reset();
     DistortedObjectTree.reset();
     EffectMessageSender.startSending();
     }
@@ -379,7 +379,7 @@ public class Distorted
  */
   public static void onDestroy()
     {
-    DistortedObject.release();
+    DistortedTexture.release();
     DistortedFramebuffer.release();
     DistortedObjectTree.release();
     EffectQueue.release();
diff --git a/src/main/java/org/distorted/library/DistortedEffectQueues.java b/src/main/java/org/distorted/library/DistortedEffectQueues.java
new file mode 100644
index 0000000..7ea6e63
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedEffectQueues.java
@@ -0,0 +1,763 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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)
+    {
+    DistortedFramebuffer.deleteAllMarked();
+
+    GLES20.glViewport(0, 0, df.mWidth, df.mHeight);
+
+    mM.compute(currTime);
+    mM.send(df,tex.mHalfX,tex.mHalfY,tex.mHalfZ);
+      
+    mV.compute(currTime);
+    mV.send(tex.mHalfX,tex.mHalfY,tex.mHalfZ);
+        
+    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.mHalfZ);
+    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;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.
+ * We only call this from the descendant's classes' constructors where we have to pay attention
+ * to give it the appropriate type of a DistortedObject!
+ *
+ * @param dc    Source object to create our object from
+ * @param flags A bitmask of values specifying what to copy.
+ *              For example, CLONE_BITMAP | CLONE_MATRIX.
+ */
+  public DistortedEffectQueues(DistortedEffectQueues dc, int flags)
+    {
+    mID = mNextID++;
+    initializeEffectLists(dc,flags);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Draw the DistortedObject to the location specified by current Matrix effects.    
+ *     
+ * @param currTime current time, in milliseconds.
+ *        This gets passed on to Dynamics inside the Effects that are currently applied to the
+ *        Object.
+ */
+  public void draw(long currTime, DistortedTexture tex, GridObject grid)
+    {
+    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex.mTextureDataH[0]);
+    drawPriv(currTime, tex, grid, Distorted.mFramebuffer);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Draw the DistortedObject to the Framebuffer passed.
+ *
+ * @param currTime Current time, in milliseconds.
+ * @param df       Framebuffer to render this to.
+ */
+  public void draw(long currTime, DistortedTexture tex, GridObject grid, DistortedFramebuffer df)
+    {
+    df.setAsOutput();
+    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex.mTextureDataH[0]);
+    drawPriv(currTime, tex, grid, df);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Releases all resources.
+ */
+  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 that are currently applied to the DistortedObject.
+ * 
+ * @param el A class implementing the EffectListener interface that wants to get notifications.
+ */
+  public void addEventListener(EffectListener el)
+    {
+    mV.addListener(el);
+    mF.addListener(el);
+    mM.addListener(el);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the calling class from the list of Listeners.
+ * 
+ * @param el A class implementing the EffectListener interface that no longer wants to get notifications.
+ */
+  public void removeEventListener(EffectListener el)
+    {
+    mV.removeListener(el);
+    mF.removeListener(el);
+    mM.removeListener(el);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * 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 DistortedObject#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/DistortedObject.java b/src/main/java/org/distorted/library/DistortedObject.java
deleted file mode 100644
index 6130953..0000000
--- a/src/main/java/org/distorted/library/DistortedObject.java
+++ /dev/null
@@ -1,982 +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.graphics.Bitmap;
-import android.graphics.Matrix;
-import android.opengl.GLES20;
-import android.opengl.GLUtils;
-
-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;
-
-import java.util.HashMap;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * All Objects to which Distorted Graphics effects can be applied need to be extended from here.
- * <p>
- * General idea is as follows:
- * <ul>
- * <li> Create an instance of (some class descended from) DistortedObject
- * <li> Paint something onto the Bitmap that's backing it up
- * <li> Apply some effects
- * <li> Draw it!
- * </ul>
- * <p>
- * The effects we can apply fall into three general categories:
- * <ul>
- * <li> Matrix Effects, i.e. ones that change the Bitmap's ModelView Matrix (moves, scales, rotations)
- * <li> Vertex Effects, i.e. effects that are implemented in the Vertex Shader. Those typically change
- *      the shape of (some sub-Region of) the Bitmap in some way (deforms, distortions, sinks)
- * <li> Fragment Effects, i.e. effects that change (some of) the pixels of the Bitmap (transparency, macroblock)
- * </ul>
- * <p>
- * Just like in DistortedObjectTree and DistortedFramebuffer, we need to have a static list of all
- * DistortedObjects currently created by the application so that we can implement the 'mark for
- * deletion now - actually delete on next render' thing.
- * We need to be able to quickly retrieve an Object by its ID, thus a HashMap.
- */
-public class DistortedObject
-  {
-  private static long mNextID =0;
-  private static HashMap<Long,DistortedObject> mObjects = new HashMap<>();
-
-  private EffectQueueMatrix    mM;
-  private EffectQueueFragment  mF;
-  private EffectQueueVertex    mV;
-
-  private boolean matrixCloned, vertexCloned, fragmentCloned;
-  private long mID;
-  private int mSizeX, mSizeY, mSizeZ;   // in screen space
-  private float mHalfX, mHalfY, mHalfZ; // halves of the above
-
-  private Bitmap[] mBmp= null; //
-  int[] mTextureDataH;         // have to be shared among all the cloned Objects
-  boolean[] mBitmapSet;        //
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// We have to flip vertically every single Bitmap that we get fed with.
-//
-// Reason: textures read from files are the only objects in OpenGL which have their origins at the
-// 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
-// created from FBO have their origins in the lower-left... Mindfuck!
-
-  private static Bitmap flipBitmap(Bitmap src)
-    {
-    Matrix matrix = new Matrix();
-    matrix.preScale(1.0f,-1.0f);
-
-    return Bitmap.createBitmap(src,0,0,src.getWidth(),src.getHeight(), matrix,true);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void initializeData(int x, int y, int z)
-    {
-    mSizeX= x; mHalfX = x/2.0f;
-    mSizeY= y; mHalfY = y/2.0f;
-    mSizeZ= z; mHalfZ = z/2.0f;
-
-    mID = mNextID++;
-    mObjects.put(mID,this);
-
-    mTextureDataH   = new int[1];
-    mTextureDataH[0]= 0;
-    mBmp            = new Bitmap[1];
-    mBmp[0]         = null;
-    mBitmapSet      = new boolean[1];
-    mBitmapSet[0]   = false;
-      
-    initializeEffectLists(this,0);
-      
-    if( Distorted.isInitialized() ) resetTexture();
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-  private void initializeEffectLists(DistortedObject d, int flags)
-    {
-    long objID = d.getID();
-
-    if( (flags & Distorted.CLONE_MATRIX) != 0 )
-      {
-      mM = d.mM;
-      matrixCloned = true;
-      }
-    else
-      {
-      mM = new EffectQueueMatrix(objID);
-      matrixCloned = false;
-      }
-    
-    if( (flags & Distorted.CLONE_VERTEX) != 0 )
-      {
-      mV = d.mV;
-      vertexCloned = true;
-      }
-    else
-      {
-      mV = new EffectQueueVertex(objID);
-      vertexCloned = false;
-      }
-    
-    if( (flags & Distorted.CLONE_FRAGMENT) != 0 )
-      {
-      mF = d.mF;
-      fragmentCloned = true;
-      }
-    else
-      {
-      mF = new EffectQueueFragment(objID);
-      fragmentCloned = false;
-      }
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this will be called on startup and every time OpenGL context has been lost
-// also call this from the constructor if the OpenGL context has been created already.
-    
-  private void resetTexture()
-    {
-    if( mTextureDataH!=null )
-      {
-      if( mTextureDataH[0]==0 ) GLES20.glGenTextures(1, mTextureDataH, 0);
-
-      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataH[0]);
-      GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR );
-      GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR );
-      GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE );
-      GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE );
-       
-      if( mBmp!=null && mBmp[0]!=null)
-        {
-        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, flipBitmap(mBmp[0]), 0);
-        mBmp[0] = null;
-        }
-      }
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  void drawPriv(long currTime, GridObject grid, DistortedFramebuffer df)
-    {
-    DistortedFramebuffer.deleteAllMarked();
-
-    GLES20.glViewport(0, 0, df.mWidth, df.mHeight);
-
-    mM.compute(currTime);
-    mM.send(df,mHalfX,mHalfY,mHalfZ);
-      
-    mV.compute(currTime);
-    mV.send(mHalfX,mHalfY,mHalfZ);
-        
-    mF.compute(currTime);
-    mF.send(mHalfX,mHalfY);
-
-    grid.draw();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  void drawNoEffectsPriv(GridObject grid, DistortedFramebuffer df)
-    {
-    GLES20.glViewport(0, 0, df.mWidth, df.mHeight);
-
-    mM.sendZero(df,mHalfX,mHalfY,mHalfZ);
-    mV.sendZero();
-    mF.sendZero();
-
-    grid.draw();
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  private void releasePriv()
-    {
-    if( !matrixCloned  ) mM.abortAll(false);
-    if( !vertexCloned  ) mV.abortAll(false);
-    if( !fragmentCloned) mF.abortAll(false);
-
-    mBmp          = null;
-    mM            = null;
-    mV            = null;
-    mF            = null;
-    mTextureDataH = null;
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  long getBitmapID()
-    {
-    return mBmp==null ? 0 : mBmp.hashCode();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static synchronized void reset()
-    {
-    for(long id: mObjects.keySet())
-      {
-      mObjects.get(id).resetTexture();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static synchronized void release()
-    {
-    for(long id: mObjects.keySet())
-      {
-      mObjects.get(id).releasePriv();
-      }
-
-    mObjects.clear();
-    mNextID = 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create empty effect queue with no Bitmap.
- */
-  public DistortedObject(int width, int height, int depth)
-    {
-    initializeData(width,height,depth);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor used to create a DistortedObject based on various parts of another object.
- * <p>
- * Whatever we do not clone gets created just like in the default constructor.
- * We only call this from the descendant's classes' constructors where we have to pay attention
- * to give it the appropriate type of a DistortedObject!
- *
- * @param dc    Source object to create our object from
- * @param flags A bitmask of values specifying what to copy.
- *              For example, CLONE_BITMAP | CLONE_MATRIX.
- */
-  public DistortedObject(DistortedObject dc, int flags)
-    {
-    initializeEffectLists(dc,flags);
-
-    mID = mNextID++;
-    mObjects.put(mID,this);
-
-    mSizeX = dc.mSizeX;
-    mSizeY = dc.mSizeY;
-    mSizeZ = dc.mSizeZ;
-    mHalfX = dc.mHalfX;
-    mHalfY = dc.mHalfY;
-    mHalfZ = dc.mHalfZ;
-
-    if( (flags & Distorted.CLONE_BITMAP) != 0 )
-      {
-      mTextureDataH = dc.mTextureDataH;
-      mBmp          = dc.mBmp;
-      mBitmapSet    = dc.mBitmapSet;
-      }
-    else
-      {
-      mTextureDataH   = new int[1];
-      mTextureDataH[0]= 0;
-      mBitmapSet      = new boolean[1];
-      mBitmapSet[0]   = false;
-      mBmp            = new Bitmap[1];
-      mBmp[0]         = null;
-
-      if( Distorted.isInitialized() ) resetTexture();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Draw the DistortedObject to the location specified by current Matrix effects.    
- *     
- * @param currTime current time, in milliseconds.
- *        This gets passed on to Dynamics inside the Effects that are currently applied to the
- *        Object.
- */
-  public void draw(long currTime, GridObject grid)
-    {
-    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
-    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataH[0]);
-    drawPriv(currTime, grid, Distorted.mFramebuffer);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Draw the DistortedObject to the Framebuffer passed.
- *
- * @param currTime Current time, in milliseconds.
- * @param df       Framebuffer to render this to.
- */
-  public void draw(long currTime, GridObject grid, DistortedFramebuffer df)
-    {
-    df.setAsOutput();
-    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataH[0]);
-    drawPriv(currTime,grid, df);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Releases all resources.
- */
-  public synchronized void delete()
-    {
-    releasePriv();
-    mObjects.remove(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Sets the underlying android.graphics.Bitmap object and uploads it to the GPU. 
- * <p>
- * You can only recycle() the passed Bitmap once the OpenGL context gets created (i.e. after call 
- * to onSurfaceCreated) because only after this point can the Library upload it to the GPU!
- * 
- * @param bmp The android.graphics.Bitmap object to apply effects to and display.
- */
-   
-  public void setTexture(Bitmap bmp)
-    {
-    mBitmapSet[0] = true;
-/*
-    mSizeX= bmp.getWidth() ; mHalfX = mSizeX/2;
-    mSizeY= bmp.getHeight(); mHalfY = mSizeY/2;
-*/
-    if( Distorted.isInitialized() )
-      {
-      GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
-      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataH[0]);
-      GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, flipBitmap(bmp), 0);
-      }
-    else
-      {
-      mBmp[0] = bmp;
-      }
-    }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Adds the calling class to the list of Listeners that get notified each time some event happens 
- * to one of the Effects that are currently applied to the DistortedObject.
- * 
- * @param el A class implementing the EffectListener interface that wants to get notifications.
- */
-  public void addEventListener(EffectListener el)
-    {
-    mV.addListener(el);
-    mF.addListener(el);
-    mM.addListener(el);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Removes the calling class from the list of Listeners.
- * 
- * @param el A class implementing the EffectListener interface that no longer wants to get notifications.
- */
-  public void removeEventListener(EffectListener el)
-    {
-    mV.removeListener(el);
-    mF.removeListener(el);
-    mM.removeListener(el);
-    }
-   
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the height of the DistortedObject.
- *    
- * @return height of the object, in pixels.
- */
-  public int getWidth()
-     {
-     return mSizeX;   
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the width of the DistortedObject.
- * 
- * @return width of the Object, in pixels.
- */
-  public int getHeight()
-      {
-      return mSizeY;  
-      }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the depth of the DistortedObject.
- * 
- * @return depth of the Object, in pixels.
- */
-  public int getDepth()
-      {
-      return mSizeZ;  
-      }
-        
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns unique ID of this instance.
- * 
- * @return ID of the object.
- */
-  public long getID()
-      {
-      return mID;  
-      }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Aborts all Effects.
- * @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 DistortedObject#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/DistortedObjectTree.java b/src/main/java/org/distorted/library/DistortedObjectTree.java
index 83af6e7..2d589e2 100644
--- a/src/main/java/org/distorted/library/DistortedObjectTree.java
+++ b/src/main/java/org/distorted/library/DistortedObjectTree.java
@@ -38,7 +38,8 @@ public class DistortedObjectTree
   private static long mNextNodeID =0;
 
   private GridObject mGrid;
-  private DistortedObject mObject;
+  private DistortedEffectQueues mQueues;
+  private DistortedTexture mTexture;
   private NodeData mData;
 
   private DistortedObjectTree mParent;
@@ -87,7 +88,7 @@ public class DistortedObjectTree
     {
     if( mNumChildren[0]<=0 )
       {
-      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mObject.mTextureDataH[0]);
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture.mTextureDataH[0]);
       }
     else
       {
@@ -99,10 +100,10 @@ public class DistortedObjectTree
         GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
         GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
       
-        if( mObject.mBitmapSet[0] )
+        if( mTexture.mBitmapSet[0] )
           {
-          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mObject.mTextureDataH[0]);
-          mObject.drawNoEffectsPriv(mGrid, mData.mDF);
+          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture.mTextureDataH[0]);
+          mQueues.drawNoEffectsPriv(mTexture, mGrid, mData.mDF);
           }
       
         synchronized(this)
@@ -118,7 +119,7 @@ public class DistortedObjectTree
       mData.mDF.setAsInput();
       }
     
-    mObject.drawPriv(currTime, mGrid, df);
+    mQueues.drawPriv(currTime, mTexture, mGrid, df);
     }
   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -146,7 +147,7 @@ public class DistortedObjectTree
     {
     ArrayList<Long> ret = new ArrayList<>();
      
-    ret.add( mNumChildren[0]>0 ? mObject.getBitmapID() : mObject.getID() );
+    ret.add( mNumChildren[0]>0 ? mTexture.getBitmapID() : mTexture.getID() );
     DistortedObjectTree node;
    
     for(int i=0; i<mNumChildren[0]; i++)
@@ -177,7 +178,7 @@ public class DistortedObjectTree
       if( newList.size()>1 )
         {
         if( mData.mDF==null )
-          mData.mDF = new DistortedFramebuffer(mObject.getWidth(), mObject.getHeight());
+          mData.mDF = new DistortedFramebuffer(mTexture.getWidth(), mTexture.getHeight());
         }
       else
         {
@@ -259,11 +260,14 @@ public class DistortedObjectTree
 /**
  * Constructs new Node of the Tree.
  *     
- * @param obj DistortedObject to put into the new Node.
+ * @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(DistortedObject obj, GridObject grid)
+  public DistortedObjectTree(DistortedTexture texture, DistortedEffectQueues queues, GridObject grid)
     {
-    mObject = obj;
+    mTexture= texture;
+    mQueues = queues;
     mGrid   = grid;
     mParent = null;
     mChildren = null;
@@ -271,7 +275,7 @@ public class DistortedObjectTree
     mNumChildren[0] = 0;
    
     ArrayList<Long> list = new ArrayList<>();
-    list.add(obj.getID());
+    list.add(queues.getID());   // TODO: this should depend on both texture and queues! Maybe even on grid as well
       
     mData = mMapNodeID.get(list);
    
@@ -299,7 +303,8 @@ public class DistortedObjectTree
   public DistortedObjectTree(DistortedObjectTree node, int flags)
     {
     mParent = null;
-    mObject = new DistortedObject(node.mObject,flags);
+    mTexture= new DistortedTexture(node.mTexture,flags);
+    mQueues = new DistortedEffectQueues(node.mQueues, flags);
     mGrid   = node.mGrid;
 
     if( (flags & Distorted.CLONE_CHILDREN) != 0 )
@@ -352,15 +357,17 @@ public class DistortedObjectTree
 /**
  * Adds a new child to the last position in the list of our Node's children.
  * 
- * @param obj DistortedObject to initialize our child Node with.
+ * @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(DistortedObject obj, GridObject grid)
+  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(obj,grid);
+    DistortedObjectTree node = new DistortedObjectTree(texture,queues,grid);
     node.mParent = this;
     mChildren.add(node);
     mNumChildren[0]++;
@@ -401,19 +408,19 @@ public class DistortedObjectTree
 /**
  * Removes the first occurrence of a specified child from the list of children of our Node.
  * 
- * @param obj DistortedObject to remove.
+ * @param queues DistortedEffectQueues to remove.
  * @return <code>true</code> if the child was successfully removed.
  */
-  public synchronized boolean detach(DistortedObject obj)
+  public synchronized boolean detach(DistortedEffectQueues queues)
     {
-    long id = obj.getID();
+    long id = queues.getID();
     DistortedObjectTree node;
    
     for(int i=0; i<mNumChildren[0]; i++)
       {
       node = mChildren.get(i);
      
-      if( node.mObject.getID()==id )
+      if( node.mQueues.getID()==id )
         {
         ArrayList<Long> prev = generateIDList();   
      
@@ -480,13 +487,24 @@ public class DistortedObjectTree
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Returns the DistortedObject object that's in the Node.
+ * Returns the DistortedEffectQueues object that's in the Node.
  * 
- * @return The DistortedObject contained in the Node.
+ * @return The DistortedEffectQueues contained in the Node.
  */
-  public DistortedObject getObject()
+  public DistortedEffectQueues getQueues()
     {
-    return mObject;
+    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
new file mode 100644
index 0000000..7cbb382
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedTexture.java
@@ -0,0 +1,273 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+
+import java.util.HashMap;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class DistortedTexture
+  {
+  private static long mNextID =0;
+  private static HashMap<Long,DistortedTexture> mTextures = new HashMap<>();
+
+  private int mSizeX, mSizeY, mSizeZ;   // in screen space
+  float mHalfX, mHalfY, mHalfZ;         // halves of the above
+
+  private long mID;
+
+  private Bitmap[] mBmp= null; //
+  int[] mTextureDataH;         // have to be shared among all the cloned Objects
+  boolean[] mBitmapSet;        //
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// We have to flip vertically every single Bitmap that we get fed with.
+//
+// Reason: textures read from files are the only objects in OpenGL which have their origins at the
+// 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
+// created from FBO have their origins in the lower-left... Mindfuck!
+
+  private static Bitmap flipBitmap(Bitmap src)
+    {
+    Matrix matrix = new Matrix();
+    matrix.preScale(1.0f,-1.0f);
+
+    return Bitmap.createBitmap(src,0,0,src.getWidth(),src.getHeight(), matrix,true);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this will be called on startup and every time OpenGL context has been lost
+// also call this from the constructor if the OpenGL context has been created already.
+
+  private void resetTexture()
+    {
+    if( mTextureDataH!=null )
+      {
+      if( mTextureDataH[0]==0 ) GLES20.glGenTextures(1, mTextureDataH, 0);
+
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataH[0]);
+      GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR );
+      GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR );
+      GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE );
+      GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE );
+
+      if( mBmp!=null && mBmp[0]!=null)
+        {
+        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, flipBitmap(mBmp[0]), 0);
+        mBmp[0] = null;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void releasePriv()
+    {
+    mBmp          = null;
+    mTextureDataH = null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  long getBitmapID()
+    {
+    return mBmp==null ? 0 : mBmp.hashCode();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized void reset()
+    {
+    for(long id: mTextures.keySet())
+      {
+      mTextures.get(id).resetTexture();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized void release()
+    {
+    for(long id: mTextures.keySet())
+      {
+      mTextures.get(id).releasePriv();
+      }
+
+    mTextures.clear();
+    mNextID = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create empty texture of given dimensions.
+ */
+  public DistortedTexture(int width, int height, int depth)
+    {
+    mSizeX= width ; mHalfX = mSizeX/2.0f;
+    mSizeY= height; mHalfY = mSizeY/2.0f;
+    mSizeZ= depth ; mHalfZ = mSizeZ/2.0f;
+
+    mID = mNextID++;
+    mTextures.put(mID,this);
+
+    mTextureDataH   = new int[1];
+    mTextureDataH[0]= 0;
+    mBmp            = new Bitmap[1];
+    mBmp[0]         = null;
+    mBitmapSet      = new boolean[1];
+    mBitmapSet[0]   = false;
+
+    if( Distorted.isInitialized() ) resetTexture();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Copy constructor.
+ * <p>
+ * Whatever we do not clone gets created just like in the default constructor.
+ *
+ * @param dt    Source object to create our object from
+ * @param flags A bitmask of values specifying what to copy.
+ *              For example, CLONE_BITMAP.
+ */
+  public DistortedTexture(DistortedTexture dt, int flags)
+    {
+    mID = mNextID++;
+    mTextures.put(mID,this);
+
+    mSizeX = dt.mSizeX;
+    mSizeY = dt.mSizeY;
+    mSizeZ = dt.mSizeZ;
+    mHalfX = dt.mHalfX;
+    mHalfY = dt.mHalfY;
+    mHalfZ = dt.mHalfZ;
+
+    if( (flags & Distorted.CLONE_BITMAP) != 0 )
+      {
+      mTextureDataH = dt.mTextureDataH;
+      mBmp          = dt.mBmp;
+      mBitmapSet    = dt.mBitmapSet;
+      }
+    else
+      {
+      mTextureDataH   = new int[1];
+      mTextureDataH[0]= 0;
+      mBitmapSet      = new boolean[1];
+      mBitmapSet[0]   = false;
+      mBmp            = new Bitmap[1];
+      mBmp[0]         = null;
+
+      if( Distorted.isInitialized() ) resetTexture();
+      }
+    }
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Releases all resources.
+ */
+  public synchronized void delete()
+    {
+    releasePriv();
+    mTextures.remove(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the underlying android.graphics.Bitmap object and uploads it to the GPU.
+ * <p>
+ * You can only recycle() the passed Bitmap once the OpenGL context gets created (i.e. after call
+ * to onSurfaceCreated) because only after this point can the Library upload it to the GPU!
+ *
+ * @param bmp The android.graphics.Bitmap object to apply effects to and display.
+ */
+
+  public void setTexture(Bitmap bmp)
+    {
+    mBitmapSet[0] = true;
+
+    if( Distorted.isInitialized() )
+      {
+      GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataH[0]);
+      GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, flipBitmap(bmp), 0);
+      }
+    else
+      {
+      mBmp[0] = bmp;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the height of the DistortedObject.
+ *
+ * @return height of the object, in pixels.
+ */
+  public int getWidth()
+     {
+     return mSizeX;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the width of the DistortedObject.
+ *
+ * @return width of the Object, in pixels.
+ */
+  public int getHeight()
+      {
+      return mSizeY;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the depth of the DistortedObject.
+ *
+ * @return depth of the Object, in pixels.
+ */
+  public int getDepth()
+      {
+      return mSizeZ;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns unique ID of this instance.
+ *
+ * @return ID of the object.
+ */
+  public long getID()
+      {
+      return mID;
+      }
+
+  }
diff --git a/src/main/java/org/distorted/library/GridObject.java b/src/main/java/org/distorted/library/GridObject.java
index aaf1428..4f99b48 100644
--- a/src/main/java/org/distorted/library/GridObject.java
+++ b/src/main/java/org/distorted/library/GridObject.java
@@ -32,8 +32,7 @@ public abstract class GridObject
    protected static final int NORMAL_DATA_SIZE  = 3; // Size of the normal data in elements.
    protected static final int TEX_DATA_SIZE     = 2; // Size of the texture coordinate data in elements. 
 
-   protected int dataLength;                       
-      
+   protected int dataLength;
    protected FloatBuffer mGridPositions,mGridNormals,mGridTexture;
  
 ///////////////////////////////////////////////////////////////////////////////////////////////////
