commit 70b7654929d5cacb7b5052d8831f329cd4286262
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri Feb 21 14:57:57 2020 +0000

    More work on Cubit.

diff --git a/src/main/java/org/distorted/object/Cubit.java b/src/main/java/org/distorted/object/Cubit.java
new file mode 100644
index 00000000..d8383858
--- /dev/null
+++ b/src/main/java/org/distorted/object/Cubit.java
@@ -0,0 +1,308 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.object;
+
+import android.content.SharedPreferences;
+
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.effect.MatrixEffectQuaternion;
+import org.distorted.library.effect.MatrixEffectRotate;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedNode;
+import org.distorted.library.mesh.MeshCubes;
+import org.distorted.library.message.EffectListener;
+import org.distorted.library.type.Dynamic1D;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.magic.RubikSurfaceView;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class Cubit
+  {
+  private static final Static3D matrCenter = new Static3D(0,0,0);
+
+  private final Static3D mOrigPosition;
+
+  private RubikObject mParent;
+  private MeshCubes mCube;
+  private Static3D mRotationAxis;
+  private MatrixEffectRotate mRotateEffect;
+
+  Dynamic1D mRotationAngle;
+  Static3D mCurrentPosition;
+  DistortedNode mNode;
+  DistortedEffects mEffect;
+  Static4D mQuatScramble;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Because of quatMultiplication, errors can accumulate - so to avoid this, we
+// correct the value of the 'scramble' quat to what it should be - one of the legal quats from the
+// list LEGAL_QUATS.
+//
+// We also have to remember that the group of unit quaternions is a double-cover of rotations
+// in 3D ( q represents the same rotation as -q ) - so invert if needed.
+
+  private void normalizeScrambleQuat(Static4D quat)
+    {
+    float x = quat.get0();
+    float y = quat.get1();
+    float z = quat.get2();
+    float w = quat.get3();
+    float diff;
+
+    for(float legal: mParent.LEGAL_QUATS)
+      {
+      diff = x-legal;
+      if( diff*diff<0.01f ) x = legal;
+      diff = y-legal;
+      if( diff*diff<0.01f ) y = legal;
+      diff = z-legal;
+      if( diff*diff<0.01f ) z = legal;
+      diff = w-legal;
+      if( diff*diff<0.01f ) w = legal;
+      }
+
+    if( w<0 )
+      {
+      w = -w;
+      z = -z;
+      y = -y;
+      x = -x;
+      }
+    else if( w==0 )
+      {
+      if( z<0 )
+        {
+        z = -z;
+        y = -y;
+        x = -x;
+        }
+      else if( z==0 )
+        {
+        if( y<0 )
+          {
+          y = -y;
+          x = -x;
+          }
+        else if( y==0 )
+          {
+          if( x<0 )
+            {
+            x = -x;
+            }
+          }
+        }
+      }
+
+    quat.set(x,y,z,w);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeNearestAngle(float angle)
+    {
+    final int NEAREST = 90;
+
+    int tmp = (int)((angle+NEAREST/2)/NEAREST);
+    if( angle< -(NEAREST*0.5) ) tmp-=1;
+
+    return NEAREST*tmp;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void modifyCurrentPosition(Static3D currentPosition, Static4D quat)
+    {
+    float diff = 0.5f*(mParent.mSize-1);
+    float cubitCenterX = currentPosition.get0() - diff;
+    float cubitCenterY = currentPosition.get1() - diff;
+    float cubitCenterZ = currentPosition.get2() - diff;
+
+    Static4D cubitCenter =  new Static4D(cubitCenterX, cubitCenterY, cubitCenterZ, 0);
+    Static4D rotatedCenter = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat);
+
+    float rotatedX = rotatedCenter.get0() + diff;
+    float rotatedY = rotatedCenter.get1() + diff;
+    float rotatedZ = rotatedCenter.get2() + diff;
+
+    int roundedX = (int)(rotatedX+0.1f);
+    int roundedY = (int)(rotatedY+0.1f);
+    int roundedZ = (int)(rotatedZ+0.1f);
+
+    currentPosition.set(roundedX, roundedY, roundedZ);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Cubit(RubikObject parent, MeshCubes mesh, Static3D position)
+    {
+    float x = position.get0();
+    float y = position.get1();
+    float z = position.get2();
+
+    float nc = mParent.mSize*0.5f;
+    int TS = RubikObject.TEXTURE_SIZE;
+    Static3D vector = new Static3D(TS*(x-nc), TS*(y-nc), TS*(z-nc));
+
+    mParent          = parent;
+    mCube            = mesh;
+    mOrigPosition    = new Static3D(x,y,z);
+    mQuatScramble    = new Static4D(0,0,0,1);
+    mRotationAngle   = new Dynamic1D();
+    mRotationAxis    = new Static3D(1,0,0);
+    mCurrentPosition = position;
+    mRotateEffect    = new MatrixEffectRotate(mRotationAngle, mRotationAxis, matrCenter);
+
+    mEffect = new DistortedEffects();
+    mEffect.apply(mParent.mSinkEffect);
+    mEffect.apply( new MatrixEffectMove(vector) );
+    mEffect.apply( new MatrixEffectQuaternion(mQuatScramble, matrCenter));
+    mEffect.apply(mRotateEffect);
+    mEffect.apply(mParent.mQuatAEffect);
+    mEffect.apply(mParent.mQuatCEffect);
+    mEffect.apply(mParent.mScaleEffect);
+    mEffect.apply(mParent.mMoveEffect);
+
+    mNode = new DistortedNode(mParent.mTexture,mEffect,mCube);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void savePreferences(SharedPreferences.Editor editor)
+    {
+    String number = mOrigPosition.get0()+"_"+mOrigPosition.get1()+"_"+mOrigPosition.get2();
+
+    editor.putFloat("qx_"+number, mQuatScramble.get0());
+    editor.putFloat("qy_"+number, mQuatScramble.get1());
+    editor.putFloat("qz_"+number, mQuatScramble.get2());
+    editor.putFloat("qw_"+number, mQuatScramble.get3());
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void restorePreferences(SharedPreferences preferences)
+    {
+    String number = mOrigPosition.get0()+"_"+mOrigPosition.get1()+"_"+mOrigPosition.get2();
+
+    float qx = preferences.getFloat("qx_"+number, 0.0f);
+    float qy = preferences.getFloat("qy_"+number, 0.0f);
+    float qz = preferences.getFloat("qz_"+number, 0.0f);
+    float qw = preferences.getFloat("qw_"+number, 1.0f);
+
+    mQuatScramble.set(qx,qy,qz,qw);
+    modifyCurrentPosition( mCurrentPosition, mQuatScramble);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  long finishRotationNow(EffectListener listener)
+    {
+    int pointNum = mRotationAngle.getNumPoints();
+
+    if( pointNum>=1 )
+      {
+      float startingAngle = mRotationAngle.getPoint(pointNum-1).get0();
+      int nearestAngleInDegrees = computeNearestAngle(startingAngle);
+      mParent.mRotationAngleStatic.set0(startingAngle);
+      mParent.mRotationAngleFinal.set0(nearestAngleInDegrees);
+      mParent.mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-startingAngle)*0.2f );
+      return setUpCallback(listener);
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Static4D returnRotationQuat(float qx,float qy,float qz)
+    {
+    int pointNum = mRotationAngle.getNumPoints();
+
+    if( pointNum>=1 )
+      {
+      float startingAngle = mRotationAngle.getPoint(pointNum-1).get0();
+      int nearestAngleInDegrees = computeNearestAngle(startingAngle);
+      double nearestAngleInRadians = nearestAngleInDegrees*Math.PI/180;
+      float sinA =-(float)Math.sin(nearestAngleInRadians*0.5);
+      float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
+      return new Static4D(qx*sinA, qy*sinA, qz*sinA, cosA);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeRotationNow(Static4D quat)
+    {
+    mRotationAngle.removeAll();
+    mQuatScramble.set(RubikSurfaceView.quatMultiply(quat,mQuatScramble));
+    normalizeScrambleQuat( mQuatScramble );
+    modifyCurrentPosition( mCurrentPosition,quat);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// all DistortedTextures, DistortedNodes, DistortedFramebuffers, DistortedScreens and all types of
+// Meshes HAVE TO be markedForDeletion when they are no longer needed- otherwise we have a major
+// memory leak.
+
+  void releaseResources()
+    {
+    mCube.markForDeletion();
+    mNode.markForDeletion();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void solve()
+    {
+    mQuatScramble.set(0,0,0,1);
+    mCurrentPosition.set(mOrigPosition);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void beginNewRotation(Static3D axis)
+    {
+    mRotationAxis.set(axis);
+    mRotationAngle.add(mParent.mRotationAngleStatic);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void addNewRotation(Static3D axis, long durationMillis, int angle)
+    {
+    mRotationAxis.set(axis);
+    mRotationAngle.setDuration(durationMillis);
+    mRotationAngle.resetToBeginning();
+    mRotationAngle.add(new Static1D(0));
+    mRotationAngle.add(new Static1D(angle));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  long setUpCallback(EffectListener listener)
+    {
+    mRotateEffect.notifyWhenFinished(listener);
+    return mRotateEffect.getID();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/object/RubikCube.java b/src/main/java/org/distorted/object/RubikCube.java
index 78e9c9cf..f1347e68 100644
--- a/src/main/java/org/distorted/object/RubikCube.java
+++ b/src/main/java/org/distorted/object/RubikCube.java
@@ -25,20 +25,13 @@ import android.graphics.Canvas;
 import android.graphics.Paint;
 
 import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.effect.MatrixEffectQuaternion;
-import org.distorted.library.effect.MatrixEffectRotate;
 import org.distorted.library.main.DistortedEffects;
-import org.distorted.library.main.DistortedNode;
 import org.distorted.library.main.DistortedTexture;
 import org.distorted.library.mesh.MeshCubes;
 import org.distorted.library.mesh.MeshRectangles;
 import org.distorted.library.message.EffectListener;
-import org.distorted.library.type.Dynamic1D;
-import org.distorted.library.type.Static1D;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
-import org.distorted.magic.RubikSurfaceView;
 
 import static org.distorted.object.RubikObjectList.VECTX;
 import static org.distorted.object.RubikObjectList.VECTY;
@@ -51,180 +44,36 @@ class RubikCube extends RubikObject
     private static final Static3D VectX = new Static3D(1,0,0);
     private static final Static3D VectY = new Static3D(0,1,0);
     private static final Static3D VectZ = new Static3D(0,0,1);
+    private static final Static4D mapFront, mapBack, mapLeft, mapRight, mapTop, mapBottom, mapBlack;
 
-    private static final Static3D matrCenter = new Static3D(0,0,0);
-
-    /////////////////////////////////////////////////////////////////////////////////
+    private Cubit[][][] mCubits;
 
-    private class Cubit
+    static
       {
-      private final Static3D mOrigPosition;
-
-      DistortedNode mNode;
-      MeshCubes mCube;
-      DistortedEffects mEffect;
-      Static4D mQuatScramble;
-      Static3D mRotationAxis;
-      Dynamic1D mRotationAngle;
-      Static3D mCurrentPosition;
-
-      MatrixEffectRotate mRotateEffect;
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      Cubit(MeshCubes mesh,Static3D vector, Static3D position)
-        {
-        mOrigPosition = new Static3D( position.get0(), position.get1(), position.get2() );
-
-        mCube = mesh;
-        mQuatScramble    = new Static4D(0,0,0,1);
-        mRotationAngle   = new Dynamic1D();
-        mRotationAxis    = new Static3D(1,0,0);
-        mCurrentPosition = position;
-        mRotateEffect    = new MatrixEffectRotate(mRotationAngle, mRotationAxis, matrCenter);
-
-        mEffect = new DistortedEffects();
-        mEffect.apply(mSinkEffect);
-        mEffect.apply( new MatrixEffectMove(vector) );
-        mEffect.apply( new MatrixEffectQuaternion(mQuatScramble, matrCenter));
-        mEffect.apply(mRotateEffect);
-        mEffect.apply(mQuatAEffect);
-        mEffect.apply(mQuatCEffect);
-        mEffect.apply(mScaleEffect);
-        mEffect.apply(mMoveEffect);
-
-        mNode = new DistortedNode(mTexture,mEffect,mCube);
-        }
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      void savePreferences(SharedPreferences.Editor editor)
-        {
-        String number = mOrigPosition.get0()+"_"+mOrigPosition.get1()+"_"+mOrigPosition.get2();
-
-        editor.putFloat("qx_"+number, mQuatScramble.get0());
-        editor.putFloat("qy_"+number, mQuatScramble.get1());
-        editor.putFloat("qz_"+number, mQuatScramble.get2());
-        editor.putFloat("qw_"+number, mQuatScramble.get3());
-        }
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      void restorePreferences(SharedPreferences preferences)
-        {
-        String number = mOrigPosition.get0()+"_"+mOrigPosition.get1()+"_"+mOrigPosition.get2();
-
-        float qx = preferences.getFloat("qx_"+number, 0.0f);
-        float qy = preferences.getFloat("qy_"+number, 0.0f);
-        float qz = preferences.getFloat("qz_"+number, 0.0f);
-        float qw = preferences.getFloat("qw_"+number, 1.0f);
-
-        mQuatScramble.set(qx,qy,qz,qw);
-        modifyCurrentPosition( mCurrentPosition, mQuatScramble);
-        }
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      long finishRotationNow(EffectListener listener)
-        {
-        int pointNum = mRotationAngle.getNumPoints();
-
-        if( pointNum>=1 )
-          {
-          float startingAngle = mRotationAngle.getPoint(pointNum-1).get0();
-          int nearestAngleInDegrees = computeNearestAngle(startingAngle);
-          mRotationAngleStatic.set0(startingAngle);
-          mRotationAngleFinal.set0(nearestAngleInDegrees);
-          mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-startingAngle)*0.2f );
-          return setUpCallback(listener);
-          }
-        else
-          {
-          return 0;
-          }
-        }
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      Static4D returnRotationQuat(float qx,float qy,float qz)
-        {
-        int pointNum = mRotationAngle.getNumPoints();
-
-        if( pointNum>=1 )
-          {
-          float startingAngle = mRotationAngle.getPoint(pointNum-1).get0();
-          int nearestAngleInDegrees = computeNearestAngle(startingAngle);
-          double nearestAngleInRadians = nearestAngleInDegrees*Math.PI/180;
-          float sinA =-(float)Math.sin(nearestAngleInRadians*0.5);
-          float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
-          return new Static4D(qx*sinA, qy*sinA, qz*sinA, cosA);
-          }
-        else
-          {
-          return null;
-          }
-        }
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      void removeRotationNow(Static4D quat)
-        {
-        mRotationAngle.removeAll();
-        mQuatScramble.set(RubikSurfaceView.quatMultiply(quat,mQuatScramble));
-        normalizeScrambleQuat( mQuatScramble );
-        modifyCurrentPosition( mCurrentPosition,quat);
-        }
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      void releaseResources()
-        {
-        mCube.markForDeletion();
-        mNode.markForDeletion();
-        }
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      void solve()
-        {
-        mQuatScramble.set(0,0,0,1);
-        mCurrentPosition.set(mOrigPosition);
-        }
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      void beginNewRotation(Static3D axis)
-        {
-        mRotationAxis.set(axis);
-        mRotationAngle.add(mRotationAngleStatic);
-        }
-
-    /////////////////////////////////////////////////////////////////////////////////
-
-      void addNewRotation(Static3D axis, long durationMillis, int angle)
-        {
-        mRotationAxis.set(axis);
-        mRotationAngle.setDuration(durationMillis);
-        mRotationAngle.resetToBeginning();
-        mRotationAngle.add(new Static1D(0));
-        mRotationAngle.add(new Static1D(angle));
-        }
+      // 3x2 bitmap = 6 squares:
+      //
+      // RED     GREEN   BLUE
+      // YELLOW  WHITE   BROWN
 
+      final float ze = 0.0f;
+      final float ot = 1.0f/3.0f;
+      final float tt = 2.0f/3.0f;
+      final float oh = 1.0f/2.0f;
+      final float of = 1.0f/40.0f;
 
-    /////////////////////////////////////////////////////////////////////////////////
+      mapFront = new Static4D(ze,oh, ze+ot,oh+oh);
+      mapBack  = new Static4D(tt,ze, tt+ot,ze+oh);
+      mapLeft  = new Static4D(ot,ze, ot+ot,ze+oh);
+      mapRight = new Static4D(ze,ze, ze+ot,ze+oh);
+      mapTop   = new Static4D(tt,oh, tt+ot,oh+oh);
+      mapBottom= new Static4D(ot,oh, ot+ot,oh+oh);
 
-      long setUpCallback(EffectListener listener)
-        {
-        mRotateEffect.notifyWhenFinished(listener);
-        return mRotateEffect.getID();
-        }
+      mapBlack = new Static4D(ze,ze, ze+of,ze+of);
       }
 
-    private Cubit[][][] mCubits;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// All legal rotation quats must have all four of their components equal to either
-// 0, 1, -1, 0.5, -0.5 or +-sqrt(2)/2.
+// All legal rotation quats of a RubikCube of any size must have all four of their components
+// equal to either 0, 1, -1, 0.5, -0.5 or +-sqrt(2)/2.
 
     float[] getLegalQuats()
       {
@@ -252,51 +101,18 @@ class RubikCube extends RubikObject
       {
       super(size,quatCur,quatAcc,texture,mesh,effects);
 
-      mRotAxis = VECTX;
       mTexture = new DistortedTexture(TEXTURE_SIZE,TEXTURE_SIZE);
       mCubits  = new Cubit[mSize][mSize][mSize];
 
-      // 3x2 bitmap = 6 squares:
-      //
-      // RED     GREEN   BLUE
-      // YELLOW  WHITE   BROWN
-
-      final float ze = 0.0f;
-      final float ot = 1.0f/3.0f;
-      final float tt = 2.0f/3.0f;
-      final float oh = 1.0f/2.0f;
-      final float of = 1.0f/40.0f;
-
-      final Static4D mapFront = new Static4D(ze,oh, ze+ot,oh+oh);
-      final Static4D mapBack  = new Static4D(tt,ze, tt+ot,ze+oh);
-      final Static4D mapLeft  = new Static4D(ot,ze, ot+ot,ze+oh);
-      final Static4D mapRight = new Static4D(ze,ze, ze+ot,ze+oh);
-      final Static4D mapTop   = new Static4D(tt,oh, tt+ot,oh+oh);
-      final Static4D mapBottom= new Static4D(ot,oh, ot+ot,oh+oh);
-
-      final Static4D mapBlack = new Static4D(ze,ze, ze+of,ze+of);
-
-      Static4D tmpFront, tmpBack, tmpLeft, tmpRight, tmpTop, tmpBottom;
-      float nc = 0.5f*mSize;
       int vertices = (int)(24.0f/mSize + 2.0f);
 
       for(int x = 0; x< mSize; x++)
         for(int y = 0; y< mSize; y++)
           for(int z = 0; z< mSize; z++)
             {
-            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 ) // only the external walls
+            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
               {
-              tmpLeft  = (x==       0 ? mapLeft  :mapBlack);
-              tmpRight = (x== mSize-1 ? mapRight :mapBlack);
-              tmpFront = (z== mSize-1 ? mapFront :mapBlack);
-              tmpBack  = (z==       0 ? mapBack  :mapBlack);
-              tmpTop   = (y== mSize-1 ? mapTop   :mapBlack);
-              tmpBottom= (y==       0 ? mapBottom:mapBlack);
-
-              mCubits[x][y][z] = new Cubit(new MeshCubes(vertices,vertices,vertices, tmpFront, tmpBack, tmpLeft, tmpRight, tmpTop, tmpBottom),
-                                           new Static3D( TEXTURE_SIZE*(x-nc), TEXTURE_SIZE*(y-nc), TEXTURE_SIZE*(z-nc) ),
-                                           new Static3D(x,y,z) );
-
+              mCubits[x][y][z] = new Cubit( this,createMesh(vertices,x,y,z),new Static3D(x,y,z) );
               attach(mCubits[x][y][z].mNode);
               }
             }
@@ -359,9 +175,6 @@ class RubikCube extends RubikObject
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// all DistortedTextures, DistortedNodes, DistortedFramebuffers, DistortedScreens and all types of
-// Meshes HAVE TO be markedForDeletion when they are no longer needed- otherwise we have a major
-// memory leak.
 
    public void releaseResources()
      {
@@ -378,50 +191,6 @@ class RubikCube extends RubikObject
            }
      }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void createTexture()
-     {
-     Bitmap bitmap;
-
-     final int S = 128;
-     final int W = 3*S;
-     final int H = 2*S;
-     final int R = S/10;
-     final int M = S/20;
-
-     Paint paint = new Paint();
-     bitmap = Bitmap.createBitmap(W,H, Bitmap.Config.ARGB_8888);
-     Canvas canvas = new Canvas(bitmap);
-
-     paint.setAntiAlias(true);
-     paint.setTextAlign(Paint.Align.CENTER);
-     paint.setStyle(Paint.Style.FILL);
-
-     // 3x2 bitmap = 6 squares:
-     //
-     // RED     GREEN   BLUE
-     // YELLOW  WHITE   BROWN
-
-     paint.setColor(0xff000000);                                  // BLACK BACKGROUND
-     canvas.drawRect(0, 0, W, H, paint);                          //
-
-     paint.setColor(0xffff0000);                                  // RED
-     canvas.drawRoundRect(    M,   M,   S-M,   S-M, R, R, paint); //
-     paint.setColor(0xff00ff00);                                  // GREEN
-     canvas.drawRoundRect(  S+M,   M, 2*S-M,   S-M, R, R, paint); //
-     paint.setColor(0xff0000ff);                                  // BLUE
-     canvas.drawRoundRect(2*S+M,   M, 3*S-M,   S-M, R, R, paint); //
-     paint.setColor(0xffffff00);                                  // YELLOW
-     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
-     canvas.drawRoundRect(2*S+M, S+M, 3*S-M, 2*S-M, R, R, paint); //
-
-     mTexture.setTexture(bitmap);
-     }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
    public void apply(Effect effect, int position)
@@ -598,4 +367,62 @@ class RubikCube extends RubikObject
 
       mRotationAngleStatic.set0(0);
       }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void createTexture()
+     {
+     Bitmap bitmap;
+
+     final int S = 128;
+     final int W = 3*S;
+     final int H = 2*S;
+     final int R = S/10;
+     final int M = S/20;
+
+     Paint paint = new Paint();
+     bitmap = Bitmap.createBitmap(W,H, Bitmap.Config.ARGB_8888);
+     Canvas canvas = new Canvas(bitmap);
+
+     paint.setAntiAlias(true);
+     paint.setTextAlign(Paint.Align.CENTER);
+     paint.setStyle(Paint.Style.FILL);
+
+     // 3x2 bitmap = 6 squares:
+     //
+     // RED     GREEN   BLUE
+     // YELLOW  WHITE   BROWN
+
+     paint.setColor(0xff000000);                                  // BLACK BACKGROUND
+     canvas.drawRect(0, 0, W, H, paint);                          //
+
+     paint.setColor(0xffff0000);                                  // RED
+     canvas.drawRoundRect(    M,   M,   S-M,   S-M, R, R, paint); //
+     paint.setColor(0xff00ff00);                                  // GREEN
+     canvas.drawRoundRect(  S+M,   M, 2*S-M,   S-M, R, R, paint); //
+     paint.setColor(0xff0000ff);                                  // BLUE
+     canvas.drawRoundRect(2*S+M,   M, 3*S-M,   S-M, R, R, paint); //
+     paint.setColor(0xffffff00);                                  // YELLOW
+     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
+     canvas.drawRoundRect(2*S+M, S+M, 3*S-M, 2*S-M, R, R, paint); //
+
+     mTexture.setTexture(bitmap);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private MeshCubes createMesh(int vertices,int x, int y, int z)
+     {
+     Static4D tmpLeft  = (x==       0 ? mapLeft  :mapBlack);
+     Static4D tmpRight = (x== mSize-1 ? mapRight :mapBlack);
+     Static4D tmpFront = (z== mSize-1 ? mapFront :mapBlack);
+     Static4D tmpBack  = (z==       0 ? mapBack  :mapBlack);
+     Static4D tmpTop   = (y== mSize-1 ? mapTop   :mapBlack);
+     Static4D tmpBottom= (y==       0 ? mapBottom:mapBlack);
+
+     return new MeshCubes(vertices,vertices,vertices, tmpFront, tmpBack, tmpLeft, tmpRight, tmpTop, tmpBottom);
+     }
 }
diff --git a/src/main/java/org/distorted/object/RubikObject.java b/src/main/java/org/distorted/object/RubikObject.java
index dec25916..6bbc8285 100644
--- a/src/main/java/org/distorted/object/RubikObject.java
+++ b/src/main/java/org/distorted/object/RubikObject.java
@@ -35,7 +35,6 @@ import org.distorted.library.type.Dynamic1D;
 import org.distorted.library.type.Static1D;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
-import org.distorted.magic.RubikSurfaceView;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -45,7 +44,7 @@ public abstract class RubikObject extends DistortedNode
   static final int TEXTURE_SIZE = 100;
 
   private static final int POST_ROTATION_MILLISEC = 500;
-  private final float[] LEGAL_QUATS;
+  final float[] LEGAL_QUATS;
 
   private Static3D mMove, mScale, mNodeMove, mNodeScale;
   private Static4D mQuatAccumulated;
@@ -100,81 +99,6 @@ public abstract class RubikObject extends DistortedNode
     effects.apply(nodeMoveEffect);
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Because of quatMultiplication, errors can accumulate - so to avoid this, we
-// correct the value of the 'scramble' quat to what it should be - one of the legal quats from the
-// list LEGAL_QUATS.
-//
-// We also have to remember that the group of unit quaternions is a double-cover of rotations
-// in 3D ( q represents the same rotation as -q ) - so invert if needed.
-
-    void normalizeScrambleQuat(Static4D quat)
-      {
-      float x = quat.get0();
-      float y = quat.get1();
-      float z = quat.get2();
-      float w = quat.get3();
-      float diff;
-
-      for(float legal: LEGAL_QUATS)
-        {
-        diff = x-legal;
-        if( diff*diff<0.01f ) x = legal;
-        diff = y-legal;
-        if( diff*diff<0.01f ) y = legal;
-        diff = z-legal;
-        if( diff*diff<0.01f ) z = legal;
-        diff = w-legal;
-        if( diff*diff<0.01f ) w = legal;
-        }
-
-      if( w<0 )
-        {
-        w = -w;
-        z = -z;
-        y = -y;
-        x = -x;
-        }
-      else if( w==0 )
-        {
-        if( z<0 )
-          {
-          z = -z;
-          y = -y;
-          x = -x;
-          }
-        else if( z==0 )
-          {
-          if( y<0 )
-            {
-            y = -y;
-            x = -x;
-            }
-          else if( y==0 )
-            {
-            if( x<0 )
-              {
-              x = -x;
-              }
-            }
-          }
-        }
-
-      quat.set(x,y,z,w);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int computeNearestAngle(float angle)
-    {
-    final int NEAREST = 90;
-
-    int tmp = (int)((angle+NEAREST/2)/NEAREST);
-    if( angle< -(NEAREST*0.5) ) tmp-=1;
-
-    return NEAREST*tmp;
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void resetRotationAngle(Dynamic1D rotationAngle)
@@ -187,29 +111,6 @@ public abstract class RubikObject extends DistortedNode
     rotationAngle.add(mRotationAngleFinal);
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void modifyCurrentPosition(Static3D currentPosition, Static4D quat)
-    {
-    float diff = 0.5f*(mSize-1);
-    float cubitCenterX = currentPosition.get0() - diff;
-    float cubitCenterY = currentPosition.get1() - diff;
-    float cubitCenterZ = currentPosition.get2() - diff;
-
-    Static4D cubitCenter =  new Static4D(cubitCenterX, cubitCenterY, cubitCenterZ, 0);
-    Static4D rotatedCenter = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat);
-
-    float rotatedX = rotatedCenter.get0() + diff;
-    float rotatedY = rotatedCenter.get1() + diff;
-    float rotatedZ = rotatedCenter.get2() + diff;
-
-    int roundedX = (int)(rotatedX+0.1f);
-    int roundedY = (int)(rotatedY+0.1f);
-    int roundedZ = (int)(rotatedZ+0.1f);
-
-    currentPosition.set(roundedX, roundedY, roundedZ);
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   private float getSinkStrength()
