commit e4c0057e7894d423fb06473e7aefc22d2e269ae8
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri Mar 29 21:26:31 2019 +0000

    Finish the Rubik app.

diff --git a/src/main/java/org/distorted/examples/rubik/RubikRenderer.java b/src/main/java/org/distorted/examples/rubik/RubikRenderer.java
index 7703c0a..20f0bc7 100644
--- a/src/main/java/org/distorted/examples/rubik/RubikRenderer.java
+++ b/src/main/java/org/distorted/examples/rubik/RubikRenderer.java
@@ -24,14 +24,17 @@ import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.opengl.GLSurfaceView;
 
+import org.distorted.library.effect.EffectName;
 import org.distorted.library.effect.MatrixEffectMove;
 import org.distorted.library.effect.MatrixEffectQuaternion;
 import org.distorted.library.effect.MatrixEffectScale;
+import org.distorted.library.effect.PostprocessEffectGlow;
 import org.distorted.library.main.Distorted;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.main.DistortedTexture;
 import org.distorted.library.mesh.MeshCubes;
+import org.distorted.library.type.Static1D;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 
@@ -42,10 +45,12 @@ import javax.microedition.khronos.opengles.GL10;
 
 class RubikRenderer implements GLSurfaceView.Renderer
 {
-    private static final int CUBE_SIZE = 3;
+            static final int NUM_CUBES = 3;
     private static final int VERTICES  = 5;
     private static final int SIZE      = 200;
 
+    private static final float CUBE_SCREEN_RATIO = 0.5f;
+
     private GLSurfaceView mView;
     private DistortedTexture mTexture;
     private DistortedScreen mScreen;
@@ -53,7 +58,13 @@ class RubikRenderer implements GLSurfaceView.Renderer
     private MeshCubes[][][] mCubes;
     private DistortedEffects[][][] mEffects;
 
-    Static4D mQuat1, mQuat2;
+    private int mScreenWidth, mScreenHeight;
+    private int mLastRow, mLastCol, mLastSli;
+
+    private Static1D mGlowRadius;
+    private Static4D mGlowColor;
+
+    Static4D mQuatCurrent, mQuatAccumulated;
     int mScreenMin;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -63,23 +74,28 @@ class RubikRenderer implements GLSurfaceView.Renderer
       mView = v;
 
       mScreen = new DistortedScreen();
-   // mScreen.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+      mScreen.setProjection(90.0f, 0.1f);
 
-      mQuat1 = new Static4D(           0,         0,           0,          1);  // unity quaternion
-      mQuat2 = new Static4D(-0.25189602f,0.3546389f,0.009657208f,0.90038127f);  // something semi-random that looks good
+      mQuatCurrent     = new Static4D(           0,         0,           0,          1);  // unity quaternion
+      mQuatAccumulated = new Static4D(-0.25189602f,0.3546389f,0.009657208f,0.90038127f);  // something semi-random that looks good
 
-      mCubes = new MeshCubes[CUBE_SIZE][CUBE_SIZE][CUBE_SIZE];
-      mEffects = new DistortedEffects[CUBE_SIZE][CUBE_SIZE][CUBE_SIZE];
-      Static3D[][][] cubeVectors = new Static3D[CUBE_SIZE][CUBE_SIZE][CUBE_SIZE];
+      mCubes = new MeshCubes[NUM_CUBES][NUM_CUBES][NUM_CUBES];
+      mEffects = new DistortedEffects[NUM_CUBES][NUM_CUBES][NUM_CUBES];
+      Static3D[][][] cubeVectors = new Static3D[NUM_CUBES][NUM_CUBES][NUM_CUBES];
 
       mMove  = new Static3D(0,0,0);
       mScale = new Static3D(1,1,1);
       mCenter= new Static3D(0,0,0);
 
+      mLastRow = mLastCol = mLastSli = 0;
+
+      mGlowRadius = new Static1D(10);
+      mGlowColor  = new Static4D(1.0f,1.0f,1.0f,0.6f);
+
       MatrixEffectMove       move  = new MatrixEffectMove(mMove);
       MatrixEffectScale      scale = new MatrixEffectScale(mScale);
-      MatrixEffectQuaternion quat1 = new MatrixEffectQuaternion(mQuat1, mCenter);
-      MatrixEffectQuaternion quat2 = new MatrixEffectQuaternion(mQuat2, mCenter);
+      MatrixEffectQuaternion quat1 = new MatrixEffectQuaternion(mQuatCurrent    , mCenter);
+      MatrixEffectQuaternion quat2 = new MatrixEffectQuaternion(mQuatAccumulated, mCenter);
 
       // 3x2 bitmap = 6 squares:
       //
@@ -103,20 +119,20 @@ class RubikRenderer implements GLSurfaceView.Renderer
 
       Static4D tmpFront, tmpBack, tmpLeft, tmpRight, tmpTop, tmpBottom;
 
-      for(int x=0; x<CUBE_SIZE; x++)
-        for(int y=0; y<CUBE_SIZE; y++)
-          for(int z=0; z<CUBE_SIZE; z++)
+      for(int x = 0; x< NUM_CUBES; x++)
+        for(int y = 0; y< NUM_CUBES; y++)
+          for(int z = 0; z< NUM_CUBES; z++)
             {
             tmpLeft  = (x==          0 ? mapLeft  :mapBlack);
-            tmpRight = (x==CUBE_SIZE-1 ? mapRight :mapBlack);
-            tmpFront = (z==CUBE_SIZE-1 ? mapFront :mapBlack);
+            tmpRight = (x== NUM_CUBES -1 ? mapRight :mapBlack);
+            tmpFront = (z== NUM_CUBES -1 ? mapFront :mapBlack);
             tmpBack  = (z==          0 ? mapBack  :mapBlack);
-            tmpTop   = (y==CUBE_SIZE-1 ? mapTop   :mapBlack);
+            tmpTop   = (y== NUM_CUBES -1 ? mapTop   :mapBlack);
             tmpBottom= (y==          0 ? mapBottom:mapBlack);
 
             mCubes[x][y][z] = new MeshCubes(VERTICES,VERTICES,VERTICES, tmpFront, tmpBack, tmpLeft, tmpRight, tmpTop, tmpBottom);
 
-            cubeVectors[x][y][z] = new Static3D( SIZE*(x-0.5f*(CUBE_SIZE-1)), SIZE*(y-0.5f*(CUBE_SIZE-1)), SIZE*(z-0.5f*(CUBE_SIZE-1)) );
+            cubeVectors[x][y][z] = new Static3D( SIZE*(x-0.5f*(NUM_CUBES -1)), SIZE*(y-0.5f*(NUM_CUBES -1)), SIZE*(z-0.5f*(NUM_CUBES -1)) );
 
             mEffects[x][y][z] = new DistortedEffects();
 
@@ -139,13 +155,15 @@ class RubikRenderer implements GLSurfaceView.Renderer
     
     public void onSurfaceChanged(GL10 glUnused, int width, int height) 
       {
+      mScreenWidth = width;
+      mScreenHeight= height;
       mScreenMin = width<height ? width:height;
 
       float w = mTexture.getWidth();
       float h = mTexture.getHeight();
       float d = mTexture.getDepth(mCubes[0][0][0]);
 
-      float factor = 0.6f*(width>height ? height/h:width/w)/CUBE_SIZE;
+      float factor = CUBE_SCREEN_RATIO*(width>height ? height/h:width/w)/ NUM_CUBES;
 
       mCenter.set(w/2,h/2,d/2);
       mMove.set( (width-factor*w)/2 , (height-factor*h)/2 , -factor*d/2 );
@@ -183,13 +201,13 @@ class RubikRenderer implements GLSurfaceView.Renderer
       canvas.drawRect(0, 0, W, H, paint);                          //
 
       paint.setColor(0xffff0000);                                  // RED
-      canvas.drawRoundRect(  0+M, 0+M,   S-M,   S-M, R, R, paint); //
+      canvas.drawRoundRect(    M,   M,   S-M,   S-M, R, R, paint); //
       paint.setColor(0xff00ff00);                                  // GREEN
-      canvas.drawRoundRect(  S+M, 0+M, 2*S-M,   S-M, R, R, paint); //
+      canvas.drawRoundRect(  S+M,   M, 2*S-M,   S-M, R, R, paint); //
       paint.setColor(0xff0000ff);                                  // BLUE
-      canvas.drawRoundRect(2*S+M, 0+M, 3*S-M,   S-M, R, R, paint); //
+      canvas.drawRoundRect(2*S+M,   M, 3*S-M,   S-M, R, R, paint); //
       paint.setColor(0xffffff00);                                  // YELLOW
-      canvas.drawRoundRect(  0+M, S+M,   S-M, 2*S-M, R, R, paint); //
+      canvas.drawRoundRect(    M, S+M,   S-M, 2*S-M, R, R, paint); //
       paint.setColor(0xffffffff);                                  // WHITE
       canvas.drawRoundRect(  S+M, S+M, 2*S-M, 2*S-M, R, R, paint); //
       paint.setColor(0xffb5651d);                                  // BROWN
@@ -200,11 +218,13 @@ class RubikRenderer implements GLSurfaceView.Renderer
 
       mScreen.detachAll();
 
-      for(int x=0; x<CUBE_SIZE; x++)
-        for(int y=0; y<CUBE_SIZE; y++)
-          for(int z=0; z<CUBE_SIZE; z++)
+      for(int x = 0; x< NUM_CUBES; x++)
+        for(int y = 0; y< NUM_CUBES; y++)
+          for(int z = 0; z< NUM_CUBES; z++)
              mScreen.attach(mTexture,mEffects[x][y][z],mCubes[x][y][z]);
 
+      PostprocessEffectGlow.enable();
+
       try
         {
         Distorted.onCreate(mView.getContext());
@@ -214,4 +234,60 @@ class RubikRenderer implements GLSurfaceView.Renderer
         android.util.Log.e("Rubik", ex.getMessage() );
         }
       }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    float returnCameraDistance()
+      {
+      float fov = mScreen.getFOV();
+      double fovInRadians = (fov*Math.PI) / 180.0;
+      double distance = (mScreenHeight*0.5f)/Math.tan(fovInRadians*0.5);
+
+      return (float)distance;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// NUM_CUBES individual little cubes, each SIZE in size, times 'scaleFactor' (see onSurfaceChanged)
+
+    float returnCubeSize()
+      {
+      float w = mTexture.getWidth();
+      float h = mTexture.getHeight();
+      float max = (mScreenWidth>mScreenHeight ? mScreenHeight/h:mScreenWidth/w);
+      float scaleFactor = CUBE_SCREEN_RATIO*max/NUM_CUBES;
+
+      return scaleFactor*NUM_CUBES*SIZE;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    float getScreenWidth()
+      {
+      return mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    float getScreenHeight()
+      {
+      return mScreenHeight;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void abortLastEffect()
+      {
+      mEffects[mLastCol][mLastRow][mLastSli].abortByName(EffectName.GLOW);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void applyNewEffect(int col, int row, int sli)
+      {
+      mLastCol = col;
+      mLastRow = row;
+      mLastSli = sli;
+
+      mEffects[mLastCol][mLastRow][mLastSli].apply(new PostprocessEffectGlow(mGlowRadius,mGlowColor));
+      }
 }
diff --git a/src/main/java/org/distorted/examples/rubik/RubikSurfaceView.java b/src/main/java/org/distorted/examples/rubik/RubikSurfaceView.java
index b43f616..2e5a72b 100644
--- a/src/main/java/org/distorted/examples/rubik/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/examples/rubik/RubikSurfaceView.java
@@ -25,11 +25,23 @@ import android.content.pm.ConfigurationInfo;
 import android.opengl.GLSurfaceView;
 import android.view.MotionEvent;
 
+import org.distorted.library.type.Static4D;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 class RubikSurfaceView extends GLSurfaceView
 {
+    private final static int ERROR  =-1;
+    private final static int FRONT  = 0;
+    private final static int BACK   = 1;
+    private final static int LEFT   = 2;
+    private final static int RIGHT  = 3;
+    private final static int TOP    = 4;
+    private final static int BOTTOM = 5;
+
+    private boolean mDragging;
     private int mX, mY;
+    private int mTouchedRow, mTouchedCol, mTouchedSli;
     private RubikRenderer mRenderer;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -38,9 +50,7 @@ class RubikSurfaceView extends GLSurfaceView
       {
       super(context);
 
-      mX = -1;
-      mY = -1;
-
+      mDragging = false;
       mRenderer = new RubikRenderer(this);
       final ActivityManager activityManager     = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
       final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
@@ -57,7 +67,8 @@ class RubikSurfaceView extends GLSurfaceView
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    @Override public boolean onTouchEvent(MotionEvent event)
+    @Override
+    public boolean onTouchEvent(MotionEvent event)
       {
       int action = event.getAction();
       int x = (int)event.getX();
@@ -65,67 +76,278 @@ class RubikSurfaceView extends GLSurfaceView
 
       switch(action)
          {
-         case MotionEvent.ACTION_DOWN: mX = x;
-                                       mY = y;
-                                       break;
-
-         case MotionEvent.ACTION_MOVE: if( mX>=0 && mY>= 0 )
+         case MotionEvent.ACTION_DOWN: if( faceTouched(x,y) != ERROR )
                                          {
-                                         float px = mY-y;
-                                         float py = mX-x;
-                                         float pz = 0;
-                                         float plen = (float)Math.sqrt(px*px + py*py + pz*pz);
-
-                                         if( plen>0 )
-                                           {
-                                           px /= plen;
-                                           py /= plen;
-                                           pz /= plen;
-
-                                           float cosA = (float)Math.cos(plen*3.14f/mRenderer.mScreenMin);
-                                           float sinA = (float)Math.sqrt(1-cosA*cosA);
-
-                                           mRenderer.mQuat1.set(px*sinA, py*sinA, pz*sinA, cosA);
-                                           }
+                                         mRenderer.abortLastEffect();
+                                         mRenderer.applyNewEffect(mTouchedCol, mTouchedRow, mTouchedSli);
+                                         }
+                                       else
+                                         {
+                                         mX = x;
+                                         mY = y;
+                                         mDragging = true;
                                          }
                                        break;
-
-         case MotionEvent.ACTION_UP  : mX = -1;
-                                       mY = -1;
-
-                                       float qx = mRenderer.mQuat1.get1();
-                                       float qy = mRenderer.mQuat1.get2();
-                                       float qz = mRenderer.mQuat1.get3();
-                                       float qw = mRenderer.mQuat1.get4();
-
-                                       float rx = mRenderer.mQuat2.get1();
-                                       float ry = mRenderer.mQuat2.get2();
-                                       float rz = mRenderer.mQuat2.get3();
-                                       float rw = mRenderer.mQuat2.get4();
-
-                                       // 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);
-
+         case MotionEvent.ACTION_MOVE: if( mDragging ) mRenderer.mQuatCurrent.set(quatFromDrag(mX-x,mY-y));
+                                       break;
+         case MotionEvent.ACTION_UP  : mDragging = false;
+                                       mRenderer.mQuatAccumulated.set(quatMultiply(mRenderer.mQuatCurrent, mRenderer.mQuatAccumulated));
+                                       mRenderer.mQuatCurrent.set(0f, 0f, 0f, 1f);
                                        break;
          }
 
       return true;
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return quat1*quat2
+
+    private Static4D quatMultiply( Static4D quat1, Static4D quat2 )
+      {
+      float qx = quat1.get1();
+      float qy = quat1.get2();
+      float qz = quat1.get3();
+      float qw = quat1.get4();
+
+      float rx = quat2.get1();
+      float ry = quat2.get2();
+      float rz = quat2.get3();
+      float rw = quat2.get4();
+
+      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;
+
+      return new Static4D(tx,ty,tz,tw);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private Static4D quatFromDrag(float dragX, float dragY)
+      {
+      float axisX = dragY;  // inverted X and Y - rotation axis is
+      float axisY = dragX;  // perpendicular to (dragX,dragY)
+      float axisZ = 0;
+      float axisL = (float)Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
+
+      if( axisL>0 )
+        {
+        axisX /= axisL;
+        axisY /= axisL;
+        axisZ /= axisL;
+
+        float cosA = (float)Math.cos(axisL*Math.PI/mRenderer.mScreenMin);
+        float sinA = (float)Math.sqrt(1-cosA*cosA);
+
+        return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
+        }
+
+      return new Static4D(0f, 0f, 0f, 1f);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private int returnFrontFace()
+      {
+      Static4D rotated = rotateVector(new Static4D(0,0,1,0));
+
+      float rotatedX = rotated.get1();
+      float rotatedY = rotated.get2();
+      float rotatedZ = rotated.get3();
+
+      float absX = rotatedX>0 ? rotatedX : -rotatedX;
+      float absY = rotatedY>0 ? rotatedY : -rotatedY;
+      float absZ = rotatedZ>0 ? rotatedZ : -rotatedZ;
+
+      if( absX>absY && absX>absZ ) return rotatedX>0 ? RIGHT:LEFT;
+      if( absY>absX && absY>absZ ) return rotatedY>0 ? TOP:BOTTOM;
+      if( absZ>absX && absZ>absY ) return rotatedZ>0 ? FRONT:BACK;
+
+      return ERROR;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private boolean faceIsVisible(int face)
+      {
+      float cameraDistance = mRenderer.returnCameraDistance();
+      float cubeHalfSize   = mRenderer.returnCubeSize()*0.5f;
+
+      Static4D rotated = rotateVector(new Static4D(0,0,cameraDistance,0));
+
+      switch(face)
+        {
+        case FRONT : return rotated.get3() >  cubeHalfSize;
+        case BACK  : return rotated.get3() < -cubeHalfSize;
+        case LEFT  : return rotated.get1() < -cubeHalfSize;
+        case RIGHT : return rotated.get1() >  cubeHalfSize;
+        case TOP   : return rotated.get2() >  cubeHalfSize;
+        case BOTTOM: return rotated.get2() < -cubeHalfSize;
+        }
+
+      return false;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// rotate 'vector' by quaternion q=mQuatAccumulated^(-1)  ( i.e. return (q^-1)*vector*q )
+
+    private Static4D rotateVector(Static4D vector)
+      {
+      float qx = mRenderer.mQuatAccumulated.get1();
+      float qy = mRenderer.mQuatAccumulated.get2();
+      float qz = mRenderer.mQuatAccumulated.get3();
+      float qw = mRenderer.mQuatAccumulated.get4();
+
+      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
+      Static4D tmp = quatMultiply(quatInverted,vector);
+
+      return quatMultiply(tmp,mRenderer.mQuatAccumulated);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private int faceTouched(int xTouch, int yTouch)
+      {
+      float cameraDistance = mRenderer.returnCameraDistance();
+      float halfScrWidth   = mRenderer.getScreenWidth()*0.5f;
+      float halfScrHeight  = mRenderer.getScreenHeight()*0.5f;
+      float cubeHalfSize   = mRenderer.returnCubeSize()*0.5f;
+
+      Static4D cameraPoint = new Static4D(                  0,                    0, cameraDistance, 0);
+      Static4D touchPoint  = new Static4D(xTouch-halfScrWidth, halfScrHeight-yTouch,              0, 0);
+
+      Static4D rotatedCamera    = rotateVector(cameraPoint);
+      Static4D rotatedTouchPoint= rotateVector(touchPoint);
+
+      float camX = rotatedCamera.get1();
+      float camY = rotatedCamera.get2();
+      float camZ = rotatedCamera.get3();
+
+      float poiX = rotatedTouchPoint.get1();
+      float poiY = rotatedTouchPoint.get2();
+      float poiZ = rotatedTouchPoint.get3();
+
+      if( faceIsVisible(FRONT) )
+        {
+        if( poiZ!= camZ )
+          {
+          float A = (cubeHalfSize-camZ)/(poiZ-camZ);
+          float X = (poiX-camX)*A + camX;
+          float Y = (poiY-camY)*A + camY;
+          float qX= (X+cubeHalfSize) / (2*cubeHalfSize);
+          float qY= (Y+cubeHalfSize) / (2*cubeHalfSize);
+
+          if( qX<=1 && qX>=0 && qY<=1 && qY>=0 )
+            {
+            mTouchedCol = (int)(qX*RubikRenderer.NUM_CUBES);
+            mTouchedRow = (int)(qY*RubikRenderer.NUM_CUBES);
+            mTouchedSli = RubikRenderer.NUM_CUBES - 1;
+            return FRONT;
+            }
+          }
+        }
+      if( faceIsVisible(BACK) )
+        {
+        if( poiZ!= camZ )
+          {
+          float A = (-cubeHalfSize-camZ)/(poiZ-camZ);
+          float X = (poiX-camX)*A + camX;
+          float Y = (poiY-camY)*A + camY;
+          float qX= (X+cubeHalfSize) / (2*cubeHalfSize);
+          float qY= (Y+cubeHalfSize) / (2*cubeHalfSize);
+
+          if( qX<=1 && qX>=0 && qY<=1 && qY>=0 )
+            {
+            mTouchedCol = (int)( qX*RubikRenderer.NUM_CUBES);
+            mTouchedRow = (int)( qY*RubikRenderer.NUM_CUBES);
+            mTouchedSli = 0;
+            return BACK;
+            }
+          }
+        }
+      if( faceIsVisible(LEFT) )
+        {
+        if( poiX!= camX )
+          {
+          float A = (-cubeHalfSize-camX)/(poiX-camX);
+          float Y = (poiY-camY)*A + camY;
+          float Z = (poiZ-camZ)*A + camZ;
+          float qY= (Y+cubeHalfSize) / (2*cubeHalfSize);
+          float qZ= (Z+cubeHalfSize) / (2*cubeHalfSize);
+
+          if( qZ<=1 && qZ>=0 && qY<=1 && qY>=0 )
+            {
+            mTouchedCol = 0;
+            mTouchedRow = (int)( qY*RubikRenderer.NUM_CUBES);
+            mTouchedSli = (int)( qZ*RubikRenderer.NUM_CUBES);
+            return LEFT;
+            }
+          }
+        }
+      if( faceIsVisible(RIGHT) )
+        {
+        if( poiX!= camX )
+          {
+          float A = (cubeHalfSize-camX)/(poiX-camX);
+          float Y = (poiY-camY)*A + camY;
+          float Z = (poiZ-camZ)*A + camZ;
+          float qY= (Y+cubeHalfSize) / (2*cubeHalfSize);
+          float qZ= (Z+cubeHalfSize) / (2*cubeHalfSize);
+
+          if( qZ<=1 && qZ>=0 && qY<=1 && qY>=0 )
+            {
+            mTouchedCol = RubikRenderer.NUM_CUBES -1;
+            mTouchedRow = (int)( qY*RubikRenderer.NUM_CUBES);
+            mTouchedSli = (int)( qZ*RubikRenderer.NUM_CUBES);
+            return RIGHT;
+            }
+          }
+        }
+      if( faceIsVisible(TOP) )
+        {
+        if( poiY!= camY )
+          {
+          float A = (cubeHalfSize-camY)/(poiY-camY);
+          float X = (poiX-camX)*A + camX;
+          float Z = (poiZ-camZ)*A + camZ;
+          float qX= (X+cubeHalfSize) / (2*cubeHalfSize);
+          float qZ= (Z+cubeHalfSize) / (2*cubeHalfSize);
+
+          if( qZ<=1 && qZ>=0 && qX<=1 && qX>=0 )
+            {
+            mTouchedCol = (int)( qX*RubikRenderer.NUM_CUBES);
+            mTouchedRow = RubikRenderer.NUM_CUBES -1;
+            mTouchedSli = (int)( qZ*RubikRenderer.NUM_CUBES);
+            return TOP;
+            }
+          }
+        }
+      if( faceIsVisible(BOTTOM) )
+        {
+        if( poiY!= camY )
+          {
+          float A = (-cubeHalfSize-camY)/(poiY-camY);
+          float X = (poiX-camX)*A + camX;
+          float Z = (poiZ-camZ)*A + camZ;
+          float qX= (X+cubeHalfSize) / (2*cubeHalfSize);
+          float qZ= (Z+cubeHalfSize) / (2*cubeHalfSize);
+
+          if( qZ<=1 && qZ>=0 && qX<=1 && qX>=0 )
+            {
+            mTouchedCol = (int)( qX*RubikRenderer.NUM_CUBES);
+            mTouchedRow = 0;
+            mTouchedSli = (int)( qZ*RubikRenderer.NUM_CUBES);
+            return BOTTOM;
+            }
+          }
+        }
+
+      mTouchedRow = -1;
+      mTouchedCol = -1;
+      mTouchedSli = -1;
+
+      return ERROR;
+      }
 }
 
