commit ea9b68db8fa88fd16c5300def60399a17b650663
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sat Mar 7 01:05:28 2020 +0000

    Progress with MeshJoin app.
    Fix rotating in some apps.

diff --git a/src/main/java/org/distorted/examples/earth/EarthSurfaceView.java b/src/main/java/org/distorted/examples/earth/EarthSurfaceView.java
index 8c27cd6..51199a9 100644
--- a/src/main/java/org/distorted/examples/earth/EarthSurfaceView.java
+++ b/src/main/java/org/distorted/examples/earth/EarthSurfaceView.java
@@ -33,6 +33,7 @@ import org.distorted.examples.R;
 
 class EarthSurfaceView extends GLSurfaceView
 {
+    private final static int DIRECTION_SENSITIVITY=  12;
     private int mX, mY;
     private EarthRenderer mRenderer;
 	
@@ -63,6 +64,29 @@ class EarthSurfaceView extends GLSurfaceView
       return mRenderer;
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void resetQuats()
+      {
+      float qx = mRenderer.mQuat1.get0();
+      float qy = mRenderer.mQuat1.get1();
+      float qz = mRenderer.mQuat1.get2();
+      float qw = mRenderer.mQuat1.get3();
+
+      float rx = mRenderer.mQuat2.get0();
+      float ry = mRenderer.mQuat2.get1();
+      float rz = mRenderer.mQuat2.get2();
+      float rw = mRenderer.mQuat2.get3();
+
+      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+      mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
+      mRenderer.mQuat2.set(tx, ty, tz, tw);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
     
     @Override public boolean onTouchEvent(MotionEvent event) 
@@ -95,40 +119,18 @@ class EarthSurfaceView extends GLSurfaceView
                                          
                                            mRenderer.mQuat1.set(px*sinA, py*sinA, pz*sinA, cosA);
                                            }
-                                         }                             
+                                         }
+                                       if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > mRenderer.mScreenMin*mRenderer.mScreenMin/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
+                                         {
+                                         mX = x;
+                                         mY = y;
+                                         resetQuats();
+                                         }
                                        break;
                                        
          case MotionEvent.ACTION_UP  : mX = -1;
                                        mY = -1;
-        	                           
-                                       float qx = mRenderer.mQuat1.get0();
-                                       float qy = mRenderer.mQuat1.get1();
-                                       float qz = mRenderer.mQuat1.get2();
-                                       float qw = mRenderer.mQuat1.get3();
-
-                                       float rx = mRenderer.mQuat2.get0();
-                                       float ry = mRenderer.mQuat2.get1();
-                                       float rz = mRenderer.mQuat2.get2();
-                                       float rw = mRenderer.mQuat2.get3();
-
-                                       // This is quaternion multiplication. (tx,ty,tz,tw)
-                                       // is now equal to (qx,qy,qz,qw)*(rx,ry,rz,rw)
-                                       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-                                       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-                                       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-                                       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-                                       // The point of this is so that there are always
-                                       // exactly 2 quaternions: Quat1 representing the rotation
-                                       // accumulating only since the last screen touch, and Quat2
-                                       // which remembers the combined effect of all previous
-                                       // swipes.
-                                       // We cannot be accumulating an ever-growing list of quaternions
-                                       // and add a new one every time user swipes the screen - there
-                                       // is a limited number of slots in the EffectQueueMatrix!
-                                       mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
-                                       mRenderer.mQuat2.set(tx, ty, tz, tw);
-                                       
+        	                             resetQuats();
                                        break;
          }
              
diff --git a/src/main/java/org/distorted/examples/flag/FlagRenderer.java b/src/main/java/org/distorted/examples/flag/FlagRenderer.java
index 4c92bfc..31811e1 100644
--- a/src/main/java/org/distorted/examples/flag/FlagRenderer.java
+++ b/src/main/java/org/distorted/examples/flag/FlagRenderer.java
@@ -102,8 +102,8 @@ class FlagRenderer implements GLSurfaceView.Renderer
       Static3D center= new Static3D(0,0,0);
 
       effects.apply( new MatrixEffectScale(mScale));
-      effects.apply( new MatrixEffectQuaternion(mQuat1, center) );
       effects.apply( new MatrixEffectQuaternion(mQuat2, center) );
+      effects.apply( new MatrixEffectQuaternion(mQuat1, center) );
 
       mScreen = new DistortedScreen();
       mScreen.attach(mTexture, effects, mesh );
diff --git a/src/main/java/org/distorted/examples/flag/FlagSurfaceView.java b/src/main/java/org/distorted/examples/flag/FlagSurfaceView.java
index 6e3ea49..6117598 100644
--- a/src/main/java/org/distorted/examples/flag/FlagSurfaceView.java
+++ b/src/main/java/org/distorted/examples/flag/FlagSurfaceView.java
@@ -30,6 +30,7 @@ import android.view.MotionEvent;
 
 class FlagSurfaceView extends GLSurfaceView
 {
+    private final static int DIRECTION_SENSITIVITY=  12;
     private int mX, mY;
     private FlagRenderer mRenderer;
 	
@@ -59,6 +60,29 @@ class FlagSurfaceView extends GLSurfaceView
       return mRenderer;
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void resetQuats()
+      {
+      float qx = mRenderer.mQuat1.get0();
+      float qy = mRenderer.mQuat1.get1();
+      float qz = mRenderer.mQuat1.get2();
+      float qw = mRenderer.mQuat1.get3();
+
+      float rx = mRenderer.mQuat2.get0();
+      float ry = mRenderer.mQuat2.get1();
+      float rz = mRenderer.mQuat2.get2();
+      float rw = mRenderer.mQuat2.get3();
+
+      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+      mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
+      mRenderer.mQuat2.set(tx, ty, tz, tw);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override public boolean onTouchEvent(MotionEvent event) 
@@ -91,40 +115,18 @@ class FlagSurfaceView extends GLSurfaceView
                                          
                                            mRenderer.mQuat1.set(px*sinA, py*sinA, pz*sinA, cosA);
                                            }
-                                         }                             
+                                         }
+                                       if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > mRenderer.mScreenMin*mRenderer.mScreenMin/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
+                                         {
+                                         mX = x;
+                                         mY = y;
+                                         resetQuats();
+                                         }
                                        break;
                                        
          case MotionEvent.ACTION_UP  : mX = -1;
                                        mY = -1;
-        	                           
-                                       float qx = mRenderer.mQuat1.get0();
-                                       float qy = mRenderer.mQuat1.get1();
-                                       float qz = mRenderer.mQuat1.get2();
-                                       float qw = mRenderer.mQuat1.get3();
-
-                                       float rx = mRenderer.mQuat2.get0();
-                                       float ry = mRenderer.mQuat2.get1();
-                                       float rz = mRenderer.mQuat2.get2();
-                                       float rw = mRenderer.mQuat2.get3();
-
-                                       // This is quaternion multiplication. (tx.ty.tz.tw)
-                                       // is now equal to (qx,qy,qz,qw)*(rx,ry,rz,rw)
-                                       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-                                       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-                                       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-                                       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-                                       // The point of this is so that there are always
-                                       // exactly 2 quaternions: Quat1 representing the rotation
-                                       // accumulating only since the last screen touch, and Quat2
-                                       // which remembers the combined effect of all previous
-                                       // swipes.
-                                       // We cannot be accumulating an ever-growing list of quaternions
-                                       // and add a new one every time user swipes the screen - there
-                                       // is a limited number of slots in the EffectQueueMatrix!
-                                       mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
-                                       mRenderer.mQuat2.set(tx, ty, tz, tw);
-                                       
+        	                             resetQuats();
                                        break;
          }
              
diff --git a/src/main/java/org/distorted/examples/generic/GenericSurfaceView.java b/src/main/java/org/distorted/examples/generic/GenericSurfaceView.java
index 272e1b9..de35bf0 100644
--- a/src/main/java/org/distorted/examples/generic/GenericSurfaceView.java
+++ b/src/main/java/org/distorted/examples/generic/GenericSurfaceView.java
@@ -30,6 +30,7 @@ import android.view.MotionEvent;
 
 class GenericSurfaceView extends GLSurfaceView
   {
+  private final static int DIRECTION_SENSITIVITY=  12;
   private int mX, mY;
   private GenericRenderer mRenderer;
 
@@ -56,6 +57,29 @@ class GenericSurfaceView extends GLSurfaceView
     return mRenderer;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void resetQuats()
+    {
+    float qx = mRenderer.mQuat1.get0();
+    float qy = mRenderer.mQuat1.get1();
+    float qz = mRenderer.mQuat1.get2();
+    float qw = mRenderer.mQuat1.get3();
+
+    float rx = mRenderer.mQuat2.get0();
+    float ry = mRenderer.mQuat2.get1();
+    float rz = mRenderer.mQuat2.get2();
+    float rw = mRenderer.mQuat2.get3();
+
+    float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+    float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+    float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+    float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+    mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
+    mRenderer.mQuat2.set(tx, ty, tz, tw);
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   @Override
@@ -90,29 +114,18 @@ class GenericSurfaceView extends GLSurfaceView
                                         mRenderer.mQuat1.set(px*sinA, py*sinA, pz*sinA, cosA);
                                         }
                                       }
+
+                                    if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > mRenderer.mScreenMin*mRenderer.mScreenMin/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
+                                      {
+                                      mX = x;
+                                      mY = y;
+                                      resetQuats();
+                                      }
                                     break;
 
       case MotionEvent.ACTION_UP  : mX = -1;
                                     mY = -1;
-
-                                    float qx = mRenderer.mQuat1.get0();
-                                    float qy = mRenderer.mQuat1.get1();
-                                    float qz = mRenderer.mQuat1.get2();
-                                    float qw = mRenderer.mQuat1.get3();
-
-                                    float rx = mRenderer.mQuat2.get0();
-                                    float ry = mRenderer.mQuat2.get1();
-                                    float rz = mRenderer.mQuat2.get2();
-                                    float rw = mRenderer.mQuat2.get3();
-
-                                    float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-                                    float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-                                    float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-                                    float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-                                    mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
-                                    mRenderer.mQuat2.set(tx, ty, tz, tw);
-
+                                    resetQuats();
                                     break;
       }
 
diff --git a/src/main/java/org/distorted/examples/inflate/InflateRenderer.java b/src/main/java/org/distorted/examples/inflate/InflateRenderer.java
index 0ef0345..5b2ac3a 100644
--- a/src/main/java/org/distorted/examples/inflate/InflateRenderer.java
+++ b/src/main/java/org/distorted/examples/inflate/InflateRenderer.java
@@ -88,8 +88,8 @@ class InflateRenderer implements GLSurfaceView.Renderer
 
       mEffects = new DistortedEffects();
       mEffects.apply( new MatrixEffectScale(mScale));
-      mEffects.apply( new MatrixEffectQuaternion(quatInt1, center) );
       mEffects.apply( new MatrixEffectQuaternion(quatInt2, center) );
+      mEffects.apply( new MatrixEffectQuaternion(quatInt1, center) );
       mEffects.apply( new FragmentEffectAlpha(mAlpha));
 
       mScreen = new DistortedScreen();
diff --git a/src/main/java/org/distorted/examples/inflate/InflateSurfaceView.java b/src/main/java/org/distorted/examples/inflate/InflateSurfaceView.java
index ae799d3..841c9e7 100644
--- a/src/main/java/org/distorted/examples/inflate/InflateSurfaceView.java
+++ b/src/main/java/org/distorted/examples/inflate/InflateSurfaceView.java
@@ -33,6 +33,7 @@ import android.widget.Toast;
 
 class InflateSurfaceView extends GLSurfaceView
 {
+    private final static int DIRECTION_SENSITIVITY=  12;
     private int mX, mY;
     private InflateRenderer mRenderer;
 	
@@ -63,6 +64,29 @@ class InflateSurfaceView extends GLSurfaceView
       return mRenderer;
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void resetQuats()
+      {
+      float qx = mRenderer.mQuat1.get0();
+      float qy = mRenderer.mQuat1.get1();
+      float qz = mRenderer.mQuat1.get2();
+      float qw = mRenderer.mQuat1.get3();
+
+      float rx = mRenderer.mQuat2.get0();
+      float ry = mRenderer.mQuat2.get1();
+      float rz = mRenderer.mQuat2.get2();
+      float rw = mRenderer.mQuat2.get3();
+
+      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+      mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
+      mRenderer.mQuat2.set(tx, ty, tz, tw);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
     
     @Override public boolean onTouchEvent(MotionEvent event) 
@@ -95,40 +119,18 @@ class InflateSurfaceView extends GLSurfaceView
                                          
                                            mRenderer.mQuat1.set(px*sinA, py*sinA, pz*sinA, cosA);
                                            }
-                                         }                             
+                                         }
+                                       if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > mRenderer.mScreenMin*mRenderer.mScreenMin/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
+                                         {
+                                         mX = x;
+                                         mY = y;
+                                         resetQuats();
+                                         }
                                        break;
                                        
          case MotionEvent.ACTION_UP  : mX = -1;
                                        mY = -1;
-        	                           
-                                       float qx = mRenderer.mQuat1.get0();
-                                       float qy = mRenderer.mQuat1.get1();
-                                       float qz = mRenderer.mQuat1.get2();
-                                       float qw = mRenderer.mQuat1.get3();
-
-                                       float rx = mRenderer.mQuat2.get0();
-                                       float ry = mRenderer.mQuat2.get1();
-                                       float rz = mRenderer.mQuat2.get2();
-                                       float rw = mRenderer.mQuat2.get3();
-
-                                       // This is quaternion multiplication. (tx,ty,tz,tw)
-                                       // is now equal to (qx,qy,qz,qw)*(rx,ry,rz,rw)
-                                       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-                                       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-                                       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-                                       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-                                       // The point of this is so that there are always
-                                       // exactly 2 quaternions: Quat1 representing the rotation
-                                       // accumulating only since the last screen touch, and Quat2
-                                       // which remembers the combined effect of all previous
-                                       // swipes.
-                                       // We cannot be accumulating an ever-growing list of quaternions
-                                       // and add a new one every time user swipes the screen - there
-                                       // is a limited number of slots in the EffectQueueMatrix!
-                                       mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
-                                       mRenderer.mQuat2.set(tx, ty, tz, tw);
-                                       
+        	                             resetQuats();
                                        break;
          }
              
diff --git a/src/main/java/org/distorted/examples/meshjoin/MeshJoinRenderer.java b/src/main/java/org/distorted/examples/meshjoin/MeshJoinRenderer.java
index 95c0b02..c2e1775 100644
--- a/src/main/java/org/distorted/examples/meshjoin/MeshJoinRenderer.java
+++ b/src/main/java/org/distorted/examples/meshjoin/MeshJoinRenderer.java
@@ -21,6 +21,8 @@ package org.distorted.examples.meshjoin;
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.opengl.GLSurfaceView;
 
 import org.distorted.examples.R;
@@ -29,16 +31,15 @@ import org.distorted.library.effect.MatrixEffectMove;
 import org.distorted.library.effect.MatrixEffectQuaternion;
 import org.distorted.library.effect.MatrixEffectRotate;
 import org.distorted.library.effect.MatrixEffectScale;
+import org.distorted.library.effect.VertexEffectSink;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedLibrary;
 import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.main.DistortedTexture;
 import org.distorted.library.mesh.MeshBase;
 import org.distorted.library.mesh.MeshJoined;
-import org.distorted.library.mesh.MeshQuad;
-import org.distorted.library.mesh.MeshRectangles;
-import org.distorted.library.mesh.MeshSphere;
 import org.distorted.library.mesh.MeshTriangles;
+import org.distorted.library.type.Dynamic1D;
 import org.distorted.library.type.DynamicQuat;
 import org.distorted.library.type.Static1D;
 import org.distorted.library.type.Static3D;
@@ -73,6 +74,10 @@ class MeshJoinRenderer implements GLSurfaceView.Renderer
       mScale= new Static3D(1,1,1);
       Static3D center=new Static3D(0,0,0);
 
+      Dynamic1D sink = new Dynamic1D(5000,0.0f);
+      sink.add( new Static1D(0.5f) );
+      sink.add( new Static1D(2.0f) );
+
       mQuat1 = new Static4D(0,0,0,1);  // unity
       mQuat2 = new Static4D(0,0,0,1);  // quaternions
 
@@ -86,6 +91,9 @@ class MeshJoinRenderer implements GLSurfaceView.Renderer
       mEffects.apply( new MatrixEffectQuaternion(quatInt2, center) );
       mEffects.apply( new MatrixEffectQuaternion(quatInt1, center) );
       mEffects.apply( new MatrixEffectScale(mScale));
+      mEffects.apply( new VertexEffectSink( sink, center, new Static4D(0,0,0,0.75f) ) );
+
+      mScreen.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -99,7 +107,7 @@ class MeshJoinRenderer implements GLSurfaceView.Renderer
     
     public void onSurfaceChanged(GL10 glUnused, int width, int height) 
       {
-      final float SCALE = 0.6f;
+      final float SCALE = 0.7f;
       mScreenMin = width<height ? width:height;
       float factor = SCALE*mScreenMin;
       mScale.set(factor,factor,factor);
@@ -110,7 +118,7 @@ class MeshJoinRenderer implements GLSurfaceView.Renderer
     
     public void onSurfaceCreated(GL10 glUnused, EGLConfig config) 
       {
-      InputStream is = mView.getContext().getResources().openRawResource(R.raw.grid);
+      InputStream is = mView.getContext().getResources().openRawResource(R.raw.pyraminx);
       Bitmap bitmap;
 
       try
@@ -127,15 +135,17 @@ class MeshJoinRenderer implements GLSurfaceView.Renderer
         }
 
       if( mTexture==null ) mTexture = new DistortedTexture();
-      mTexture.setTexture(bitmap);
+      mTexture.setTexture( createTetrahedronTexture(bitmap) );
 
       if( mMesh==null ) mMesh = createJoinedTetrahedron();
 
-     // mMesh.setShowNormals(true);
+      //mMesh.setShowNormals(true);
 
       mScreen.detachAll();
       mScreen.attach(mTexture,mEffects,mMesh);
 
+      VertexEffectSink.enable();
+
       try
         {
         DistortedLibrary.onCreate(mView.getContext());
@@ -146,99 +156,26 @@ class MeshJoinRenderer implements GLSurfaceView.Renderer
         }
       }
 
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private MeshBase createJoinedDoubleQuad()
-      {
-      MeshBase[] meshes = new MeshBase[2];
-      meshes[0] = new MeshQuad();
-      meshes[1] = new MeshQuad();
-
-      MatrixEffect[] effects0 = new MatrixEffect[1];
-      effects0[0] = new MatrixEffectMove( new Static3D(0,+0.6f,0));
-
-      meshes[0].apply(effects0);
-
-      MatrixEffect[] effects1 = new MatrixEffect[1];
-      effects1[0] = new MatrixEffectMove( new Static3D(0,-0.6f,0));
-
-      meshes[1].apply(effects1);
-
-      return new MeshJoined(meshes);
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    private MeshBase createJoinedSphereAndTriangle()
+    private Bitmap createTetrahedronTexture(Bitmap input)
       {
-      MeshBase[] meshes = new MeshBase[2];
-      meshes[0] = new MeshSphere(5);
-      meshes[1] = new MeshTriangles(5);
+      final int[] FACE_COLORS = new int[] { 0xffffff00, 0xff00ff00, 0xff0000ff, 0xffff0000 };
+      final int FACES=FACE_COLORS.length;
+      int height = input.getHeight();
 
-      MatrixEffect[] effects0 = new MatrixEffect[2];
-      effects0[0] = new MatrixEffectScale( new Static3D(0.5f, 1.0f, 0.5f) );
-      effects0[1] = new MatrixEffectMove( new Static3D(+0.25f,0,0));
+      Bitmap result = Bitmap.createBitmap(FACES*height,height, Bitmap.Config.ARGB_8888);
+      Canvas canvas = new Canvas(result);
+      Paint paint = new Paint();
+      paint.setStyle(Paint.Style.FILL);
 
-      meshes[0].apply(effects0);
-
-      MatrixEffect[] effects1 = new MatrixEffect[2];
-      effects1[0] = new MatrixEffectScale( new Static3D(0.5f, 1.0f, 0.5f) );
-      effects1[1] = new MatrixEffectMove( new Static3D(-0.25f,0,0));
-
-      meshes[1].apply(effects1);
-
-      return new MeshJoined(meshes);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
+      for(int i=0; i<FACES; i++)
+        {
+        paint.setColor(FACE_COLORS[i]);
+        canvas.drawRect(i*height,0,(i+1)*height,height,paint);
+        canvas.drawBitmap(input,i*height,0,null);
+        }
 
-    private MeshBase createJoinedCube()
-      {
-      final int MESHES=6;
-
-      Static3D axisY  = new Static3D(0,1,0);
-      Static3D axisX  = new Static3D(1,0,0);
-      Static3D center = new Static3D(0,0,0);
-      Static1D angle  = new Static1D(0);
-
-      MatrixEffect[] effectsY = new MatrixEffect[2];
-      effectsY[0] = new MatrixEffectMove(new Static3D(0,0,+0.5f));
-      effectsY[1] = new MatrixEffectRotate( angle, axisY, center );
-
-      MeshBase[] meshes = new MeshRectangles[MESHES];
-      for(int i=0; i<MESHES; i++) meshes[i] = new MeshRectangles(5,5);
-
-      angle.set(0);
-      meshes[0].apply(effectsY);
-      angle.set(90);
-      meshes[1].apply(effectsY);
-      angle.set(180);
-      meshes[2].apply(effectsY);
-      angle.set(270);
-      meshes[3].apply(effectsY);
-
-      MatrixEffect[] effectsX = new MatrixEffect[2];
-      effectsX[0] = new MatrixEffectMove(new Static3D(0,0,+0.5f));
-      effectsX[1] = new MatrixEffectRotate( angle, axisX, center );
-
-      angle.set( 90);
-      meshes[4].apply(effectsX);
-      angle.set(-90);
-      meshes[5].apply(effectsX);
-
-      MeshJoined result = new MeshJoined(meshes);
-
-      Static4D[] maps = new Static4D[MESHES];
-
-      maps[0] = new Static4D(3.0f/8, 3.0f/8, 2.0f/8, 2.0f/8);
-      maps[1] = new Static4D(5.0f/8, 1.0f/8, 2.0f/8, 2.0f/8);
-      maps[2] = new Static4D(1.0f/8, 5.0f/8, 2.0f/8, 2.0f/8);
-      maps[3] = new Static4D(1.0f/8, 3.0f/8, 2.0f/8, 2.0f/8);
-      maps[4] = new Static4D(3.0f/8, 1.0f/8, 2.0f/8, 2.0f/8);
-      maps[5] = new Static4D(1.0f/8, 1.0f/8, 2.0f/8, 2.0f/8);
-
-      result.setTextureMap(maps);
       return result;
       }
 
@@ -251,7 +188,7 @@ class MeshJoinRenderer implements GLSurfaceView.Renderer
       final int MESHES=4;
 
       MeshBase[] meshes = new MeshTriangles[MESHES];
-      for(int i=0; i<MESHES; i++) meshes[i] = new MeshTriangles(5);
+      for(int i=0; i<MESHES; i++) meshes[i] = new MeshTriangles(10);
 
       MatrixEffect[] effects0 = new MatrixEffect[3];
       effects0[0] = new MatrixEffectScale( new Static3D(1,SQ3/2,1) );
@@ -279,6 +216,13 @@ class MeshJoinRenderer implements GLSurfaceView.Renderer
       axis.set2(SQ3/2);
       meshes[3].apply(effects1);
 
-      return new MeshJoined(meshes);
+      Static4D[] textureMaps = new Static4D[MESHES];
+
+      for(int i=0; i<MESHES; i++) textureMaps[i] = new Static4D(i*0.25f,0.0f,0.25f,0.866f);
+
+      MeshBase result = new MeshJoined(meshes);
+      result.setTextureMap(textureMaps);
+
+      return result;
       }
 }
diff --git a/src/main/java/org/distorted/examples/meshjoin/MeshJoinSurfaceView.java b/src/main/java/org/distorted/examples/meshjoin/MeshJoinSurfaceView.java
index 1c35afa..bfff5c9 100644
--- a/src/main/java/org/distorted/examples/meshjoin/MeshJoinSurfaceView.java
+++ b/src/main/java/org/distorted/examples/meshjoin/MeshJoinSurfaceView.java
@@ -29,6 +29,7 @@ import android.view.MotionEvent;
 
 class MeshJoinSurfaceView extends GLSurfaceView
 {
+    private final static int DIRECTION_SENSITIVITY=  12;
     private int mX, mY;
     private MeshJoinRenderer mRenderer;
 	
@@ -56,7 +57,40 @@ class MeshJoinSurfaceView extends GLSurfaceView
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-    
+
+    private void resetQuats()
+      {
+      float qx = mRenderer.mQuat1.get0();
+      float qy = mRenderer.mQuat1.get1();
+      float qz = mRenderer.mQuat1.get2();
+      float qw = mRenderer.mQuat1.get3();
+
+      float rx = mRenderer.mQuat2.get0();
+      float ry = mRenderer.mQuat2.get1();
+      float rz = mRenderer.mQuat2.get2();
+      float rw = mRenderer.mQuat2.get3();
+
+       // This is quaternion multiplication. (tx,ty,tz,tw)
+       // is now equal to (qx,qy,qz,qw)*(rx,ry,rz,rw)
+       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+       // The point of this is so that there are always
+       // exactly 2 quaternions: Quat1 representing the rotation
+       // accumulating only since the last screen touch, and Quat2
+       // which remembers the combined effect of all previous
+       // swipes.
+       // We cannot be accumulating an ever-growing list of quaternions
+       // and add a new one every time user swipes the screen - there
+       // is a limited number of slots in the EffectQueueMatrix!
+       mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
+       mRenderer.mQuat2.set(tx, ty, tz, tw);
+       }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
     @Override public boolean onTouchEvent(MotionEvent event) 
       {
       int action = event.getAction();
@@ -87,40 +121,20 @@ class MeshJoinSurfaceView extends GLSurfaceView
                                          
                                            mRenderer.mQuat1.set(px*sinA, py*sinA, pz*sinA, cosA);
                                            }
-                                         }                             
+                                         }
+
+                                       if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > mRenderer.mScreenMin*mRenderer.mScreenMin/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
+                                         {
+                                         mX = x;
+                                         mY = y;
+                                         resetQuats();
+                                         }
+
                                        break;
                                        
          case MotionEvent.ACTION_UP  : mX = -1;
                                        mY = -1;
-        	                           
-                                       float qx = mRenderer.mQuat1.get0();
-                                       float qy = mRenderer.mQuat1.get1();
-                                       float qz = mRenderer.mQuat1.get2();
-                                       float qw = mRenderer.mQuat1.get3();
-
-                                       float rx = mRenderer.mQuat2.get0();
-                                       float ry = mRenderer.mQuat2.get1();
-                                       float rz = mRenderer.mQuat2.get2();
-                                       float rw = mRenderer.mQuat2.get3();
-
-                                       // This is quaternion multiplication. (tx,ty,tz,tw)
-                                       // is now equal to (qx,qy,qz,qw)*(rx,ry,rz,rw)
-                                       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-                                       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-                                       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-                                       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-                                       // The point of this is so that there are always
-                                       // exactly 2 quaternions: Quat1 representing the rotation
-                                       // accumulating only since the last screen touch, and Quat2
-                                       // which remembers the combined effect of all previous
-                                       // swipes.
-                                       // We cannot be accumulating an ever-growing list of quaternions
-                                       // and add a new one every time user swipes the screen - there
-                                       // is a limited number of slots in the EffectQueueMatrix!
-                                       mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
-                                       mRenderer.mQuat2.set(tx, ty, tz, tw);
-                                       
+                                       resetQuats();
                                        break;
          }
              
diff --git a/src/main/java/org/distorted/examples/multiblur/MultiblurSurfaceView.java b/src/main/java/org/distorted/examples/multiblur/MultiblurSurfaceView.java
index 4d05d74..6f99637 100644
--- a/src/main/java/org/distorted/examples/multiblur/MultiblurSurfaceView.java
+++ b/src/main/java/org/distorted/examples/multiblur/MultiblurSurfaceView.java
@@ -33,6 +33,7 @@ import org.distorted.examples.R;
 
 class MultiblurSurfaceView extends GLSurfaceView
 {
+    private final static int DIRECTION_SENSITIVITY=  12;
     private int mX, mY;
     private MultiblurRenderer mRenderer;
 	
@@ -63,6 +64,29 @@ class MultiblurSurfaceView extends GLSurfaceView
       return mRenderer;
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void resetQuats()
+      {
+      float qx = mRenderer.mQuat1.get0();
+      float qy = mRenderer.mQuat1.get1();
+      float qz = mRenderer.mQuat1.get2();
+      float qw = mRenderer.mQuat1.get3();
+
+      float rx = mRenderer.mQuat2.get0();
+      float ry = mRenderer.mQuat2.get1();
+      float rz = mRenderer.mQuat2.get2();
+      float rw = mRenderer.mQuat2.get3();
+
+      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+      mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
+      mRenderer.mQuat2.set(tx, ty, tz, tw);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
     
     @Override public boolean onTouchEvent(MotionEvent event) 
@@ -95,40 +119,18 @@ class MultiblurSurfaceView extends GLSurfaceView
                                          
                                            mRenderer.mQuat1.set(px*sinA, py*sinA, pz*sinA, cosA);
                                            }
-                                         }                             
+                                         }
+                                       if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > mRenderer.mScreenMin*mRenderer.mScreenMin/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
+                                         {
+                                         mX = x;
+                                         mY = y;
+                                         resetQuats();
+                                         }
                                        break;
                                        
          case MotionEvent.ACTION_UP  : mX = -1;
                                        mY = -1;
-        	                           
-                                       float qx = mRenderer.mQuat1.get0();
-                                       float qy = mRenderer.mQuat1.get1();
-                                       float qz = mRenderer.mQuat1.get2();
-                                       float qw = mRenderer.mQuat1.get3();
-
-                                       float rx = mRenderer.mQuat2.get0();
-                                       float ry = mRenderer.mQuat2.get1();
-                                       float rz = mRenderer.mQuat2.get2();
-                                       float rw = mRenderer.mQuat2.get3();
-
-                                       // This is quaternion multiplication. (tx,ty,tz,tw)
-                                       // is now equal to (qx,qy,qz,qw)*(rx,ry,rz,rw)
-                                       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-                                       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-                                       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-                                       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-                                       // The point of this is so that there are always
-                                       // exactly 2 quaternions: Quat1 representing the rotation
-                                       // accumulating only since the last screen touch, and Quat2
-                                       // which remembers the combined effect of all previous
-                                       // swipes.
-                                       // We cannot be accumulating an ever-growing list of quaternions
-                                       // and add a new one every time user swipes the screen - there
-                                       // is a limited number of slots in the EffectQueueMatrix!
-                                       mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
-                                       mRenderer.mQuat2.set(tx, ty, tz, tw);
-                                       
+        	                             resetQuats();
                                        break;
          }
              
diff --git a/src/main/java/org/distorted/examples/transparency/TransparencySurfaceView.java b/src/main/java/org/distorted/examples/transparency/TransparencySurfaceView.java
index 6f23b2a..019c9ec 100644
--- a/src/main/java/org/distorted/examples/transparency/TransparencySurfaceView.java
+++ b/src/main/java/org/distorted/examples/transparency/TransparencySurfaceView.java
@@ -33,6 +33,7 @@ import org.distorted.examples.R;
 
 class TransparencySurfaceView extends GLSurfaceView
 {
+    private final static int DIRECTION_SENSITIVITY=  12;
     private int mX, mY;
     private TransparencyRenderer mRenderer;
 
@@ -63,6 +64,29 @@ class TransparencySurfaceView extends GLSurfaceView
       return mRenderer;
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void resetQuats()
+      {
+      float qx = mRenderer.mQuat1.get0();
+      float qy = mRenderer.mQuat1.get1();
+      float qz = mRenderer.mQuat1.get2();
+      float qw = mRenderer.mQuat1.get3();
+
+      float rx = mRenderer.mQuat2.get0();
+      float ry = mRenderer.mQuat2.get1();
+      float rz = mRenderer.mQuat2.get2();
+      float rw = mRenderer.mQuat2.get3();
+
+      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+      mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
+      mRenderer.mQuat2.set(tx, ty, tz, tw);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
     
     @Override public boolean onTouchEvent(MotionEvent event) 
@@ -95,40 +119,18 @@ class TransparencySurfaceView extends GLSurfaceView
                                          
                                            mRenderer.mQuat1.set(px*sinA, py*sinA, pz*sinA, cosA);
                                            }
-                                         }                             
+                                         }
+                                       if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > mRenderer.mScreenMin*mRenderer.mScreenMin/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
+                                         {
+                                         mX = x;
+                                         mY = y;
+                                         resetQuats();
+                                         }
                                        break;
                                        
          case MotionEvent.ACTION_UP  : mX = -1;
                                        mY = -1;
-        	                           
-                                       float qx = mRenderer.mQuat1.get0();
-                                       float qy = mRenderer.mQuat1.get1();
-                                       float qz = mRenderer.mQuat1.get2();
-                                       float qw = mRenderer.mQuat1.get3();
-
-                                       float rx = mRenderer.mQuat2.get0();
-                                       float ry = mRenderer.mQuat2.get1();
-                                       float rz = mRenderer.mQuat2.get2();
-                                       float rw = mRenderer.mQuat2.get3();
-
-                                       // This is quaternion multiplication. (tx,ty,tz,tw)
-                                       // is now equal to (qx,qy,qz,qw)*(rx,ry,rz,rw)
-                                       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-                                       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-                                       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-                                       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-                                       // The point of this is so that there are always
-                                       // exactly 2 quaternions: Quat1 representing the rotation
-                                       // accumulating only since the last screen touch, and Quat2
-                                       // which remembers the combined effect of all previous
-                                       // swipes.
-                                       // We cannot be accumulating an ever-growing list of quaternions
-                                       // and add a new one every time user swipes the screen - there
-                                       // is a limited number of slots in the EffectQueueMatrix!
-                                       mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
-                                       mRenderer.mQuat2.set(tx, ty, tz, tw);
-                                       
+        	                             resetQuats();
                                        break;
          }
              
diff --git a/src/main/java/org/distorted/examples/triblur/TriblurSurfaceView.java b/src/main/java/org/distorted/examples/triblur/TriblurSurfaceView.java
index abf6683..602e20a 100644
--- a/src/main/java/org/distorted/examples/triblur/TriblurSurfaceView.java
+++ b/src/main/java/org/distorted/examples/triblur/TriblurSurfaceView.java
@@ -33,6 +33,7 @@ import org.distorted.examples.R;
 
 class TriblurSurfaceView extends GLSurfaceView
 {
+    private final static int DIRECTION_SENSITIVITY=  12;
     private int mX, mY;
     private TriblurRenderer mRenderer;
 
@@ -63,6 +64,29 @@ class TriblurSurfaceView extends GLSurfaceView
       return mRenderer;
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void resetQuats()
+      {
+      float qx = mRenderer.mQuat1.get0();
+      float qy = mRenderer.mQuat1.get1();
+      float qz = mRenderer.mQuat1.get2();
+      float qw = mRenderer.mQuat1.get3();
+
+      float rx = mRenderer.mQuat2.get0();
+      float ry = mRenderer.mQuat2.get1();
+      float rz = mRenderer.mQuat2.get2();
+      float rw = mRenderer.mQuat2.get3();
+
+      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+      mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
+      mRenderer.mQuat2.set(tx, ty, tz, tw);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
     
     @Override public boolean onTouchEvent(MotionEvent event) 
@@ -95,40 +119,18 @@ class TriblurSurfaceView extends GLSurfaceView
                                          
                                            mRenderer.mQuat1.set(px*sinA, py*sinA, pz*sinA, cosA);
                                            }
-                                         }                             
+                                         }
+                                       if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > mRenderer.mScreenMin*mRenderer.mScreenMin/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
+                                         {
+                                         mX = x;
+                                         mY = y;
+                                         resetQuats();
+                                         }
                                        break;
                                        
          case MotionEvent.ACTION_UP  : mX = -1;
                                        mY = -1;
-        	                           
-                                       float qx = mRenderer.mQuat1.get0();
-                                       float qy = mRenderer.mQuat1.get1();
-                                       float qz = mRenderer.mQuat1.get2();
-                                       float qw = mRenderer.mQuat1.get3();
-
-                                       float rx = mRenderer.mQuat2.get0();
-                                       float ry = mRenderer.mQuat2.get1();
-                                       float rz = mRenderer.mQuat2.get2();
-                                       float rw = mRenderer.mQuat2.get3();
-
-                                       // This is quaternion multiplication. (tx,ty,tz,tw)
-                                       // is now equal to (qx,qy,qz,qw)*(rx,ry,rz,rw)
-                                       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-                                       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-                                       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-                                       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-                                       // The point of this is so that there are always
-                                       // exactly 2 quaternions: Quat1 representing the rotation
-                                       // accumulating only since the last screen touch, and Quat2
-                                       // which remembers the combined effect of all previous
-                                       // swipes.
-                                       // We cannot be accumulating an ever-growing list of quaternions
-                                       // and add a new one every time user swipes the screen - there
-                                       // is a limited number of slots in the EffectQueueMatrix!
-                                       mRenderer.mQuat1.set(0f, 0f, 0f, 1f);
-                                       mRenderer.mQuat2.set(tx, ty, tz, tw);
-                                       
+        	                             resetQuats();
                                        break;
          }
              
