commit a09ada4c18897d7406fe9136afcc898a7a7249d5
Author: Leszek Koltunski <leszek@distoretedandroid.org>
Date:   Fri Feb 10 13:26:20 2017 +0000

    Preparation for change of the render API.

diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
index 016d8ea..071ff5e 100644
--- a/src/main/java/org/distorted/library/Distorted.java
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -68,7 +68,7 @@ public class Distorted
    */
   public static final int CLONE_POSTPROCESS= 0x10;
   /**
-   * When creating an instance of a DistortedTree from another instance, clone the children Nodes.
+   * When creating an instance of a DistortedNode from another instance, clone the children Nodes.
    * <p>
    * This is mainly useful for creating many similar sub-trees and rendering then at different places
    * on the screen with (optionally) different Effects.
@@ -121,7 +121,7 @@ public class Distorted
     DistortedEffects.createProgram(resources);
     EffectQueuePostprocess.createProgram(resources);
 
-    DistortedTree.reset();
+    DistortedNode.reset();
     EffectMessageSender.startSending();
 
     mInitialized = true;
@@ -135,7 +135,7 @@ public class Distorted
   public static void onDestroy()
     {
     DistortedSurface.onDestroy();
-    DistortedTree.onDestroy();
+    DistortedNode.onDestroy();
     EffectQueue.onDestroy();
     DistortedEffects.onDestroy();
     EffectMessageSender.stopSending();
diff --git a/src/main/java/org/distorted/library/DistortedNode.java b/src/main/java/org/distorted/library/DistortedNode.java
new file mode 100644
index 0000000..4afc4c4
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedNode.java
@@ -0,0 +1,497 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.GLES30;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Class which represents a Node in a Tree of (InputSurface,Mesh,Effects) triplets.
+ *  
+ * Having organized such sets into a Tree, we can then render any Node to any OutputSurface.
+ * That recursively renders the set held in the Node and all its children.
+ */
+public class DistortedNode
+  {
+  private static HashMap<ArrayList<Long>,NodeData> mMapNodeID = new HashMap<>();
+  private static long mNextNodeID =0;
+
+  private MeshObject mMesh;
+  private DistortedEffects mEffects;
+  private DistortedInputSurface mSurface;
+  private NodeData mData;
+
+  private DistortedNode mParent;
+  private ArrayList<DistortedNode> mChildren;
+  private int[] mNumChildren;  // ==mChildren.length(), but we only create mChildren if the first one gets added
+
+  private class NodeData
+    {
+    long ID;
+    int numPointingNodes;
+    int numRendered;
+    DistortedFramebuffer mFBO;
+
+    NodeData(long id)
+      {
+      ID              = id;
+      numPointingNodes= 1;
+      numRendered     = 0;
+      mFBO            = null;
+      }
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized void onDestroy()
+    {
+    mNextNodeID = 0;
+    mMapNodeID.clear();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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( mSurface.getID() );
+    DistortedNode node;
+   
+    for(int i=0; i<mNumChildren[0]; i++)
+      {
+      node = mChildren.get(i);
+      ret.add(node.mData.ID);
+      }
+   
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void adjustNodeData(ArrayList<Long> oldList, ArrayList<Long> newList)
+    {
+    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.mFBO ==null )
+          mData.mFBO = new DistortedFramebuffer(mSurface.getWidth(), mSurface.getHeight());
+        }
+      else
+        {
+        if( mData.mFBO !=null )
+          {
+          mData.mFBO.markForDeletion();
+          mData.mFBO = null;
+          }
+        else
+          {
+          android.util.Log.e("DistortedNode", "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.mFBO != null ) 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);
+      }
+    }
+*/
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void drawRecursive(long currTime, DistortedOutputSurface surface)
+    {
+    mSurface.create();
+    float halfX = mSurface.getWidth()/2.0f;
+    float halfY = mSurface.getHeight()/2.0f;
+
+    if( mNumChildren[0]<=0 )
+      {
+      mSurface.setAsInput();
+      }
+    else
+      {
+      mData.mFBO.create();
+
+      if( mData.numRendered==0 )
+        {
+        mData.mFBO.setAsOutput();
+
+        GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+        GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
+
+        if( mSurface.setAsInput() )
+          DistortedEffects.drawNoEffectsPriv(halfX, halfY, mMesh, mData.mFBO );
+
+        for(int i=0; i<mNumChildren[0]; i++)
+          {
+          mChildren.get(i).drawRecursive(currTime, mData.mFBO);
+          }
+        }
+
+      mData.numRendered++;
+      mData.numRendered %= mData.numPointingNodes;
+      mData.mFBO.setAsInput();
+      }
+
+    mEffects.drawPriv(halfX, halfY, mMesh, surface, currTime);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructs new Node.
+ *     
+ * @param surface InputSurface to put into the new Node.
+ * @param effects DistortedEffects to put into the new Node.
+ * @param mesh MeshObject to put into the new Node.
+ */
+  public DistortedNode(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
+    {
+    mSurface       = surface;
+    mEffects       = effects;
+    mMesh          = mesh;
+    mParent        = null;
+    mChildren      = null;
+    mNumChildren   = new int[1];
+    mNumChildren[0]= 0;
+   
+    ArrayList<Long> list = new ArrayList<>();
+    list.add(mSurface.getID());
+
+    mData = mMapNodeID.get(list);
+   
+    if( mData!=null )
+      {
+      mData.numPointingNodes++;
+      }
+    else
+      {
+      mData = new NodeData(++mNextNodeID);   
+      mMapNodeID.put(list, mData);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Copy-constructs new Node from another Node.
+ *     
+ * @param node The DistortedNode to copy data from.
+ * @param flags bit field composed of a subset of the following:
+ *        {@link Distorted#CLONE_SURFACE},  {@link Distorted#CLONE_MATRIX}, {@link Distorted#CLONE_VERTEX},
+ *        {@link Distorted#CLONE_FRAGMENT} and {@link Distorted#CLONE_CHILDREN}.
+ *        For example flags = CLONE_SURFACE | CLONE_CHILDREN.
+ */
+  public DistortedNode(DistortedNode node, int flags)
+    {
+    mParent = null;
+    mEffects= new DistortedEffects(node.mEffects,flags);
+    mMesh = node.mMesh;
+
+    if( (flags & Distorted.CLONE_SURFACE) != 0 )
+      {
+      mSurface = node.mSurface;
+      }
+    else
+      {
+      int w = node.mSurface.getWidth();
+      int h = node.mSurface.getHeight();
+
+      if( node.mSurface instanceof DistortedTexture )
+        {
+        mSurface = new DistortedTexture(w,h);
+        }
+      else if( node.mSurface instanceof DistortedFramebuffer )
+        {
+        boolean hasDepth = ((DistortedFramebuffer) node.mSurface).hasDepth();
+        mSurface = new DistortedFramebuffer(w,h,hasDepth);
+        }
+      }
+    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(DistortedNode 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 surface InputSurface to initialize our child Node with.
+ * @param effects DistortedEffects to initialize our child Node with.
+ * @param mesh MeshObject to initialize our child Node with.
+ * @return the newly constructed child Node, or null if we couldn't allocate resources.
+ */
+  public synchronized DistortedNode attach(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
+    {
+    ArrayList<Long> prev = generateIDList(); 
+      
+    if( mChildren==null ) mChildren = new ArrayList<>(2);
+    DistortedNode node = new DistortedNode(surface,effects,mesh);
+    node.mParent = this;
+    mChildren.add(node);
+    mNumChildren[0]++;
+   
+    RecomputeNodeID(prev);
+
+    return node;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the first occurrence of a specified child from the list of children of our Node.
+ * 
+ * @param node The Node to remove.
+ * @return <code>true</code> if the child was successfully removed.
+ */
+  public synchronized boolean detach(DistortedNode node)
+    {
+    if( mNumChildren[0]>0 )
+      {
+      ArrayList<Long> prev = generateIDList();  
+         
+      if( mChildren.remove(node) )
+        {
+        node.mParent = null;  
+        mNumChildren[0]--;
+     
+        RecomputeNodeID(prev);
+     
+        return true;
+        }
+      }
+   
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the first occurrence of a specified child from the list of children of our Node.
+ * <p>
+ * A bit questionable method as there can be many different Nodes attached as children, some
+ * of them having the same Effects but - for instance - different Mesh. Use with care.
+ *
+ * @param effects DistortedEffects to remove.
+ * @return <code>true</code> if the child was successfully removed.
+ */
+  public synchronized boolean detach(DistortedEffects effects)
+    {
+    long id = effects.getID();
+    DistortedNode node;
+
+    for(int i=0; i<mNumChildren[0]; i++)
+      {
+      node = mChildren.get(i);
+
+      if( node.mEffects.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);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the DistortedEffects object that's in the Node.
+ * 
+ * @return The DistortedEffects contained in the Node.
+ */
+  public DistortedEffects getEffects()
+    {
+    return mEffects;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the DistortedInputSurface object that's in the Node.
+ *
+ * @return The DistortedInputSurface contained in the Node.
+ */
+  public DistortedInputSurface getSurface()
+    {
+    return mSurface;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the DistortedFramebuffer object that's in the Node.
+ *
+ * @return The DistortedFramebuffer contained in the Node.
+ */
+  public DistortedFramebuffer getFramebuffer()
+    {
+    return mData.mFBO;
+    }
+
+  }
diff --git a/src/main/java/org/distorted/library/DistortedOutputSurface.java b/src/main/java/org/distorted/library/DistortedOutputSurface.java
index 08d4143..6ddfd34 100644
--- a/src/main/java/org/distorted/library/DistortedOutputSurface.java
+++ b/src/main/java/org/distorted/library/DistortedOutputSurface.java
@@ -20,11 +20,15 @@
 package org.distorted.library;
 
 import android.opengl.Matrix;
+import java.util.ArrayList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 abstract class DistortedOutputSurface extends DistortedSurface
 {
+  private ArrayList<DistortedNode> mChildren;
+  private int mNumChildren;   // ==mChildren.length(), but we only create mChildren if the first one gets added
+
   private float mX, mY, mFOV;
   int mWidth,mHeight,mDepth;
   float mDistance;
@@ -114,10 +118,10 @@ abstract class DistortedOutputSurface extends DistortedSurface
  * <p>
  * Must be called from a thread holding OpenGL Context.
  *
- * @param dt DistortedTree to render.
+ * @param dt DistortedNode to render.
  * @param time Current time, in milliseconds. This will be passed to all the Effects stored in the Tree.
  */
-  public void renderTo(DistortedTree dt, long time)
+  public void renderTo(DistortedNode dt, long time)
     {
     DistortedSurface.deleteAllMarked();
     create();
@@ -166,4 +170,64 @@ abstract class DistortedOutputSurface extends DistortedSurface
       if( mColorH[0]>0 ) markForDeletion();
       }
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new child to the last position in the list of our Surface's children.
+ *
+ * @param node The new Node to add.
+ */
+  public synchronized void attach(DistortedNode node)
+    {
+    if( mChildren==null ) mChildren = new ArrayList<>(2);
+    mChildren.add(node);
+    mNumChildren++;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new child to the last position in the list of our Surface's children.
+ *
+ * @param surface InputSurface to initialize our child Node with.
+ * @param effects DistortedEffects to initialize our child Node with.
+ * @param mesh MeshObject to initialize our child Node with.
+ * @return the newly constructed child Node, or null if we couldn't allocate resources.
+ */
+  public synchronized DistortedNode attach(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
+    {
+    if( mChildren==null ) mChildren = new ArrayList<>(2);
+    DistortedNode node = new DistortedNode(surface,effects,mesh);
+    mChildren.add(node);
+    mNumChildren++;
+
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the first occurrence of a specified child from the list of children of our Surface.
+ *
+ * @param node The Node to remove.
+ * @return <code>true</code> if the child was successfully removed.
+ */
+  public synchronized boolean detach(DistortedNode node)
+    {
+    if( mChildren.remove(node) )
+      {
+      mNumChildren--;
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all children Nodes.
+ */
+  public synchronized void detachAll()
+    {
+    mNumChildren = 0;
+    mChildren.clear();
+    }
 }
diff --git a/src/main/java/org/distorted/library/DistortedTexture.java b/src/main/java/org/distorted/library/DistortedTexture.java
index 6d5daa1..ffc3230 100644
--- a/src/main/java/org/distorted/library/DistortedTexture.java
+++ b/src/main/java/org/distorted/library/DistortedTexture.java
@@ -41,7 +41,7 @@ public class DistortedTexture extends DistortedSurface implements DistortedInput
 // 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, (DistortedTree does so!) things get inverted as textures
+// then take the FBO and render to screen, (DistortedNode 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
deleted file mode 100644
index 995fa69..0000000
--- a/src/main/java/org/distorted/library/DistortedTree.java
+++ /dev/null
@@ -1,497 +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.GLES30;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Class which represents a Node in a Tree of (Texture,Mesh,Effects) objects.
- *  
- * 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 MeshObject mMesh;
-  private DistortedEffects mEffects;
-  private DistortedInputSurface mSurface;
-  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;
-    int numRendered;
-    DistortedFramebuffer mFBO;
-
-    NodeData(long id)
-      {
-      ID              = id;
-      numPointingNodes= 1;
-      numRendered     = 0;
-      mFBO            = null;
-      }
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static synchronized void onDestroy()
-    {
-    mNextNodeID = 0;
-    mMapNodeID.clear();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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( mSurface.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.mFBO ==null )
-          mData.mFBO = new DistortedFramebuffer(mSurface.getWidth(), mSurface.getHeight());
-        }
-      else
-        {
-        if( mData.mFBO !=null )
-          {
-          mData.mFBO.markForDeletion();
-          mData.mFBO = 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.mFBO != null ) 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);
-      }
-    }
-*/
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void drawRecursive(long currTime, DistortedOutputSurface surface)
-    {
-    mSurface.create();
-    float halfX = mSurface.getWidth()/2.0f;
-    float halfY = mSurface.getHeight()/2.0f;
-
-    if( mNumChildren[0]<=0 )
-      {
-      mSurface.setAsInput();
-      }
-    else
-      {
-      mData.mFBO.create();
-
-      if( mData.numRendered==0 )
-        {
-        mData.mFBO.setAsOutput();
-
-        GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-        GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
-
-        if( mSurface.setAsInput() )
-          DistortedEffects.drawNoEffectsPriv(halfX, halfY, mMesh, mData.mFBO );
-
-        synchronized(this)
-          {
-          for(int i=0; i<mNumChildren[0]; i++)
-            {
-            mChildren.get(i).drawRecursive(currTime, mData.mFBO);
-            }
-          }
-        }
-
-      mData.numRendered++;
-      mData.numRendered %= mData.numPointingNodes;
-      mData.mFBO.setAsInput();
-      }
-
-    mEffects.drawPriv(halfX, halfY, mMesh, surface, currTime);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Constructs new Node of the Tree.
- *     
- * @param surface InputSurface to put into the new Node.
- * @param effects DistortedEffects to put into the new Node.
- * @param mesh MeshObject to put into the new Node.
- */
-  public DistortedTree(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
-    {
-    mSurface       = surface;
-    mEffects       = effects;
-    mMesh          = mesh;
-    mParent        = null;
-    mChildren      = null;
-    mNumChildren   = new int[1];
-    mNumChildren[0]= 0;
-   
-    ArrayList<Long> list = new ArrayList<>();
-    list.add(mSurface.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_SURFACE},  {@link Distorted#CLONE_MATRIX}, {@link Distorted#CLONE_VERTEX},
- *        {@link Distorted#CLONE_FRAGMENT} and {@link Distorted#CLONE_CHILDREN}.
- *        For example flags = CLONE_SURFACE | CLONE_CHILDREN.
- */
-  public DistortedTree(DistortedTree node, int flags)
-    {
-    mParent = null;
-    mEffects= new DistortedEffects(node.mEffects,flags);
-    mMesh = node.mMesh;
-
-    if( (flags & Distorted.CLONE_SURFACE) != 0 )
-      {
-      mSurface = node.mSurface;
-      }
-    else
-      {
-      int w = node.mSurface.getWidth();
-      int h = node.mSurface.getHeight();
-
-      if( node.mSurface instanceof DistortedTexture )
-        {
-        mSurface = new DistortedTexture(w,h);
-        }
-      else if( node.mSurface instanceof DistortedFramebuffer )
-        {
-        boolean hasDepth = ((DistortedFramebuffer) node.mSurface).hasDepth();
-        mSurface = new DistortedFramebuffer(w,h,hasDepth);
-        }
-      }
-    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 surface InputSurface to initialize our child Node with.
- * @param effects DistortedEffects to initialize our child Node with.
- * @param mesh MeshObject to initialize our child Node with.
- * @return the newly constructed child Node, or null if we couldn't allocate resources.
- */
-  public synchronized DistortedTree attach(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
-    {
-    ArrayList<Long> prev = generateIDList(); 
-      
-    if( mChildren==null ) mChildren = new ArrayList<>(2);
-    DistortedTree node = new DistortedTree(surface,effects,mesh);
-    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 effects DistortedEffects to remove.
- * @return <code>true</code> if the child was successfully removed.
- */
-  public synchronized boolean detach(DistortedEffects effects)
-    {
-    long id = effects.getID();
-    DistortedTree node;
-   
-    for(int i=0; i<mNumChildren[0]; i++)
-      {
-      node = mChildren.get(i);
-     
-      if( node.mEffects.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);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the DistortedEffects object that's in the Node.
- * 
- * @return The DistortedEffects contained in the Node.
- */
-  public DistortedEffects getEffects()
-    {
-    return mEffects;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the DistortedInputSurface object that's in the Node.
- *
- * @return The DistortedInputSurface contained in the Node.
- */
-  public DistortedInputSurface getSurface()
-    {
-    return mSurface;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the DistortedFramebuffer object that's in the Node.
- *
- * @return The DistortedFramebuffer contained in the Node.
- */
-  public DistortedFramebuffer getFramebuffer()
-    {
-    return mData.mFBO;
-    }
-
-  }
