commit c90aca24b31e6e9f0539bb77c0f1c7107ad72bbe
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Wed Feb 26 15:52:02 2020 +0000

    Move the 'pre-multiply mesh before applying any effects' thing from [(Xsize of texture, Ysize of texture) x Mesh's zFactor] to Effects.setStretch(sx,sy,sz)

diff --git a/src/main/java/org/distorted/library/effect/MatrixEffect.java b/src/main/java/org/distorted/library/effect/MatrixEffect.java
index 4d76d81..125f532 100644
--- a/src/main/java/org/distorted/library/effect/MatrixEffect.java
+++ b/src/main/java/org/distorted/library/effect/MatrixEffect.java
@@ -19,8 +19,6 @@
 
 package org.distorted.library.effect;
 
-import android.opengl.Matrix;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Abstract class that represents an Effect that works by modifying the ModelView matrix.
@@ -32,8 +30,6 @@ public abstract class MatrixEffect extends Effect
  */
   public static final int NUM_UNIFORMS = 7;
 
-  private float[] mMatrix,mTmpArray;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Only for use by the library itself.
@@ -42,104 +38,6 @@ public abstract class MatrixEffect extends Effect
  */
   public abstract void apply(float[] matrix, float[] uniforms, int index);
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Only for use by the library itself.
- * <p>
- * The three values buffer[index], buffer[index+1], buffer[index+2] are assumed to contain the
- * x,y,z coordinates of a point. We apply this Matrix effect to this point and overwrite those
- * three values.
- *
- * This is on purpose a static evaluation - if this Effect contains any Dynamics, they will be
- * evaluated at 0.
- *
- * @y.exclude
- */
- public void applyToPoint(float[] buffer, int index)
-   {
-   Matrix.setIdentityM(mMatrix,0);
-   compute(mTmpArray,0,0,0);
-   apply(mMatrix, mTmpArray, 0);
-
-   float x = buffer[index  ];
-   float y = buffer[index+1];
-   float z = buffer[index+2];
-
-   buffer[index  ] = mMatrix[0]*x + mMatrix[4]*y + mMatrix[ 8]*z + mMatrix[12];
-   buffer[index+1] = mMatrix[1]*x + mMatrix[5]*y + mMatrix[ 9]*z + mMatrix[13];
-   buffer[index+2] = mMatrix[2]*x + mMatrix[6]*y + mMatrix[10]*z + mMatrix[14];
-   }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Only for use by the library itself.
- * <p>
- * The three values buffer[index], buffer[index+1], buffer[index+2] are assumed to contain the
- * x,y,z coordinates of a vector. We apply this Matrix effect to this vector and overwrite those
- * three values.
- *
- * This is on purpose a static evaluation - if this Effect contains any Dynamics, they will be
- * evaluated at 0.
- *
- * @y.exclude
- */
- public void applyToVector(float[] buffer, int index)
-   {
-   Matrix.setIdentityM(mMatrix,0);
-   compute(mTmpArray,0,0,0);
-   apply(mMatrix, mTmpArray, 0);
-
-   float x = buffer[index  ];
-   float y = buffer[index+1];
-   float z = buffer[index+2];
-
-   buffer[index  ] = mMatrix[0]*x + mMatrix[4]*y + mMatrix[ 8]*z;
-   buffer[index+1] = mMatrix[1]*x + mMatrix[5]*y + mMatrix[ 9]*z;
-   buffer[index+2] = mMatrix[2]*x + mMatrix[6]*y + mMatrix[10]*z;
-   }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Only for use by the library itself.
- * <p>
- * The 9 floats starting at buffer[index] are assumed to contain info about a vertex arranged in
- * MeshBase way, i.e.  coord point (x,y,z) ; normal vector (x,y,z) ; inflate vector (x,y,z)
- *
- * @y.exclude
- */
- public void applyToVertex(float[] buffer, int index)
-   {
-   float x,y,z;
-
-   Matrix.setIdentityM(mMatrix,0);
-   compute(mTmpArray,0,0,0);
-   apply(mMatrix, mTmpArray, 0);
-
-   x = buffer[index  ];
-   y = buffer[index+1];
-   z = buffer[index+2];
-
-   buffer[index  ] = mMatrix[0]*x + mMatrix[4]*y + mMatrix[ 8]*z + mMatrix[12];
-   buffer[index+1] = mMatrix[1]*x + mMatrix[5]*y + mMatrix[ 9]*z + mMatrix[13];
-   buffer[index+2] = mMatrix[2]*x + mMatrix[6]*y + mMatrix[10]*z + mMatrix[14];
-
-   x = buffer[index+3];
-   y = buffer[index+4];
-   z = buffer[index+5];
-
-   buffer[index+3] = mMatrix[0]*x + mMatrix[4]*y + mMatrix[ 8]*z;
-   buffer[index+4] = mMatrix[1]*x + mMatrix[5]*y + mMatrix[ 9]*z;
-   buffer[index+5] = mMatrix[2]*x + mMatrix[6]*y + mMatrix[10]*z;
-
-   x = buffer[index+6];
-   y = buffer[index+7];
-   z = buffer[index+8];
-
-   buffer[index+6] = mMatrix[0]*x + mMatrix[4]*y + mMatrix[ 8]*z;
-   buffer[index+7] = mMatrix[1]*x + mMatrix[5]*y + mMatrix[ 9]*z;
-   buffer[index+8] = mMatrix[2]*x + mMatrix[6]*y + mMatrix[10]*z;
-   }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // empty function for completeness
 
@@ -153,8 +51,5 @@ public abstract class MatrixEffect extends Effect
   MatrixEffect(EffectName name)
     {
     super(name);
-
-    mMatrix  = new float[16];
-    mTmpArray= new float[4];
     }
   }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.java b/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.java
index 2443a73..f3931a1 100644
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.java
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.java
@@ -76,7 +76,7 @@ class EffectQueueMatrix extends EffectQueue
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// return a float which describes how much larger an object must be so that it appears to be (dialog_about)
+// return a float which describes how much larger an object must be so that it appears to be (about)
 // 'marginInPixels' pixels larger in each direction. Used in Postprocessing.
 
   float magnify(float[] projection, int width, int height, float mipmap, float halfX, float halfY, float halfZ, float marginInPixels)
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
index ca71666..7374e32 100644
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
@@ -27,6 +27,7 @@ import org.distorted.library.R;
 import org.distorted.library.effect.EffectType;
 import org.distorted.library.effect.PostprocessEffect;
 import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedLibrary;
 import org.distorted.library.main.DistortedFramebuffer;
 import org.distorted.library.main.DistortedNode;
@@ -164,13 +165,14 @@ public class EffectQueuePostprocess extends EffectQueue
     if( input.setAsInput() )
       {
       MeshBase mesh = node.getMesh();
+      DistortedEffects effects = node.getEffects();
 
-      float halfW = input.getWidth() / 2.0f;
-      float halfH = input.getHeight()/ 2.0f;
-      float halfZ = halfW*mesh.getZFactor();
+      float halfW = effects.getStartchX() / 2.0f;
+      float halfH = effects.getStartchY() / 2.0f;
+      float halfZ = effects.getStartchZ() / 2.0f;
 
-      int width = buffer.getWidth();
-      int height = buffer.getHeight();
+      int width   = buffer.getWidth();
+      int height  = buffer.getHeight();
 
       InternalRenderState.setUpStencilMark(mA!=0.0f);
       InternalRenderState.disableBlending();
@@ -181,7 +183,7 @@ public class EffectQueuePostprocess extends EffectQueue
 
       mesh.bindVertexAttribs(mPreProgram);
 
-      EffectQueue[] queues = node.getEffects().getQueues();
+      EffectQueue[] queues = effects.getQueues();
       EffectQueueMatrix matrix = (EffectQueueMatrix)queues[0];
       EffectQueueVertex vertex = (EffectQueueVertex)queues[1];
 
diff --git a/src/main/java/org/distorted/library/main/DistortedEffects.java b/src/main/java/org/distorted/library/main/DistortedEffects.java
index dbc398c..0668edd 100644
--- a/src/main/java/org/distorted/library/main/DistortedEffects.java
+++ b/src/main/java/org/distorted/library/main/DistortedEffects.java
@@ -36,6 +36,8 @@ public class DistortedEffects
   private long mID;
   private EffectQueue[] mQueues;
 
+  private int[] mStretchX, mStretchY, mStretchZ;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * @y.exclude
@@ -58,8 +60,35 @@ public class DistortedEffects
 /**
  * Create empty effect queue.
  */
-  public DistortedEffects()
+  public DistortedEffects(int stretchX, int stretchY, int stretchZ)
+    {
+    mStretchX = new int[1];
+    mStretchY = new int[1];
+    mStretchZ = new int[1];
+
+    mStretchX[0] = stretchX;
+    mStretchY[0] = stretchY;
+    mStretchZ[0] = stretchZ;
+
+    mID = ++mNextID;
+    mQueues = new EffectQueue[EffectType.LENGTH];
+    EffectQueue.allocateQueues(mQueues,null,0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Temporary constructor.
+ */
+ public DistortedEffects(int dummy)
     {
+    mStretchX = new int[1];
+    mStretchY = new int[1];
+    mStretchZ = new int[1];
+
+    mStretchX[0] = 1;
+    mStretchY[0] = 1;
+    mStretchZ[0] = 1;
+
     mID = ++mNextID;
     mQueues = new EffectQueue[EffectType.LENGTH];
     EffectQueue.allocateQueues(mQueues,null,0);
@@ -77,11 +106,57 @@ public class DistortedEffects
  */
   public DistortedEffects(DistortedEffects dc, int flags)
     {
+    mStretchX = dc.mStretchX;
+    mStretchY = dc.mStretchY;
+    mStretchZ = dc.mStretchZ;
+
     mID = ++mNextID;
     mQueues = new EffectQueue[EffectType.LENGTH];
     EffectQueue.allocateQueues(mQueues,dc.getQueues(),flags);
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the stretch parameters. Coordinates of all vertices of any Mesh rendered with those Effects
+ * will be first pre-multiplied by those.
+ */
+  public void setStretch(int sx, int sy, int sz)
+    {
+    mStretchX[0] = sx;
+    mStretchY[0] = sy;
+    mStretchZ[0] = sz;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * X coordinates of all vertices of any Mesh rendered with those Effects will be first pre-multiplied
+ * by this parameter.
+ */
+  public int getStartchX()
+    {
+    return mStretchX[0];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Y coordinates of all vertices of any Mesh rendered with those Effects will be first pre-multiplied
+ * by this parameter.
+ */
+  public int getStartchY()
+    {
+    return mStretchY[0];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Z coordinates of all vertices of any Mesh rendered with those Effects will be first pre-multiplied
+ * by this parameter.
+ */
+  public int getStartchZ()
+    {
+    return mStretchZ[0];
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Returns unique ID of this instance.
diff --git a/src/main/java/org/distorted/library/main/DistortedLibrary.java b/src/main/java/org/distorted/library/main/DistortedLibrary.java
index 99ce6e3..8319b1c 100644
--- a/src/main/java/org/distorted/library/main/DistortedLibrary.java
+++ b/src/main/java/org/distorted/library/main/DistortedLibrary.java
@@ -428,11 +428,15 @@ public class DistortedLibrary
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static void drawPrivOIT(EffectQueue[] queues, float halfW, float halfH, MeshBase mesh, InternalOutputSurface surface, long currTime)
+  static void drawPrivOIT(DistortedEffects effects, MeshBase mesh, InternalOutputSurface surface, long currTime)
     {
-    float halfZ = halfW*mesh.getZFactor();
+    float halfX = effects.getStartchX() / 2.0f;
+    float halfY = effects.getStartchY() / 2.0f;
+    float halfZ = effects.getStartchZ() / 2.0f;
 
-    EffectQueue.compute(queues, currTime, halfW, halfH, halfZ );
+    EffectQueue[] queues = effects.getQueues();
+
+    EffectQueue.compute(queues, currTime, halfX, halfY, halfZ );
     GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
 
     DistortedLibrary.mMainOITProgram.useProgram();
@@ -448,24 +452,28 @@ public class DistortedLibrary
     float mipmap      = surface.mMipmap;
     float[] projection= surface.mProjectionMatrix;
 
-    EffectQueue.send(queues, width, height, distance, mipmap, projection, inflate, halfW, halfH, halfZ, 1 );
+    EffectQueue.send(queues, width, height, distance, mipmap, projection, inflate, halfX, halfY, halfZ, 1 );
     GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
 
     if( mesh.getShowNormals() )
       {
       DistortedLibrary.mMainProgram.useProgram();
-      EffectQueue.send(queues, width, height, distance, mipmap, projection, inflate, halfW, halfH, halfZ, 0 );
+      EffectQueue.send(queues, width, height, distance, mipmap, projection, inflate, halfX, halfY, halfZ, 0 );
       displayNormals(queues,mesh);
       }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static void drawPriv(EffectQueue[] queues, float halfW, float halfH, MeshBase mesh, InternalOutputSurface surface, long currTime)
+  static void drawPriv(DistortedEffects effects, MeshBase mesh, InternalOutputSurface surface, long currTime)
     {
-    float halfZ = halfW*mesh.getZFactor();
+    float halfX = effects.getStartchX() / 2.0f;
+    float halfY = effects.getStartchY() / 2.0f;
+    float halfZ = effects.getStartchZ() / 2.0f;
+
+    EffectQueue[] queues = effects.getQueues();
 
-    EffectQueue.compute(queues, currTime, halfW, halfH, halfZ );
+    EffectQueue.compute(queues, currTime, halfX, halfY, halfZ );
     GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
 
     DistortedLibrary.mMainProgram.useProgram();
@@ -479,7 +487,7 @@ public class DistortedLibrary
     float mipmap      = surface.mMipmap;
     float[] projection= surface.mProjectionMatrix;
 
-    EffectQueue.send(queues, width, height, distance, mipmap, projection, inflate, halfW, halfH, halfZ, 0 );
+    EffectQueue.send(queues, width, height, distance, mipmap, projection, inflate, halfX, halfY, halfZ, 0 );
     GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
 
     if( mesh.getShowNormals() ) displayNormals(queues,mesh);
diff --git a/src/main/java/org/distorted/library/main/DistortedNode.java b/src/main/java/org/distorted/library/main/DistortedNode.java
index da20303..d97d65e 100644
--- a/src/main/java/org/distorted/library/main/DistortedNode.java
+++ b/src/main/java/org/distorted/library/main/DistortedNode.java
@@ -157,7 +157,7 @@ public class DistortedNode implements InternalChildrenList.Parent
       {
       mState.apply();
       GLES31.glDisable(GLES31.GL_BLEND);
-      DistortedLibrary.drawPriv(mEffects.getQueues(), mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
+      DistortedLibrary.drawPriv(mEffects, mMesh, surface, currTime);
       GLES31.glEnable(GLES31.GL_BLEND);
       return 1;
       }
@@ -175,7 +175,7 @@ public class DistortedNode implements InternalChildrenList.Parent
     if( input.setAsInput() )
       {
       mState.apply();
-      DistortedLibrary.drawPrivOIT(mEffects.getQueues(), mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
+      DistortedLibrary.drawPrivOIT(mEffects, mMesh, surface, currTime);
       return 1;
       }
 
@@ -192,7 +192,7 @@ public class DistortedNode implements InternalChildrenList.Parent
     if( input.setAsInput() )
       {
       mState.apply();
-      DistortedLibrary.drawPriv(mEffects.getQueues(), mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
+      DistortedLibrary.drawPriv(mEffects, mMesh, surface, currTime);
       return 1;
       }
 
@@ -240,8 +240,8 @@ public class DistortedNode implements InternalChildrenList.Parent
 
   private DistortedFramebuffer allocateNewFBO()
     {
-    int width  = mFboW <= 0 ? mSurface.getWidth()  : mFboW;
-    int height = mFboH <= 0 ? mSurface.getHeight() : mFboH;
+    int width  = mFboW <= 0 ? mEffects.getStartchX() : mFboW;
+    int height = mFboH <= 0 ? mEffects.getStartchY() : mFboH;
     return new DistortedFramebuffer(1,mFboDepthStencil, InternalSurface.TYPE_TREE, width, height);
     }
 
@@ -273,7 +273,7 @@ public class DistortedNode implements InternalChildrenList.Parent
     mRenderWayOIT  = false;
 
     mFboW            = 0;  // i.e. take this from
-    mFboH            = 0;  // mSurface's dimensions
+    mFboH            = 0;  // mEffects's stretch{X,Y}
     mFboDepthStencil = DistortedFramebuffer.DEPTH_NO_STENCIL;
 
     mData = InternalNodeData.returnData(generateIDList());
@@ -307,15 +307,14 @@ public class DistortedNode implements InternalChildrenList.Parent
       }
     else
       {
-      int w = node.mSurface.getWidth();
-      int h = node.mSurface.getHeight();
-
       if( node.mSurface instanceof DistortedTexture )
         {
-        mSurface = new DistortedTexture(w,h, InternalSurface.TYPE_TREE);
+        mSurface = new DistortedTexture(InternalSurface.TYPE_TREE);
         }
       else if( node.mSurface instanceof DistortedFramebuffer )
         {
+        int w = node.mSurface.getWidth();
+        int h = node.mSurface.getHeight();
         int depthStencil = DistortedFramebuffer.NO_DEPTH_NO_STENCIL;
 
         if( ((DistortedFramebuffer) node.mSurface).hasDepth() )
diff --git a/src/main/java/org/distorted/library/main/DistortedScreen.java b/src/main/java/org/distorted/library/main/DistortedScreen.java
index 196cabe..4f25ecd 100644
--- a/src/main/java/org/distorted/library/main/DistortedScreen.java
+++ b/src/main/java/org/distorted/library/main/DistortedScreen.java
@@ -133,7 +133,7 @@ public class DistortedScreen extends DistortedFramebuffer
 
     if( mShowFPS && fpsTexture.setAsInput())
       {
-      DistortedLibrary.drawPriv(fpsEffects.getQueues(), FPS_W / 2.0f, FPS_H / 2.0f, fpsMesh, this, time);
+      DistortedLibrary.drawPriv(fpsEffects, fpsMesh, this, time);
       }
 
     if( ++mCurRenderedFBO>= DistortedLibrary.FBO_QUEUE_SIZE )
@@ -162,10 +162,10 @@ public class DistortedScreen extends DistortedFramebuffer
       fpsString = "";
       fpsBitmap = Bitmap.createBitmap(FPS_W, FPS_H, Bitmap.Config.ARGB_8888);
       fpsMesh = new MeshQuad();
-      fpsTexture = new DistortedTexture(FPS_W, FPS_H);
+      fpsTexture = new DistortedTexture();
       fpsTexture.setTexture(fpsBitmap);
       fpsCanvas = new Canvas(fpsBitmap);
-      fpsEffects = new DistortedEffects();
+      fpsEffects = new DistortedEffects(FPS_W,FPS_H,0);
       fpsEffects.apply(mMoveEffect);
 
       mPaint = new Paint();
diff --git a/src/main/java/org/distorted/library/main/DistortedTexture.java b/src/main/java/org/distorted/library/main/DistortedTexture.java
index 8eec93d..62637ff 100644
--- a/src/main/java/org/distorted/library/main/DistortedTexture.java
+++ b/src/main/java/org/distorted/library/main/DistortedTexture.java
@@ -113,9 +113,9 @@ public class DistortedTexture extends InternalSurface
 // inside a Tree of DistortedNodes (TREE)
 // SYSTEM surfaces do not get removed in onDestroy().
 
-  public DistortedTexture(int width, int height, int type)
+  public DistortedTexture(int type)
     {
-    super(width,height,NOT_CREATED_YET,1,1,type);
+    super(0,0,NOT_CREATED_YET,1,1,type);
     mBmp= null;
     }
 
@@ -123,11 +123,11 @@ public class DistortedTexture extends InternalSurface
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Create empty texture of given dimensions.
+ * Create an empty texture.
  */
-  public DistortedTexture(int width, int height)
+  public DistortedTexture()
     {
-    this(width,height,TYPE_USER);
+    this(TYPE_USER);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -157,7 +157,10 @@ public class DistortedTexture extends InternalSurface
  */
   public void setTexture(Bitmap bmp)
     {
-    mBmp= bmp;
+    mBmp   = bmp;
+    mWidth = bmp.getWidth();
+    mHeight= bmp.getHeight();
+
     markForCreation();
     }
 
@@ -173,9 +176,12 @@ public class DistortedTexture extends InternalSurface
     paint.setColor(argb);
     paint.setStyle(Paint.Style.FILL);
 
-    mBmp = Bitmap.createBitmap(1,1, Bitmap.Config.ARGB_8888);
+    mWidth = 1;
+    mHeight= 1;
+
+    mBmp = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888);
     Canvas canvas = new Canvas(mBmp);
-    canvas.drawRect(0,0,1,1,paint);
+    canvas.drawRect(0,0,mWidth,mHeight,paint);
 
     markForCreation();
     }
diff --git a/src/main/java/org/distorted/library/main/InternalSurface.java b/src/main/java/org/distorted/library/main/InternalSurface.java
index 956e1b8..54efbf6 100644
--- a/src/main/java/org/distorted/library/main/InternalSurface.java
+++ b/src/main/java/org/distorted/library/main/InternalSurface.java
@@ -100,11 +100,13 @@ public abstract class InternalSurface extends InternalObject
  *
  * @return depth of the Object, in pixels.
  */
+
+/*
   public int getDepth(MeshBase mesh)
     {
     return mesh==null ? 0 : (int)(mWidth*mesh.getZFactor() );
     }
-
+*/
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Bind the underlying rectangle of pixels as a OpenGL Texture.
diff --git a/src/main/java/org/distorted/library/mesh/MeshBase.java b/src/main/java/org/distorted/library/mesh/MeshBase.java
index b2930af..f7b07e5 100644
--- a/src/main/java/org/distorted/library/mesh/MeshBase.java
+++ b/src/main/java/org/distorted/library/mesh/MeshBase.java
@@ -20,6 +20,7 @@
 package org.distorted.library.mesh;
 
 import android.opengl.GLES31;
+import android.opengl.Matrix;
 
 import org.distorted.library.effect.MatrixEffect;
 import org.distorted.library.main.DistortedLibrary;
@@ -29,6 +30,7 @@ import org.distorted.library.program.DistortedProgram;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
+import java.util.ArrayList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
@@ -63,21 +65,73 @@ public abstract class MeshBase
 
    private boolean mShowNormals;      // when rendering this mesh, draw normal vectors?
    private InternalBuffer mVBO, mTFO; // main vertex buffer and transform feedback buffer
-   private final float zFactor;       // strange workaround for the fact that we need to somehow store the 'depth'
-                                      // of the Mesh. Used in DistortedEffects. See DistortedTexture.getDepth().
    private int mNumVertices;
    private float[] mVertAttribs;      // packed: PosX,PosY,PosZ, NorX,NorY,NorZ, InfX,InfY,InfZ, TexS,TexT
    private float mInflate;
 
+   private class Component
+     {
+     private int mEndIndex;
+     private float[] mTextureMap;
+
+     Component()
+       {
+       mTextureMap = new float[8];
+
+       mTextureMap[ 0] = 0.0f;  // LLX
+       mTextureMap[ 1] = 0.0f;  // LLY
+       mTextureMap[ 2] = 0.0f;  // ULX
+       mTextureMap[ 3] = 1.0f;  // ULY
+       mTextureMap[ 4] = 1.0f;  // URX
+       mTextureMap[ 5] = 1.0f;  // URY
+       mTextureMap[ 6] = 1.0f;  // LRX
+       mTextureMap[ 7] = 0.0f;  // LRY
+       }
+     Component(Component original)
+       {
+       mEndIndex = original.mEndIndex;
+       mTextureMap = new float[8];
+       System.arraycopy(original.mTextureMap,0,mTextureMap,0,8);
+       }
+
+     }
+
+   private ArrayList<Component> mComponent;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   MeshBase(float factor)
+   MeshBase()
      {
-     zFactor      = factor;
      mShowNormals = false;
+     mInflate     = 0.0f;
+     mComponent = new ArrayList<>();
+     mComponent.add(new Component());
+
+     mVBO = new InternalBuffer(GLES31.GL_ARRAY_BUFFER             , GLES31.GL_STATIC_READ);
+     mTFO = new InternalBuffer(GLES31.GL_TRANSFORM_FEEDBACK_BUFFER, GLES31.GL_STATIC_READ);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// copy constructor
+
+   MeshBase(MeshBase original)
+     {
+     mShowNormals = original.mShowNormals;
+     mInflate     = original.mInflate;
+
+     int size = original.mComponent.size();
+     mComponent = new ArrayList<>();
+     for(int i=0; i<size; i++)
+       {
+       Component comp = new Component(original.mComponent.get(i));
+       mComponent.add(comp);
+       }
 
      mVBO = new InternalBuffer(GLES31.GL_ARRAY_BUFFER             , GLES31.GL_STATIC_READ);
      mTFO = new InternalBuffer(GLES31.GL_TRANSFORM_FEEDBACK_BUFFER, GLES31.GL_STATIC_READ);
+
+     System.arraycopy(original.mVertAttribs,0,mVertAttribs,0,original.mNumVertices*VERT_ATTRIBS);
+     setAttribs(mVertAttribs);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -88,6 +142,8 @@ public abstract class MeshBase
      mNumVertices = vertexAttribs.length/VERT_ATTRIBS;
      mVertAttribs = vertexAttribs;
 
+     mComponent.get(0).mEndIndex = mNumVertices;
+
      FloatBuffer attribs = ByteBuffer.allocateDirect(mNumVertices*VERT_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
      attribs.put(vertexAttribs).position(0);
 
@@ -117,17 +173,6 @@ public abstract class MeshBase
      return mNumVertices;
      }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public float getZFactor()
-     {
-     return zFactor;
-     }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Not part of public API, do not document (public only because has to be used from the main package)
@@ -219,10 +264,10 @@ public abstract class MeshBase
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Multiply all coordinates, normal and inflate vectors by the matrix.
+ * Apply all Effects to the vertex mesh. Overwrite the mesh in place.
  * <p>
- * This is a static, permanent modification of the vertices contained in this Mesh. If the Effect
- * contains any Dynamics, they will be evaluated at 0.
+ * This is a static, permanent modification of the vertices contained in this Mesh. If the effects
+ * contain any Dynamics, they will be evaluated at 0.
  *
  * Please note that calling this once with the complete list of Effects will be much faster than
  * calling it repeatedly with one Effect at a time, as we have to reallocate the array of vertices
@@ -230,15 +275,73 @@ public abstract class MeshBase
  */
    public void apply(MatrixEffect[] effects)
      {
-     for(int v=0; v<mNumVertices; v++)
+     float[][] matrix = new float[effects.length][16];
+     float[] tmp;
+     float[] array = new float[4];
+     float x,y,z;
+     int numEffects = 0;
+
+     for(MatrixEffect eff: effects)
        {
-       for(MatrixEffect eff: effects)
+       if( eff!=null )
          {
-         if( eff!=null ) eff.applyToVertex( mVertAttribs, v*VERT_ATTRIBS );
+         Matrix.setIdentityM(matrix[numEffects],0);
+         eff.compute(array,0,0,0);
+         eff.apply(matrix[numEffects], array, 0);
+         numEffects++;
          }
        }
 
-     setAttribs(mVertAttribs);
+     for(int index=0; index<mNumVertices; index+=VERT_ATTRIBS )
+       {
+       for(int mat=0; mat<numEffects; mat++)
+         {
+         tmp = matrix[mat];
+
+         x = mVertAttribs[index+POS_ATTRIB  ];
+         y = mVertAttribs[index+POS_ATTRIB+1];
+         z = mVertAttribs[index+POS_ATTRIB+2];
+
+         mVertAttribs[index+POS_ATTRIB  ] = tmp[0]*x + tmp[4]*y + tmp[ 8]*z + tmp[12];
+         mVertAttribs[index+POS_ATTRIB+1] = tmp[1]*x + tmp[5]*y + tmp[ 9]*z + tmp[13];
+         mVertAttribs[index+POS_ATTRIB+2] = tmp[2]*x + tmp[6]*y + tmp[10]*z + tmp[14];
+
+         x = mVertAttribs[index+NOR_ATTRIB  ];
+         y = mVertAttribs[index+NOR_ATTRIB+1];
+         z = mVertAttribs[index+NOR_ATTRIB+2];
+
+         mVertAttribs[index+NOR_ATTRIB  ] = tmp[0]*x + tmp[4]*y + tmp[ 8]*z;
+         mVertAttribs[index+NOR_ATTRIB+1] = tmp[1]*x + tmp[5]*y + tmp[ 9]*z;
+         mVertAttribs[index+NOR_ATTRIB+2] = tmp[2]*x + tmp[6]*y + tmp[10]*z;
+
+         x = mVertAttribs[index+INF_ATTRIB  ];
+         y = mVertAttribs[index+INF_ATTRIB+1];
+         z = mVertAttribs[index+INF_ATTRIB+2];
+
+         mVertAttribs[index+INF_ATTRIB  ] = tmp[0]*x + tmp[4]*y + tmp[ 8]*z;
+         mVertAttribs[index+INF_ATTRIB+1] = tmp[1]*x + tmp[5]*y + tmp[ 9]*z;
+         mVertAttribs[index+INF_ATTRIB+2] = tmp[2]*x + tmp[6]*y + tmp[10]*z;
+         }
+       }
+
+     FloatBuffer attribs = ByteBuffer.allocateDirect(mNumVertices*VERT_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
+     attribs.put(mVertAttribs).position(0);
+
+     mVBO.setData(mNumVertices*VERT_SIZE, attribs);
+     mTFO.setData(mNumVertices*TRAN_SIZE, null   );
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Join a list of Meshes into this one.
+ * <p>
+ * Please note that calling this once with the complete list of Meshes will be much faster than
+ * calling it repeatedly with one Mesh at a time, as we have to reallocate the array of vertices
+ * each time.
+ */
+   public void join(MeshBase[] meshes)
+     {
+
      }
    }
 
diff --git a/src/main/java/org/distorted/library/mesh/MeshCubes.java b/src/main/java/org/distorted/library/mesh/MeshCubes.java
index 9f641ed..109ad1e 100644
--- a/src/main/java/org/distorted/library/mesh/MeshCubes.java
+++ b/src/main/java/org/distorted/library/mesh/MeshCubes.java
@@ -31,7 +31,7 @@ import java.util.ArrayList;
  */
 public class MeshCubes extends MeshBase
    {
-   private static final float R = 0.0f;//0.2f;
+   private static final float R = 0.0f;
 
    private static final int FRONT = 0;
    private static final int BACK  = 1;
@@ -165,75 +165,10 @@ public class MeshCubes extends MeshBase
       int sideVert        = 2*(mSlices-1) + mSlices*sideVertOneSlice;
       int firstWinding    = (mSlices>0 && (frontVert+1)%2==1 ) ? 1:0;
       int dataL           = mSlices==0 ? frontVert : (frontVert+1) +firstWinding+ (1+sideVert+1) + (1+frontVert);
-/*
-      android.util.Log.e("CUBES","triangleShifts="+triangleShifts+" windingShifts="+windingShifts+" winding1="+firstWinding+" frontVert="+frontVert+" sideVert="+sideVert);
-      android.util.Log.e("CUBES", "frontW="+frontWalls+" fSegments="+frontSegments+" sWalls="+mSideWalls+" sSegments="+mEdgeNum+" sideBends="+mSideBends+" dataLen="+dataL );
-*/
+
       return dataL<0 ? 0:dataL;
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/*
-   private static String debug(short[] val)
-     {
-     String ret="";j
-     
-     for(int i=0; i<val.length; i++) ret+=(" "+val[i]); 
-     
-     return ret;
-     }
-*/
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/*
-   private static String debug(float[] val, int stop)
-     {
-     String ret="";
-     float v;
-     boolean neg;
-     int mod;
-
-     for(int i=0; i<val.length; i++) 
-        {
-        if( i%stop==0 ) ret+="\n";
-
-        mod = i%stop;
-
-        if( mod==0 || mod==3 || mod==6 ) ret+=" (";
-
-        v = val[i];
-        if( v==-0.0f ) v=0.0f;
-
-
-        neg = v<0;
-        v = (v<0 ? -v:v);
-
-        ret+=((neg? " -":" +")+v);
-
-        if( mod==2 || mod==5 || mod==7 ) ret+=")";
-        }
-
-     return ret;
-     }
-*/
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/*
-   private static String debug(Edge e)
-     {
-     String d = "";
-     
-     switch(e.side)
-       {
-       case NORTH: d+="NORTH "; break;
-       case SOUTH: d+="SOUTH "; break;
-       case WEST : d+="WEST  "; break;
-       case EAST : d+="EAST  "; break;
-       }
-     
-     d+=("("+e.row+","+e.col+")");
-     
-     return d;
-     }   
-*/
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
    private void prepareDataStructures(int cols, String desc, int slices)
@@ -347,8 +282,6 @@ public class MeshCubes extends MeshBase
 
        do
          {
-         //android.util.Log.d("CUBES", "checking edge "+debug(e1));
-
          mSideWalls++;
 
          if( e1.side==NORTH || e1.side==SOUTH )
@@ -362,8 +295,6 @@ public class MeshCubes extends MeshBase
                mEdges.remove(j);
                mEdgeNum--;
                j--;
-
-               //android.util.Log.e("CUBES", "removing edge "+debug(e2));
                }
              }
            }
@@ -456,13 +387,6 @@ public class MeshCubes extends MeshBase
      if( lr>0 ) lr= 1;
      mNormalX[3] = lr*R;
      mNormalY[3] = td*R;
-     /*
-     android.util.Log.d("CUBES", "row="+row+" col="+col);
-     android.util.Log.d("CUBES", mNormalX[0]+" "+mNormalY[0]);
-     android.util.Log.d("CUBES", mNormalX[1]+" "+mNormalY[1]);
-     android.util.Log.d("CUBES", mNormalX[2]+" "+mNormalY[2]);
-     android.util.Log.d("CUBES", mNormalX[3]+" "+mNormalY[3]);
-     */
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -475,8 +399,6 @@ public class MeshCubes extends MeshBase
      boolean currentBlockIsNE;
      float vectZ = (front ? 0.5f : -0.5f);
 
-     //android.util.Log.d("CUBES", "buildFrontBack");
-
      for(int row=0; row<mRows; row++)
        {
        last =0;
@@ -491,8 +413,6 @@ public class MeshCubes extends MeshBase
 
            if( !seenLand && !front && ((currVert%2==1)^currentBlockIsNE) )
              {
-             //android.util.Log.d("CUBES","repeating winding2 vertex");
-
              repeatLast(attribs);
              }
 
@@ -538,8 +458,6 @@ public class MeshCubes extends MeshBase
 
   private void buildSideGrid(float[] attribs)
      {
-     //android.util.Log.d("CUBES", "buildSide");
-
      for(int i=0; i<mEdgeNum; i++)
        {
        buildIthSide(mEdges.get(i), attribs);
@@ -599,9 +517,7 @@ public class MeshCubes extends MeshBase
      {
      int col = curr.col;
      int row = curr.row;
-      
-     //android.util.Log.e("CUBES", "row="+row+" col="+col+" mRows="+mRows+" mCols="+mCols);
-                       
+
      switch(curr.side) 
        {
        case NORTH: if( col==mCols-1 ) 
@@ -695,8 +611,6 @@ public class MeshCubes extends MeshBase
 
       mInflateY[row][col] = (byte)diff;
       }
-
-    //android.util.Log.e("mesh","col="+col+" row="+row+" inflateX="+mInflateX[col][row]+" InflateY="+mInflateY[col][row]);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -736,7 +650,6 @@ public class MeshCubes extends MeshBase
 
   private void addSideVertex(Edge curr, boolean back, int slice, int side, float[] attribs)
      {
-     //android.util.Log.e("CUBES", "adding Side vertex!");
      float x, y, z;
      int row, col;
 
@@ -839,8 +752,6 @@ public class MeshCubes extends MeshBase
 
    private void repeatLast(float[] attribs)
      {
-     //android.util.Log.e("CUBES", "repeating last vertex!");
-
      attribs[VERT_ATTRIBS*currVert + POS_ATTRIB  ] = attribs[VERT_ATTRIBS*(currVert-1) + POS_ATTRIB  ];
      attribs[VERT_ATTRIBS*currVert + POS_ATTRIB+1] = attribs[VERT_ATTRIBS*(currVert-1) + POS_ATTRIB+1];
      attribs[VERT_ATTRIBS*currVert + POS_ATTRIB+2] = attribs[VERT_ATTRIBS*(currVert-1) + POS_ATTRIB+2];
@@ -916,8 +827,6 @@ public class MeshCubes extends MeshBase
  */
  public MeshCubes(int cols, String desc, int slices)
    {
-   super( (float)slices/cols);
-
    Static4D map = new Static4D(0.0f,0.0f,1.0f,1.0f);
    fillTexMappings(map,map,map,map,map,map);
    prepareDataStructures(cols,desc,slices);
@@ -963,7 +872,6 @@ public class MeshCubes extends MeshBase
  */
  public MeshCubes(int cols, String desc, int slices, Static4D front, Static4D back, Static4D left, Static4D right, Static4D top, Static4D bottom)
    {
-   super( (float)slices/cols);
    fillTexMappings(front,back,left,right,top,bottom);
    prepareDataStructures(cols,desc,slices);
    build();
@@ -979,8 +887,6 @@ public class MeshCubes extends MeshBase
  */
  public MeshCubes(int cols, int rows, int slices)
    {
-   super( (float)slices/cols);
-
    Static4D map = new Static4D(0.0f,0.0f,1.0f,1.0f);
    fillTexMappings(map,map,map,map,map,map);
    prepareDataStructures(cols,rows,slices);
@@ -1010,7 +916,6 @@ public class MeshCubes extends MeshBase
  */
  public MeshCubes(int cols, int rows, int slices, Static4D front, Static4D back, Static4D left, Static4D right, Static4D top, Static4D bottom)
    {
-   super( (float)slices/cols);
    fillTexMappings(front,back,left,right,top,bottom);
    prepareDataStructures(cols,rows,slices);
    build();
diff --git a/src/main/java/org/distorted/library/mesh/MeshQuad.java b/src/main/java/org/distorted/library/mesh/MeshQuad.java
index 4d2acf2..7cc7967 100644
--- a/src/main/java/org/distorted/library/mesh/MeshQuad.java
+++ b/src/main/java/org/distorted/library/mesh/MeshQuad.java
@@ -56,8 +56,6 @@ public class MeshQuad extends MeshBase
    */
   public MeshQuad()
     {
-    super(0.0f);
-
     float[] attribs= new float[VERT_ATTRIBS*4];
 
     addVertex(0.0f,0.0f, attribs,0);
diff --git a/src/main/java/org/distorted/library/mesh/MeshRectangles.java b/src/main/java/org/distorted/library/mesh/MeshRectangles.java
index 5c7f2bc..4de10af 100644
--- a/src/main/java/org/distorted/library/mesh/MeshRectangles.java
+++ b/src/main/java/org/distorted/library/mesh/MeshRectangles.java
@@ -50,8 +50,6 @@ public class MeshRectangles extends MeshBase
                      (mCols>=2 && mRows>=2 ? 2*mRows-2 : 1);
        }
 
-     //android.util.Log.e("MeshRectangles","vertices="+numVertices+" rows="+mRows+" cols="+mCols);
-
      remainingVert = numVertices;
      }
 
@@ -83,8 +81,6 @@ public class MeshRectangles extends MeshBase
 
   private int repeatLast(int vertex, float[] attribs)
      {
-     //android.util.Log.e("MeshRectangles", "repeating last vertex!");
-
      if( vertex>0 )
        {
        remainingVert--;
@@ -122,8 +118,6 @@ public class MeshRectangles extends MeshBase
      final float X = 1.0f/mCols;
      final float Y = 1.0f/mRows;
 
-     //android.util.Log.d("MeshRectangles", "buildGrid");
-
      y = 0.0f;
 
      for(int row=0; row<mRows; row++)
@@ -151,26 +145,8 @@ public class MeshRectangles extends MeshBase
 
        y+=Y;
        }
-
-     //android.util.Log.d("MeshRectangles", "buildGrid done");
      }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/*
-  private static String debug(float[] val, int stop)
-     {
-     String ret="";
-
-     for(int i=0; i<val.length; i++)
-        {
-        if( i%stop==0 ) ret+="\n";
-        ret+=(" "+val[i]);
-        }
-
-     return ret;
-     }
-*/
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -182,16 +158,12 @@ public class MeshRectangles extends MeshBase
  */
  public MeshRectangles(int cols, int rows)
     {
-    super(0.0f);
     computeNumberOfVertices(cols,rows);
 
     float[] attribs= new float[VERT_ATTRIBS*numVertices];
 
     buildGrid(attribs);
 
-    //android.util.Log.e("MeshRectangles", "dataLen="+numVertices);
-    //android.util.Log.d("MeshRectangles", "attribs: "+debug(attribs,VERT_ATTRIBS) );
-
     if( remainingVert!=0 )
       android.util.Log.d("MeshRectangles", "remainingVert " +remainingVert );
 
diff --git a/src/main/java/org/distorted/library/mesh/MeshSphere.java b/src/main/java/org/distorted/library/mesh/MeshSphere.java
index 410f15c..d26d3ea 100644
--- a/src/main/java/org/distorted/library/mesh/MeshSphere.java
+++ b/src/main/java/org/distorted/library/mesh/MeshSphere.java
@@ -38,7 +38,7 @@ public class MeshSphere extends MeshBase
   // Single row is (longitude of V1, longitude of V2, (common) latitude of V1 and V2, latitude of V3)
   // longitude of V3 is simply midpoint of V1 and V2 so we don't have to specify it here.
 
-  private static final double FACES[][] =      {
+  private static final double[][] FACES =      {
 
       { 0.00*P, 0.25*P, 0.0, 0.5*P },
       { 0.25*P, 0.50*P, 0.0, 0.5*P },
@@ -79,8 +79,6 @@ public class MeshSphere extends MeshBase
 
   private void repeatVertex(float[] attribs)
     {
-    //android.util.Log.e("sphere", "repeat last!");
-
     if( currentVert>0 )
       {
       attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB  ];
@@ -172,8 +170,6 @@ public class MeshSphere extends MeshBase
     double longitude = midLongitude(lonV1, lonV2, quotZ );
     double latitude  = midLatitude(latV12, latV3, quotY );
 
-    //android.util.Log.e("sphere", "newVertex: long:"+lonPoint+" lat:"+latPoint+" column="+column+" row="+row);
-
     double sinLON = Math.sin(longitude);
     double cosLON = Math.cos(longitude);
     double sinLAT = Math.sin(latitude);
@@ -186,8 +182,6 @@ public class MeshSphere extends MeshBase
     double texX = 0.5 + longitude/(2*P);
     if( texX>=1.0 ) texX-=1.0;
 
-    //android.util.Log.e("tex", "longitude = "+((int)(180.0*longitude/P))+" texX="+texX );
-
     double texY = 0.5 + latitude/P;
 
     attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = x;  //
@@ -197,7 +191,7 @@ public class MeshSphere extends MeshBase
     attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = 2*x;//  the vertex coords, normal vector, and
     attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = 2*y;//  inflate vector have identical (x,y,z).
     attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = 2*z;//
-                                                           //  TODO: think dialog_about some more efficient
+                                                           //  TODO: think about some more efficient
     attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = x;  //  representation.
     attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = y;  //
     attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = z;  //
@@ -259,8 +253,6 @@ public class MeshSphere extends MeshBase
    */
   public MeshSphere(int level)
     {
-    super(1.0f);
-
     computeNumberOfVertices(level);
     float[] attribs= new float[VERT_ATTRIBS*numVertices];
 
