commit a10ada2a3e8cdb685258c48f01425ad03bdf1bc6
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri Feb 21 15:35:19 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
index d8383858..d1b83547 100644
--- a/src/main/java/org/distorted/object/Cubit.java
+++ b/src/main/java/org/distorted/object/Cubit.java
@@ -159,7 +159,7 @@ class Cubit
     float y = position.get1();
     float z = position.get2();
 
-    float nc = mParent.mSize*0.5f;
+    float nc = parent.mSize*0.5f;
     int TS = RubikObject.TEXTURE_SIZE;
     Static3D vector = new Static3D(TS*(x-nc), TS*(y-nc), TS*(z-nc));
 
diff --git a/src/main/java/org/distorted/object/RubikCube.java b/src/main/java/org/distorted/object/RubikCube.java
index f1347e68..cf61d3d0 100644
--- a/src/main/java/org/distorted/object/RubikCube.java
+++ b/src/main/java/org/distorted/object/RubikCube.java
@@ -19,17 +19,14 @@
 
 package org.distorted.object;
 
-import android.content.SharedPreferences;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 
-import org.distorted.library.effect.Effect;
 import org.distorted.library.main.DistortedEffects;
 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.Static3D;
 import org.distorted.library.type.Static4D;
 
@@ -41,388 +38,152 @@ import static org.distorted.object.RubikObjectList.VECTZ;
 
 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 Static4D mapFront, mapBack, mapLeft, mapRight, mapTop, mapBottom, mapBlack;
 
-    private Cubit[][][] mCubits;
+  static
+    {
+    // 3x2 bitmap = 6 squares:
+    //
+    // RED     GREEN   BLUE
+    // YELLOW  WHITE   BROWN
 
-    static
-      {
-      // 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);
-
-      mapBlack = new Static4D(ze,ze, ze+of,ze+of);
-      }
+    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;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.
+    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);
 
-    float[] getLegalQuats()
-      {
-      final float SQ2 = 0.5f*((float)Math.sqrt(2));
-      return new float[] { 0.0f , 0.5f , -0.5f , 1.0f , -1.0f , SQ2 , -SQ2 };
-      }
+    mapBlack = new Static4D(ze,ze, ze+of,ze+of);
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// i.e. size^3 - (size-2)^3 - number of cubits in the outside wall of the Cube (we dont create or
+// render the inside0
 
-    private boolean belongsToRotation( Static3D currentPosition, int vector, int row)
-      {
-      switch(vector)
-        {
-        case VECTX: return currentPosition.get0()==row;
-        case VECTY: return currentPosition.get1()==row;
-        case VECTZ: return currentPosition.get2()==row;
-        }
-
-      return false;
-      }
+  int getNumCubits(int size)
+    {
+    return 6*size*size - 12*size + 8;
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    RubikCube(int size, Static4D quatCur, Static4D quatAcc, DistortedTexture texture, MeshRectangles mesh, DistortedEffects effects)
-      {
-      super(size,quatCur,quatAcc,texture,mesh,effects);
-
-      mTexture = new DistortedTexture(TEXTURE_SIZE,TEXTURE_SIZE);
-      mCubits  = new Cubit[mSize][mSize][mSize];
+  int[][] getCubitPositions(int size)
+    {
+    int[][] tmp = new int[getNumCubits(size)][3];
 
-      int vertices = (int)(24.0f/mSize + 2.0f);
+    int currentPosition = 0;
 
-      for(int x = 0; x< mSize; x++)
-        for(int y = 0; y< mSize; y++)
-          for(int z = 0; z< mSize; z++)
+    for(int x = 0; x<size; x++)
+      for(int y = 0; y<size; y++)
+        for(int z = 0; z<size; z++)
+          {
+          if( x==0 || x==size-1 || y==0 || y==size-1 || z==0 || z==size-1 )
             {
-            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
-              {
-              mCubits[x][y][z] = new Cubit( this,createMesh(vertices,x,y,z),new Static3D(x,y,z) );
-              attach(mCubits[x][y][z].mNode);
-              }
-            }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// mSize already saved as RubikStatePlay.mButton
-
-   public void savePreferences(SharedPreferences.Editor editor)
-     {
-     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 )
-              {
-              mCubits[x][y][z].savePreferences(editor);
-              }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void restorePreferences(SharedPreferences preferences)
-     {
-     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 )
-              {
-              mCubits[x][y][z].restorePreferences(preferences);
-              }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public long finishRotationNow(EffectListener listener)
-     {
-     boolean first = true;
-     long effectID=0;
-
-     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 )
-             {
-             if( belongsToRotation(mCubits[x][y][z].mCurrentPosition,mRotAxis,mRotRow) )
-               {
-               if( first )
-                 {
-                 first = false;
-                 effectID = mCubits[x][y][z].finishRotationNow(listener);
-                 }
-
-               resetRotationAngle(mCubits[x][y][z].mRotationAngle);
-               }
-             }
-
-     return effectID;
-     }
+            tmp[currentPosition][0] = x;
+            tmp[currentPosition][1] = y;
+            tmp[currentPosition][2] = z;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void releaseResources()
-     {
-     mTexture.markForDeletion();
-
-     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 )
-             {
-             mCubits[x][y][z].releaseResources();
-             }
-           }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void apply(Effect effect, int position)
-     {
-     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 )
-             {
-             mCubits[x][y][z].mEffect.apply(effect, position);
-             }
-           }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void remove(long effectID)
-     {
-     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 )
-             {
-             mCubits[x][y][z].mEffect.abortById(effectID);
-             }
-           }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void solve()
-     {
-     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 )
-             {
-             mCubits[x][y][z].solve();
-             }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
+            currentPosition++;
+            }
+          }
 
-   public boolean isSolved()
-     {
-     Static4D q = mCubits[0][0][0].mQuatScramble;
-
-     float x = q.get0();
-     float y = q.get1();
-     float z = q.get2();
-     float w = q.get3();
-
-     for(int i = 0; i< mSize; i++)
-       for(int j = 0; j< mSize; j++)
-         for(int k = 0; k< mSize; k++)
-           {
-           if( i==0 || i==mSize-1 || j==0 || j==mSize-1 || k==0 || k==mSize-1 )
-             {
-             q = mCubits[i][j][k].mQuatScramble;
-
-             if( q.get0()!=x || q.get1()!=y || q.get2()!=z || q.get3()!=w )
-               {
-               return false;
-               }
-             }
-           }
-
-     return true;
-     }
+    return tmp;
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.
 
-   public void beginNewRotation(int vector, int row )
-     {
-     Static3D axis = VectX;
-
-     switch(vector)
-       {
-       case VECTX: axis = VectX; break;
-       case VECTY: axis = VectY; break;
-       case VECTZ: axis = VectZ; break;
-       }
-
-     mRotAxis = vector;
-     mRotRow  = row;
-
-     mRotationAngleStatic.set0(0.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 )
-             {
-             if( belongsToRotation( mCubits[x][y][z].mCurrentPosition,vector,mRotRow) )
-               {
-               mCubits[x][y][z].beginNewRotation(axis);
-               }
-             }
-     }
+  float[] getLegalQuats()
+    {
+    final float SQ2 = 0.5f*((float)Math.sqrt(2));
+    return new float[] { 0.0f , 0.5f , -0.5f , 1.0f , -1.0f , SQ2 , -SQ2 };
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   public long addNewRotation(int vector, int row, int angle, long durationMillis, EffectListener listener )
+  boolean belongsToRotation( Static3D currentPosition, int vector, int row)
+    {
+    switch(vector)
       {
-      Static3D axis = VectX;
-      long effectID=0;
-      boolean first = true;
-
-      switch(vector)
-        {
-        case VECTX: axis = VectX; break;
-        case VECTY: axis = VectY; break;
-        case VECTZ: axis = VectZ; break;
-        }
-
-      mRotAxis = vector;
-      mRotRow  = row;
-
-      mRotationAngleStatic.set0(0.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 )
-              {
-              if( belongsToRotation(mCubits[x][y][z].mCurrentPosition,vector,mRotRow) )
-                {
-                mCubits[x][y][z].addNewRotation(axis,durationMillis,angle);
-
-                if( first )
-                  {
-                  first = false;
-                  effectID = mCubits[x][y][z].setUpCallback(listener);
-                  }
-                }
-              }
-
-      return effectID;
+      case VECTX: return currentPosition.get0()==row;
+      case VECTY: return currentPosition.get1()==row;
+      case VECTZ: return currentPosition.get2()==row;
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void removeRotationNow()
-      {
-      float qx=0,qy=0,qz=0;
-      boolean first = true;
-      Static4D quat = null;
-
-      switch(mRotAxis)
-        {
-        case VECTX: qx=1; break;
-        case VECTY: qy=1; break;
-        case VECTZ: qz=1; break;
-        }
-
-      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 )
-              {
-              if( belongsToRotation(mCubits[x][y][z].mCurrentPosition,mRotAxis,mRotRow) )
-                {
-                if( first )
-                  {
-                  first = false;
-                  quat = mCubits[x][y][z].returnRotationQuat(qx,qy,qz);
-                  }
-
-                mCubits[x][y][z].removeRotationNow(quat);
-                }
-              }
-
-      mRotationAngleStatic.set0(0);
-      }
+    return false;
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   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);
-     }
+  RubikCube(int size, Static4D quatCur, Static4D quatAcc, DistortedTexture texture, MeshRectangles mesh, DistortedEffects effects)
+    {
+    super(size,quatCur,quatAcc,texture,mesh,effects);
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
 
-   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);
-     }
+  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);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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 6bbc8285..5a7a31c0 100644
--- a/src/main/java/org/distorted/object/RubikObject.java
+++ b/src/main/java/org/distorted/object/RubikObject.java
@@ -29,6 +29,7 @@ import org.distorted.library.effect.VertexEffectSink;
 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;
@@ -36,21 +37,26 @@ import org.distorted.library.type.Static1D;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 
+import static org.distorted.object.RubikObjectList.*;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public abstract class RubikObject extends DistortedNode
   {
   static final float OBJECT_SCREEN_RATIO = 0.5f;
   static final int TEXTURE_SIZE = 100;
+  final float[] LEGAL_QUATS;
 
   private static final int POST_ROTATION_MILLISEC = 500;
-  final float[] LEGAL_QUATS;
+  private final int NUM_CUBITS;
+  private final int[][] CUBIT_POSITIONS;
+  private int mRotAxis, mRotRow;
 
   private Static3D mMove, mScale, mNodeMove, mNodeScale;
   private Static4D mQuatAccumulated;
   private DistortedTexture mNodeTexture;
 
-  int mSize, mRotAxis, mRotRow;
+  int mSize;
 
   Static1D mRotationAngleStatic, mRotationAngleMiddle, mRotationAngleFinal;
   DistortedTexture mTexture;
@@ -61,6 +67,8 @@ public abstract class RubikObject extends DistortedNode
   MatrixEffectQuaternion mQuatCEffect;
   MatrixEffectQuaternion mQuatAEffect;
 
+  private Cubit[] mCubits;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   RubikObject(int size, Static4D quatCur, Static4D quatAcc, DistortedTexture texture, MeshRectangles mesh, DistortedEffects effects)
@@ -68,6 +76,9 @@ public abstract class RubikObject extends DistortedNode
     super(texture,effects,mesh);
 
     LEGAL_QUATS = getLegalQuats();
+    NUM_CUBITS  = getNumCubits(size);
+    CUBIT_POSITIONS = getCubitPositions(size);
+
     mNodeTexture = texture;
     mSize = size;
 
@@ -97,11 +108,28 @@ public abstract class RubikObject extends DistortedNode
 
     effects.apply(nodeScaleEffect);
     effects.apply(nodeMoveEffect);
+
+
+    mCubits = new Cubit[NUM_CUBITS];
+
+    mTexture = new DistortedTexture(TEXTURE_SIZE,TEXTURE_SIZE);
+
+    int vertices = (int)(24.0f/mSize + 2.0f);
+
+    for(int i=0; i<NUM_CUBITS; i++)
+      {
+      int x = CUBIT_POSITIONS[i][0];
+      int y = CUBIT_POSITIONS[i][1];
+      int z = CUBIT_POSITIONS[i][2];
+
+      mCubits[i] = new Cubit( this ,createMesh(vertices,x,y,z), new Static3D(x,y,z) );
+      attach(mCubits[i].mNode);
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void resetRotationAngle(Dynamic1D rotationAngle)
+  private void resetRotationAngle(Dynamic1D rotationAngle)
     {
     rotationAngle.setDuration(POST_ROTATION_MILLISEC);
     rotationAngle.resetToBeginning();
@@ -176,22 +204,189 @@ public abstract class RubikObject extends DistortedNode
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  abstract float[] getLegalQuats();
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].savePreferences(editor);
+    }
 
-  public abstract void savePreferences(SharedPreferences.Editor editor);
-  public abstract void restorePreferences(SharedPreferences preferences);
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public abstract void beginNewRotation(int vector, int row );
-  public abstract long addNewRotation(int vector, int row, int angle, long durationMillis, EffectListener listener );
-  public abstract long finishRotationNow(EffectListener listener);
-  public abstract void removeRotationNow();
+  public void restorePreferences(SharedPreferences preferences)
+    {
+    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].restorePreferences(preferences);
+    }
 
-  public abstract void apply(Effect effect, int position);
-  public abstract void remove(long effectID);
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public abstract void releaseResources();
-  public abstract void createTexture();
+  public long finishRotationNow(EffectListener listener)
+    {
+    boolean first = true;
+    long effectID=0;
+
+    for(int i=0; i<NUM_CUBITS; i++)
+      {
+      if( belongsToRotation(mCubits[i].mCurrentPosition,mRotAxis,mRotRow) )
+        {
+        if( first )
+          {
+          first=false;
+          effectID = mCubits[i].finishRotationNow(listener);
+          }
+        resetRotationAngle(mCubits[i].mRotationAngle);
+        }
+      }
+
+    return effectID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void releaseResources()
+    {
+    mTexture.markForDeletion();
+
+    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].releaseResources();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void apply(Effect effect, int position)
+    {
+    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].mEffect.apply(effect, position);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void remove(long effectID)
+    {
+    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].mEffect.abortById(effectID);
+    }
 
-  public abstract void solve();
-  public abstract boolean isSolved();
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void solve()
+    {
+    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].solve();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean isSolved()
+    {
+    Static4D q = mCubits[0].mQuatScramble;
+
+    float x = q.get0();
+    float y = q.get1();
+    float z = q.get2();
+    float w = q.get3();
+
+    for(int i=0; i<NUM_CUBITS; i++)
+      {
+      q = mCubits[i].mQuatScramble;
+
+      if( q.get0()!=x || q.get1()!=y || q.get2()!=z || q.get3()!=w ) return false;
+      }
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void beginNewRotation(int vector, int row )
+    {
+    Static3D axis = VectX;
+
+    switch(vector)
+      {
+      case VECTX: axis = VectX; break;
+      case VECTY: axis = VectY; break;
+      case VECTZ: axis = VectZ; break;
+      }
+
+    mRotAxis = vector;
+    mRotRow  = row;
+
+    mRotationAngleStatic.set0(0.0f);
+
+    for(int i=0; i<NUM_CUBITS; i++)
+      if( belongsToRotation( mCubits[i].mCurrentPosition,vector,mRotRow) )
+        {
+        mCubits[i].beginNewRotation(axis);
+        }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public long addNewRotation(int vector, int row, int angle, long durationMillis, EffectListener listener )
+     {
+     Static3D axis = VectX;
+     long effectID=0;
+     boolean first = true;
+
+     switch(vector)
+       {
+       case VECTX: axis = VectX; break;
+       case VECTY: axis = VectY; break;
+       case VECTZ: axis = VectZ; break;
+       }
+
+     mRotAxis = vector;
+     mRotRow  = row;
+
+     mRotationAngleStatic.set0(0.0f);
+
+     for(int i=0; i<NUM_CUBITS; i++)
+       if( belongsToRotation(mCubits[i].mCurrentPosition,vector,mRotRow) )
+         {
+         mCubits[i].addNewRotation(axis,durationMillis,angle);
+
+         if( first )
+           {
+           first = false;
+           effectID = mCubits[i].setUpCallback(listener);
+           }
+         }
+
+     return effectID;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void removeRotationNow()
+     {
+     float qx=0,qy=0,qz=0;
+     boolean first = true;
+     Static4D quat = null;
+
+     switch(mRotAxis)
+       {
+       case VECTX: qx=1; break;
+       case VECTY: qy=1; break;
+       case VECTZ: qz=1; break;
+       }
+
+     for(int i=0; i<NUM_CUBITS; i++)
+       if( belongsToRotation(mCubits[i].mCurrentPosition,mRotAxis,mRotRow) )
+         {
+         if( first )
+           {
+           first = false;
+           quat = mCubits[i].returnRotationQuat(qx,qy,qz);
+           }
+
+         mCubits[i].removeRotationNow(quat);
+         }
+
+     mRotationAngleStatic.set0(0);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract int getNumCubits(int size);
+  abstract int[][] getCubitPositions(int size);
+  abstract float[] getLegalQuats();
+  abstract boolean belongsToRotation(Static3D position, int axis, int row);
+  abstract MeshCubes createMesh(int vertices,int x, int y, int z);
+
+  public abstract void createTexture();
   }
diff --git a/src/main/java/org/distorted/object/RubikObjectList.java b/src/main/java/org/distorted/object/RubikObjectList.java
index dbd88152..d37d860e 100644
--- a/src/main/java/org/distorted/object/RubikObjectList.java
+++ b/src/main/java/org/distorted/object/RubikObjectList.java
@@ -22,6 +22,7 @@ package org.distorted.object;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedTexture;
 import org.distorted.library.mesh.MeshRectangles;
+import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 import org.distorted.magic.R;
 
@@ -40,6 +41,9 @@ public enum RubikObjectList
   public static final int VECTX = 0;  //
   public static final int VECTY = 1;  // don't change this
   public static final int VECTZ = 2;  //
+  static final Static3D VectX = new Static3D(1,0,0);
+  static final Static3D VectY = new Static3D(0,1,0);
+  static final Static3D VectZ = new Static3D(0,0,1);
 
   public static final int LENGTH = values().length;
   private final int mObjectSize, mIconID;
