commit d6998705f29536b72d394df0971230880439bda0
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Tue Oct 12 14:20:07 2021 +0200

    Improve rotations of a Cuboid.

diff --git a/src/main/java/org/distorted/objectlib/main/ObjectType.java b/src/main/java/org/distorted/objectlib/main/ObjectType.java
index 8348a7b0..9bf0afe5 100644
--- a/src/main/java/org/distorted/objectlib/main/ObjectType.java
+++ b/src/main/java/org/distorted/objectlib/main/ObjectType.java
@@ -184,7 +184,7 @@ public enum ObjectType
       case  0:
       case  1:
       case  2:
-      case  3: return new TwistyCube          (numL, quat, move, texture, mesh, effects, res, surfaceW, surfaceH);
+      case  3: return new TwistyCuboid(numL, quat, move, texture, mesh, effects, res, surfaceW, surfaceH);
       case  4: return new TwistyJing          (numL, quat, move, texture, mesh, effects, res, surfaceW, surfaceH);
       case  5:
       case  6:
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyCube.java b/src/main/java/org/distorted/objectlib/objects/TwistyCube.java
deleted file mode 100644
index 07cb87f9..00000000
--- a/src/main/java/org/distorted/objectlib/objects/TwistyCube.java
+++ /dev/null
@@ -1,668 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.objectlib.objects;
-
-import static org.distorted.objectlib.main.Movement.TYPE_NOT_SPLIT;
-
-import android.content.res.Resources;
-
-import org.distorted.library.main.DistortedEffects;
-import org.distorted.library.main.DistortedTexture;
-import org.distorted.library.mesh.MeshSquare;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-
-import org.distorted.objectlib.R;
-import org.distorted.objectlib.main.Movement;
-import org.distorted.objectlib.main.Movement6;
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.helpers.ObjectShape;
-import org.distorted.objectlib.helpers.ObjectSticker;
-import org.distorted.objectlib.helpers.ScrambleState;
-import org.distorted.objectlib.main.Twisty6;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class TwistyCube extends Twisty6
-{
-  static final Static3D[] ROT_AXIS = new Static3D[]
-         {
-           new Static3D(1,0,0),
-           new Static3D(0,1,0),
-           new Static3D(0,0,1)
-         };
-
-  private static final int[][][] ENABLED = new int[][][]
-      {
-          {{1,2}},{{1,2}},{{0,2}},{{0,2}},{{0,1}},{{0,1}},
-      };
-
-  private ScrambleState[] mStates;
-  private Static4D[] mQuats;
-  private float[][] mCuts;
-  private boolean[][] mLayerRotatable;
-  private int[] mBasicAngle;
-  private ObjectSticker[] mStickers;
-  private Movement mMovement;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public TwistyCube(int[] numL, Static4D quat, Static3D move, DistortedTexture texture,
-                    MeshSquare mesh, DistortedEffects effects, Resources res, int surfaceW, int surfaceH)
-    {
-    super(numL, (numL[0]+numL[1]+numL[2])/3.0f, quat, move, texture, mesh, effects, res, surfaceW, surfaceH);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] createEdges(int size, int vertex)
-    {
-    int[] ret = new int[9*size];
-
-    for(int l=0; l<size; l++)
-      {
-      ret[9*l  ] = l;
-      ret[9*l+1] =-1;
-      ret[9*l+2] = vertex;
-      ret[9*l+3] = l;
-      ret[9*l+4] = 1;
-      ret[9*l+5] = vertex;
-      ret[9*l+6] = l;
-      ret[9*l+7] = 2;
-      ret[9*l+8] = vertex;
-      }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected ScrambleState[] getScrambleStates()
-    {
-    if( mStates==null )
-      {
-      int[] numL = getNumLayers();
-      int[][] m = new int[16][];
-      for(int i=1; i<16; i++) m[i] = createEdges(numL[0],i);
-
-      mStates = new ScrambleState[]
-        {
-        new ScrambleState( new int[][] { m[ 1], m[ 2], m[ 3] } ),  //  0 0
-        new ScrambleState( new int[][] {  null, m[ 4], m[ 5] } ),  //  1 x
-        new ScrambleState( new int[][] { m[ 6],  null, m[ 7] } ),  //  2 y
-        new ScrambleState( new int[][] { m[ 8], m[ 9],  null } ),  //  3 z
-        new ScrambleState( new int[][] { m[10],  null, m[ 7] } ),  //  4 xy
-        new ScrambleState( new int[][] { m[11], m[ 9],  null } ),  //  5 xz
-        new ScrambleState( new int[][] {  null, m[12], m[ 5] } ),  //  6 yx
-        new ScrambleState( new int[][] { m[ 8], m[13],  null } ),  //  7 yz
-        new ScrambleState( new int[][] {  null, m[ 4], m[14] } ),  //  8 zx
-        new ScrambleState( new int[][] { m[ 6],  null, m[15] } ),  //  9 zy
-        new ScrambleState( new int[][] {  null,  null, m[ 5] } ),  // 10 xyx
-        new ScrambleState( new int[][] {  null, m[ 4],  null } ),  // 11 xzx
-        new ScrambleState( new int[][] {  null,  null, m[ 7] } ),  // 12 yxy
-        new ScrambleState( new int[][] { m[ 6],  null,  null } ),  // 13 yzy
-        new ScrambleState( new int[][] {  null, m[ 9],  null } ),  // 14 zxz
-        new ScrambleState( new int[][] { m[ 8],  null,  null } ),  // 15 zyz
-        };
-      }
-
-    return mStates;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected int getResource(int[] numLayers)
-    {
-    int x = numLayers[0];
-    int y = numLayers[1];
-    int z = numLayers[2];
-
-    if( x==y && x==z )
-      {
-      switch(x)
-        {
-        case 2: return R.raw.cube2;
-        case 3: return R.raw.cube3;
-        case 4: return R.raw.cube4;
-        case 5: return R.raw.cube5;
-        }
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void initializeQuats()
-    {
-    mQuats = new Static4D[]
-         {
-         new Static4D(  0.0f,   0.0f,   0.0f,   1.0f),
-         new Static4D(  1.0f,   0.0f,   0.0f,   0.0f),
-         new Static4D(  0.0f,   1.0f,   0.0f,   0.0f),
-         new Static4D(  0.0f,   0.0f,   1.0f,   0.0f),
-
-         new Static4D( SQ2/2,  SQ2/2,  0.0f ,   0.0f),
-         new Static4D( SQ2/2, -SQ2/2,  0.0f ,   0.0f),
-         new Static4D( SQ2/2,   0.0f,  SQ2/2,   0.0f),
-         new Static4D(-SQ2/2,   0.0f,  SQ2/2,   0.0f),
-         new Static4D( SQ2/2,   0.0f,   0.0f,  SQ2/2),
-         new Static4D( SQ2/2,   0.0f,   0.0f, -SQ2/2),
-         new Static4D(  0.0f,  SQ2/2,  SQ2/2,   0.0f),
-         new Static4D(  0.0f,  SQ2/2, -SQ2/2,   0.0f),
-         new Static4D(  0.0f,  SQ2/2,   0.0f,  SQ2/2),
-         new Static4D(  0.0f,  SQ2/2,   0.0f, -SQ2/2),
-         new Static4D(  0.0f,   0.0f,  SQ2/2,  SQ2/2),
-         new Static4D(  0.0f,   0.0f,  SQ2/2, -SQ2/2),
-
-         new Static4D(  0.5f,   0.5f,   0.5f,   0.5f),
-         new Static4D(  0.5f,   0.5f,  -0.5f,   0.5f),
-         new Static4D(  0.5f,   0.5f,  -0.5f,  -0.5f),
-         new Static4D(  0.5f,  -0.5f,   0.5f,  -0.5f),
-         new Static4D( -0.5f,  -0.5f,  -0.5f,   0.5f),
-         new Static4D( -0.5f,   0.5f,  -0.5f,  -0.5f),
-         new Static4D( -0.5f,   0.5f,   0.5f,  -0.5f),
-         new Static4D( -0.5f,   0.5f,   0.5f,   0.5f)
-         };
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected int[] getSolvedQuats(int cubit, int[] numLayers)
-    {
-    if( mQuats ==null ) initializeQuats();
-    int status = retCubitSolvedStatus(cubit,numLayers);
-    return status<0 ? null : buildSolvedQuats(Movement6.FACE_AXIS[status], mQuats);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected ObjectShape getObjectShape(int cubit, int[] numLayers)
-    {
-    int extraI, extraV, num;
-    float height;
-    int variant = getCubitVariant(cubit,numLayers);
-    int numL = numLayers[0];
-
-    switch(numL)
-        {
-        case 2 : num = 6; extraI = 2; extraV = 2; height = 0.045f; break;
-        case 3 : num = 5; extraI = 2; extraV = 2; height = 0.045f; break;
-        case 4 : num = 5; extraI = 1; extraV = 1; height = 0.045f; break;
-        default: num = 5; extraI = 0; extraV = 0; height = 0.045f; break;
-        }
-
-    double[][] vertices = new double[][]
-          {
-              { 0.5, 0.5, 0.5 },
-              { 0.5, 0.5,-0.5 },
-              { 0.5,-0.5, 0.5 },
-              { 0.5,-0.5,-0.5 },
-              {-0.5, 0.5, 0.5 },
-              {-0.5, 0.5,-0.5 },
-              {-0.5,-0.5, 0.5 },
-              {-0.5,-0.5,-0.5 },
-          };
-
-    int[][] vert_indices = new int[][]
-          {
-              {2,3,1,0},
-              {7,6,4,5},
-              {4,0,1,5},
-              {7,3,2,6},
-              {6,2,0,4},
-              {3,7,5,1}
-          };
-
-    float[][] corners   = new float[][] { {0.036f,0.12f} };
-    int[] cornerIndices = new int[] { 0,0,0,0,0,0,0,0 };
-    float[][] centers   = new float[][] { {0.0f, 0.0f, 0.0f} };
-    int[] centerIndices = new int[] { 0,0,0,0,0,0,0,0 };
-
-    if( variant==0 )
-      {
-      float[][] bands   = new float[][] { {height,35,0.5f,0.7f,num,extraI,extraV} };
-      int[] bandIndices = new int[] { 0,0,0,0,0,0};
-      return new ObjectShape(vertices,vert_indices,bands,bandIndices,corners,cornerIndices,centers,centerIndices,getNumCubitFaces(), null);
-      }
-    else
-      {
-      int extraI2, extraV2, num2;
-
-      switch(numL)
-        {
-        case 2 : num2 = 6; extraI2 = 2; extraV2 = 2; break;
-        case 3 : num2 = 5; extraI2 = 1; extraV2 = 0; break;
-        case 4 : num2 = 4; extraI2 = 0; extraV2 = 0; break;
-        default: num2 = 3; extraI2 = 0; extraV2 = 0; break;
-        }
-
-      float[][] bands   = new float[][]
-        {
-          {height,35,0.5f,0.7f,num ,extraI ,extraV },
-          {height,35,0.5f,0.7f,num2,extraI2,extraV2},
-          {height,35,0.5f,0.7f,   2,      0,      0},
-        };
-      int[] bandIndices = new int[] { 1,1,1,1,0,2};
-      return new ObjectShape(vertices,vert_indices,bands,bandIndices,corners,cornerIndices,centers,centerIndices,getNumCubitFaces(), null);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getCenterNum(int cubit, int[] numLayers)
-    {
-    int num = cubit - getNumCornersAndEdges(numLayers);
-
-    if( num>=0 )
-      {
-      int numLR = (numLayers[1]-2)*(numLayers[2]-2);
-      if( num<  numLR ) return 0;
-      if( num<2*numLR ) return 1;
-      num -= 2*numLR;
-
-      int numTD = (numLayers[0]-2)*(numLayers[2]-2);
-      if( num<  numTD ) return 2;
-      if( num<2*numTD ) return 3;
-      num -= 2*numTD;
-
-      int numFB = (numLayers[0]-2)*(numLayers[1]-2);
-      if( num<  numFB ) return 4;
-      if( num<2*numFB ) return 5;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getNumCornersAndEdges(int[] numLayers)
-    {
-    int x = numLayers[0];
-    int y = numLayers[1];
-    int z = numLayers[2];
-
-    return ( x==1 || y==1 || z==1 ) ? x*y*z : 4*( (x-2)+(y-2)+(z-2) ) + 8;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected float[][] getCubitPositions(int[] numLayers)
-    {
-    final int X = numLayers[0];
-    final int Y = numLayers[1];
-    final int Z = numLayers[2];
-
-    final float lenX = 0.5f*(X-1);
-    final float lenY = 0.5f*(Y-1);
-    final float lenZ = 0.5f*(Z-1);
-
-    int curPos = 0;
-
-    if( X==1 )
-      {
-      float[][] pos = new float[X*Y*Z][];
-
-      for(int y=0; y<Y; y++)
-        for(int z=0; z<Z; z++) pos[curPos++] = new float[] {+lenX,y-lenY,z-lenZ};
-
-      return pos;
-      }
-
-    if( Y==1 )
-      {
-      float[][] pos = new float[X*Y*Z][];
-
-      for(int x=0; x<X; x++)
-        for(int z=0; z<Z; z++) pos[curPos++] = new float[] {x-lenX,+lenY,z-lenZ};
-
-      return pos;
-      }
-
-    if( Z==1 )
-      {
-      float[][] pos = new float[X*Y*Z][];
-
-      for(int x=0; x<X; x++)
-        for(int y=0; y<Y; y++) pos[curPos++] = new float[] {x-lenX,y-lenY,+lenZ};
-
-      return pos;
-      }
-
-    int numCubits = X*Y*Z - (X-2)*(Y-2)*(Z-2);
-    float[][] pos = new float[numCubits][];
-
-    pos[curPos++] = new float[] {-lenX,-lenY,-lenZ};
-    pos[curPos++] = new float[] {-lenX,-lenY,+lenZ};
-    pos[curPos++] = new float[] {-lenX,+lenY,-lenZ};
-    pos[curPos++] = new float[] {-lenX,+lenY,+lenZ};
-    pos[curPos++] = new float[] {+lenX,-lenY,-lenZ};
-    pos[curPos++] = new float[] {+lenX,-lenY,+lenZ};
-    pos[curPos++] = new float[] {+lenX,+lenY,-lenZ};
-    pos[curPos++] = new float[] {+lenX,+lenY,+lenZ};
-
-    for(int i=1; i<X-1; i++) pos[curPos++] = new float[] { i-lenX,  -lenY,  -lenZ };
-    for(int i=1; i<X-1; i++) pos[curPos++] = new float[] { i-lenX,  -lenY,  +lenZ };
-    for(int i=1; i<X-1; i++) pos[curPos++] = new float[] { i-lenX,  +lenY,  -lenZ };
-    for(int i=1; i<X-1; i++) pos[curPos++] = new float[] { i-lenX,  +lenY,  +lenZ };
-    for(int i=1; i<Y-1; i++) pos[curPos++] = new float[] {  -lenX, i-lenY,  -lenZ };
-    for(int i=1; i<Y-1; i++) pos[curPos++] = new float[] {  -lenX, i-lenY,  +lenZ };
-    for(int i=1; i<Y-1; i++) pos[curPos++] = new float[] {  +lenX, i-lenY,  -lenZ };
-    for(int i=1; i<Y-1; i++) pos[curPos++] = new float[] {  +lenX, i-lenY,  +lenZ };
-    for(int i=1; i<Z-1; i++) pos[curPos++] = new float[] {  -lenX,  -lenY, i-lenZ };
-    for(int i=1; i<Z-1; i++) pos[curPos++] = new float[] {  -lenX,  +lenY, i-lenZ };
-    for(int i=1; i<Z-1; i++) pos[curPos++] = new float[] {  +lenX,  -lenY, i-lenZ };
-    for(int i=1; i<Z-1; i++) pos[curPos++] = new float[] {  +lenX,  +lenY, i-lenZ };
-
-    for(int y=1; y<Y-1; y++)
-      for(int z=1; z<Z-1; z++) pos[curPos++] = new float[] {+lenX,y-lenY,z-lenZ};
-
-    for(int y=1; y<Y-1; y++)
-      for(int z=1; z<Z-1; z++) pos[curPos++] = new float[] {-lenX,y-lenY,z-lenZ};
-
-    for(int x=1; x<X-1; x++)
-      for(int z=1; z<Z-1; z++) pos[curPos++] = new float[] {x-lenX,+lenY,z-lenZ};
-
-    for(int x=1; x<X-1; x++)
-      for(int z=1; z<Z-1; z++) pos[curPos++] = new float[] {x-lenX,-lenY,z-lenZ};
-
-    for(int x=1; x<X-1; x++)
-      for(int y=1; y<Y-1; y++) pos[curPos++] = new float[] {x-lenX,y-lenY,+lenZ};
-
-    for(int x=1; x<X-1; x++)
-      for(int y=1; y<Y-1; y++) pos[curPos++] = new float[] {x-lenX,y-lenY,-lenZ};
-
-    return pos;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected Static4D getQuat(int cubit, int[] numLayers)
-    {
-    if( mQuats ==null ) initializeQuats();
-
-    int centerNum = getCenterNum(cubit,numLayers);
-
-    switch(centerNum)
-      {
-      case 0 : return mQuats[13];
-      case 1 : return mQuats[12];
-      case 2 : return mQuats[ 8];
-      case 3 : return mQuats[ 9];
-      case 4 : return mQuats[ 0];
-      case 5 : return mQuats[ 1];
-      default: return mQuats[ 0];
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected int getNumCubitVariants(int[] numLayers)
-    {
-    return numLayers[0]>2 ? 2:1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected int getCubitVariant(int cubit, int[] numLayers)
-    {
-    return cubit < getNumCornersAndEdges(numLayers) ? 0 : 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected int getFaceColor(int cubit, int cubitface, int[] numLayers)
-    {
-    int centerNum = getCenterNum(cubit,numLayers);
-
-    if( centerNum<0 )
-      {
-      int axis = cubitface/2;
-      return CUBITS[cubit].getRotRow(axis) == (cubitface%2==0 ? (1<<(numLayers[axis]-1)):1) ? cubitface : NUM_TEXTURES;
-      }
-    else
-      {
-      return cubitface == 4 ? centerNum : NUM_TEXTURES;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected ObjectSticker retSticker(int face)
-    {
-    if( mStickers==null )
-      {
-      final float[][] STICKERS = new float[][]  { { -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f } };
-      final float radius = 0.10f;
-      final float[] radii = {radius,radius,radius,radius};
-      mStickers = new ObjectSticker[STICKERS.length];
-      float stroke = 0.08f;
-
-      if( ObjectControl.isInIconMode() )
-        {
-        int[] numLayers = getNumLayers();
-
-        switch(numLayers[0])
-          {
-          case 2: stroke*=1.8f; break;
-          case 3: stroke*=2.0f; break;
-          case 4: stroke*=2.1f; break;
-          default:stroke*=2.2f; break;
-          }
-        }
-
-      mStickers[0] = new ObjectSticker(STICKERS[0],null,radii,stroke );
-      }
-
-    return mStickers[face/NUM_FACE_COLORS];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected Static4D[] getQuats()
-    {
-    if( mQuats ==null ) initializeQuats();
-    return mQuats;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected float[][] getCuts(int[] numLayers)
-    {
-    if( mCuts==null )
-      {
-      mCuts = new float[3][];
-
-      int lenX = numLayers[0];
-
-      if( lenX>=2 )
-        {
-        mCuts[0] = new float[lenX-1];
-        for(int i=0; i<lenX-1; i++) mCuts[0][i] = (2-lenX)*0.5f + i;
-        }
-      else
-        {
-        mCuts[0] = null;
-        }
-
-      int lenY = numLayers[1];
-
-      if( lenY>=2 )
-        {
-        mCuts[1] = new float[lenY-1];
-        for(int i=0; i<lenY-1; i++) mCuts[1][i] = (2-lenY)*0.5f + i;
-        }
-      else
-        {
-        mCuts[1] = null;
-        }
-
-      int lenZ = numLayers[2];
-
-      if( lenZ>=2 )
-        {
-        mCuts[2] = new float[lenZ-1];
-        for(int i=0; i<lenZ-1; i++) mCuts[2][i] = (2-lenZ)*0.5f + i;
-        }
-      else
-        {
-        mCuts[2] = null;
-        }
-      }
-
-    return mCuts;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getLayerRotatable(int[] numLayers)
-    {
-    if( mLayerRotatable==null )
-      {
-      int numAxis = ROT_AXIS.length;
-      mLayerRotatable = new boolean[numAxis][];
-
-      for(int i=0; i<numAxis; i++)
-        {
-        mLayerRotatable[i] = new boolean[numLayers[i]];
-        for(int j=0; j<numLayers[i]; j++) mLayerRotatable[i][j] = true;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected int getSolvedFunctionIndex()
-    {
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected int getNumStickerTypes(int[] numLayers)
-    {
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected int getNumCubitFaces()
-    {
-    return 6;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-
-  public Static3D[] getRotationAxis()
-    {
-    return ROT_AXIS;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
-
-  public Movement getMovement()
-    {
-    if( mMovement==null )
-      {
-      int[] numLayers = getNumLayers();
-      if( mCuts==null ) getCuts(numLayers);
-      getLayerRotatable(numLayers);
-      mMovement = new Movement6(ROT_AXIS,mCuts,mLayerRotatable,numLayers[0],TYPE_NOT_SPLIT,ENABLED);
-      }
-    return mMovement;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[] getBasicAngle()
-    {
-    if( mBasicAngle==null )
-      {
-      int[] num = getNumLayers();
-      int x = num[1]==num[2] ? 4 : 2;
-      int y = num[0]==num[2] ? 4 : 2;
-      int z = num[0]==num[1] ? 4 : 2;
-
-      mBasicAngle = new int[] { x,y,z };
-      }
-    return mBasicAngle;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
-
-  public ObjectType intGetObjectType(int[] numLayers)
-    {
-    switch(numLayers[0])
-      {
-      case 2: return numLayers[1]==2 ? ObjectType.CUBE_2 : ObjectType.CU_223;
-      case 3: return numLayers[1]==3 ? ObjectType.CUBE_3 : ObjectType.CU_334;
-      case 4: return ObjectType.CUBE_4;
-      case 5: return ObjectType.CUBE_5;
-      }
-
-    return ObjectType.CUBE_3;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getObjectName(int[] numLayers)
-    {
-    switch(numLayers[0])
-      {
-      case 2: return R.string.cube2;
-      case 3: return R.string.cube3;
-      case 4: return R.string.cube4;
-      case 5: return R.string.cube5;
-      }
-    return R.string.cube3;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getInventor(int[] numLayers)
-    {
-    switch(numLayers[0])
-      {
-      case 2: return R.string.cube2_inventor;
-      case 3: return R.string.cube3_inventor;
-      case 4: return R.string.cube4_inventor;
-      case 5: return R.string.cube5_inventor;
-      }
-    return R.string.cube3_inventor;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getComplexity(int[] numLayers)
-    {
-    switch(numLayers[0])
-      {
-      case 2: return 4;
-      case 3: return 6;
-      case 4: return 8;
-      case 5: return 10;
-      }
-    return 6;
-    }
-}
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyCuboid.java b/src/main/java/org/distorted/objectlib/objects/TwistyCuboid.java
new file mode 100644
index 00000000..3d875c14
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyCuboid.java
@@ -0,0 +1,697 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.objectlib.objects;
+
+import static org.distorted.objectlib.main.Movement.TYPE_NOT_SPLIT;
+
+import android.content.res.Resources;
+
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshSquare;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+
+import org.distorted.objectlib.R;
+import org.distorted.objectlib.main.Movement;
+import org.distorted.objectlib.main.Movement6;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.ObjectType;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
+import org.distorted.objectlib.main.Twisty6;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class TwistyCuboid extends Twisty6
+{
+  static final Static3D[] ROT_AXIS = new Static3D[]
+         {
+           new Static3D(1,0,0),
+           new Static3D(0,1,0),
+           new Static3D(0,0,1)
+         };
+
+  private static final int[][][] ENABLED = new int[][][]
+      {
+          {{1,2}},{{1,2}},{{0,2}},{{0,2}},{{0,1}},{{0,1}},
+      };
+
+  private ScrambleState[] mStates;
+  private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
+  private int[] mBasicAngle;
+  private ObjectSticker[] mStickers;
+  private Movement mMovement;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public TwistyCuboid(int[] numL, Static4D quat, Static3D move, DistortedTexture texture,
+                      MeshSquare mesh, DistortedEffects effects, Resources res, int surfaceW, int surfaceH)
+    {
+    super(numL, (numL[0]+numL[1]+numL[2])/3.0f, quat, move, texture, mesh, effects, res, surfaceW, surfaceH);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] createEdges(int size, boolean full, int vertex)
+    {
+    if( full )
+      {
+      int[] ret = new int[9*size];
+
+      for(int l=0; l<size; l++)
+        {
+        ret[9*l  ] = l;
+        ret[9*l+1] =-1;
+        ret[9*l+2] = vertex;
+        ret[9*l+3] = l;
+        ret[9*l+4] = 1;
+        ret[9*l+5] = vertex;
+        ret[9*l+6] = l;
+        ret[9*l+7] = 2;
+        ret[9*l+8] = vertex;
+        }
+
+      return ret;
+      }
+    else
+      {
+      int[] ret = new int[6*size];
+
+      for(int l=0; l<size; l++)
+        {
+        ret[6*l  ] = l;
+        ret[6*l+1] = 1;
+        ret[6*l+2] = vertex;
+        ret[6*l+3] = l;
+        ret[6*l+4] =-1;
+        ret[6*l+5] = vertex;
+        }
+
+      return ret;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected ScrambleState[] getScrambleStates()
+    {
+    if( mStates==null )
+      {
+      int[] numLayers = getNumLayers();
+
+      int X = numLayers[0];
+      int Y = numLayers[1];
+      int Z = numLayers[2];
+
+      int[][] mX = new int[16][];
+      int[][] mY = new int[16][];
+      int[][] mZ = new int[16][];
+
+      for(int i=1; i<16; i++) mX[i] = createEdges(X,Y==Z,i);
+      for(int i=1; i<16; i++) mY[i] = createEdges(Y,X==Z,i);
+      for(int i=1; i<16; i++) mZ[i] = createEdges(Z,X==Y,i);
+
+      mStates = new ScrambleState[]
+        {
+        new ScrambleState( new int[][] { mX[ 1], mY[ 2], mZ[ 3] } ),  //  0 0
+        new ScrambleState( new int[][] {   null, mY[ 4], mZ[ 5] } ),  //  1 x
+        new ScrambleState( new int[][] { mX[ 6],   null, mZ[ 7] } ),  //  2 y
+        new ScrambleState( new int[][] { mX[ 8], mY[ 9],   null } ),  //  3 z
+        new ScrambleState( new int[][] { mX[10],   null, mZ[ 7] } ),  //  4 xy
+        new ScrambleState( new int[][] { mX[11], mY[ 9],   null } ),  //  5 xz
+        new ScrambleState( new int[][] {   null, mY[12], mZ[ 5] } ),  //  6 yx
+        new ScrambleState( new int[][] { mX[ 8], mY[13],   null } ),  //  7 yz
+        new ScrambleState( new int[][] {   null, mY[ 4], mZ[14] } ),  //  8 zx
+        new ScrambleState( new int[][] { mX[ 6],   null, mZ[15] } ),  //  9 zy
+        new ScrambleState( new int[][] {   null,   null, mZ[ 5] } ),  // 10 xyx
+        new ScrambleState( new int[][] {   null, mY[ 4],   null } ),  // 11 xzx
+        new ScrambleState( new int[][] {   null,   null, mZ[ 7] } ),  // 12 yxy
+        new ScrambleState( new int[][] { mX[ 6],   null,   null } ),  // 13 yzy
+        new ScrambleState( new int[][] {   null, mY[ 9],   null } ),  // 14 zxz
+        new ScrambleState( new int[][] { mX[ 8],   null,   null } ),  // 15 zyz
+        };
+      }
+
+    return mStates;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected int getResource(int[] numLayers)
+    {
+    int x = numLayers[0];
+    int y = numLayers[1];
+    int z = numLayers[2];
+
+    if( x==y && x==z )
+      {
+      switch(x)
+        {
+        case 2: return R.raw.cube2;
+        case 3: return R.raw.cube3;
+        case 4: return R.raw.cube4;
+        case 5: return R.raw.cube5;
+        }
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void initializeQuats()
+    {
+    mQuats = new Static4D[]
+         {
+         new Static4D(  0.0f,   0.0f,   0.0f,   1.0f),
+         new Static4D(  1.0f,   0.0f,   0.0f,   0.0f),
+         new Static4D(  0.0f,   1.0f,   0.0f,   0.0f),
+         new Static4D(  0.0f,   0.0f,   1.0f,   0.0f),
+
+         new Static4D( SQ2/2,  SQ2/2,  0.0f ,   0.0f),
+         new Static4D( SQ2/2, -SQ2/2,  0.0f ,   0.0f),
+         new Static4D( SQ2/2,   0.0f,  SQ2/2,   0.0f),
+         new Static4D(-SQ2/2,   0.0f,  SQ2/2,   0.0f),
+         new Static4D( SQ2/2,   0.0f,   0.0f,  SQ2/2),
+         new Static4D( SQ2/2,   0.0f,   0.0f, -SQ2/2),
+         new Static4D(  0.0f,  SQ2/2,  SQ2/2,   0.0f),
+         new Static4D(  0.0f,  SQ2/2, -SQ2/2,   0.0f),
+         new Static4D(  0.0f,  SQ2/2,   0.0f,  SQ2/2),
+         new Static4D(  0.0f,  SQ2/2,   0.0f, -SQ2/2),
+         new Static4D(  0.0f,   0.0f,  SQ2/2,  SQ2/2),
+         new Static4D(  0.0f,   0.0f,  SQ2/2, -SQ2/2),
+
+         new Static4D(  0.5f,   0.5f,   0.5f,   0.5f),
+         new Static4D(  0.5f,   0.5f,  -0.5f,   0.5f),
+         new Static4D(  0.5f,   0.5f,  -0.5f,  -0.5f),
+         new Static4D(  0.5f,  -0.5f,   0.5f,  -0.5f),
+         new Static4D( -0.5f,  -0.5f,  -0.5f,   0.5f),
+         new Static4D( -0.5f,   0.5f,  -0.5f,  -0.5f),
+         new Static4D( -0.5f,   0.5f,   0.5f,  -0.5f),
+         new Static4D( -0.5f,   0.5f,   0.5f,   0.5f)
+         };
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected int[] getSolvedQuats(int cubit, int[] numLayers)
+    {
+    if( mQuats ==null ) initializeQuats();
+    int status = retCubitSolvedStatus(cubit,numLayers);
+    return status<0 ? null : buildSolvedQuats(Movement6.FACE_AXIS[status], mQuats);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected ObjectShape getObjectShape(int cubit, int[] numLayers)
+    {
+    int extraI, extraV, num;
+    float height;
+    int variant = getCubitVariant(cubit,numLayers);
+    int numL = numLayers[0];
+
+    switch(numL)
+        {
+        case 2 : num = 6; extraI = 2; extraV = 2; height = 0.045f; break;
+        case 3 : num = 5; extraI = 2; extraV = 2; height = 0.045f; break;
+        case 4 : num = 5; extraI = 1; extraV = 1; height = 0.045f; break;
+        default: num = 5; extraI = 0; extraV = 0; height = 0.045f; break;
+        }
+
+    double[][] vertices = new double[][]
+          {
+              { 0.5, 0.5, 0.5 },
+              { 0.5, 0.5,-0.5 },
+              { 0.5,-0.5, 0.5 },
+              { 0.5,-0.5,-0.5 },
+              {-0.5, 0.5, 0.5 },
+              {-0.5, 0.5,-0.5 },
+              {-0.5,-0.5, 0.5 },
+              {-0.5,-0.5,-0.5 },
+          };
+
+    int[][] vert_indices = new int[][]
+          {
+              {2,3,1,0},
+              {7,6,4,5},
+              {4,0,1,5},
+              {7,3,2,6},
+              {6,2,0,4},
+              {3,7,5,1}
+          };
+
+    float[][] corners   = new float[][] { {0.036f,0.12f} };
+    int[] cornerIndices = new int[] { 0,0,0,0,0,0,0,0 };
+    float[][] centers   = new float[][] { {0.0f, 0.0f, 0.0f} };
+    int[] centerIndices = new int[] { 0,0,0,0,0,0,0,0 };
+
+    if( variant==0 )
+      {
+      float[][] bands   = new float[][] { {height,35,0.5f,0.7f,num,extraI,extraV} };
+      int[] bandIndices = new int[] { 0,0,0,0,0,0};
+      return new ObjectShape(vertices,vert_indices,bands,bandIndices,corners,cornerIndices,centers,centerIndices,getNumCubitFaces(), null);
+      }
+    else
+      {
+      int extraI2, extraV2, num2;
+
+      switch(numL)
+        {
+        case 2 : num2 = 6; extraI2 = 2; extraV2 = 2; break;
+        case 3 : num2 = 5; extraI2 = 1; extraV2 = 0; break;
+        case 4 : num2 = 4; extraI2 = 0; extraV2 = 0; break;
+        default: num2 = 3; extraI2 = 0; extraV2 = 0; break;
+        }
+
+      float[][] bands   = new float[][]
+        {
+          {height,35,0.5f,0.7f,num ,extraI ,extraV },
+          {height,35,0.5f,0.7f,num2,extraI2,extraV2},
+          {height,35,0.5f,0.7f,   2,      0,      0},
+        };
+      int[] bandIndices = new int[] { 1,1,1,1,0,2};
+      return new ObjectShape(vertices,vert_indices,bands,bandIndices,corners,cornerIndices,centers,centerIndices,getNumCubitFaces(), null);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getCenterNum(int cubit, int[] numLayers)
+    {
+    int num = cubit - getNumCornersAndEdges(numLayers);
+
+    if( num>=0 )
+      {
+      int numLR = (numLayers[1]-2)*(numLayers[2]-2);
+      if( num<  numLR ) return 0;
+      if( num<2*numLR ) return 1;
+      num -= 2*numLR;
+
+      int numTD = (numLayers[0]-2)*(numLayers[2]-2);
+      if( num<  numTD ) return 2;
+      if( num<2*numTD ) return 3;
+      num -= 2*numTD;
+
+      int numFB = (numLayers[0]-2)*(numLayers[1]-2);
+      if( num<  numFB ) return 4;
+      if( num<2*numFB ) return 5;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getNumCornersAndEdges(int[] numLayers)
+    {
+    int x = numLayers[0];
+    int y = numLayers[1];
+    int z = numLayers[2];
+
+    return ( x==1 || y==1 || z==1 ) ? x*y*z : 4*( (x-2)+(y-2)+(z-2) ) + 8;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected float[][] getCubitPositions(int[] numLayers)
+    {
+    final int X = numLayers[0];
+    final int Y = numLayers[1];
+    final int Z = numLayers[2];
+
+    final float lenX = 0.5f*(X-1);
+    final float lenY = 0.5f*(Y-1);
+    final float lenZ = 0.5f*(Z-1);
+
+    int curPos = 0;
+
+    if( X==1 )
+      {
+      float[][] pos = new float[X*Y*Z][];
+
+      for(int y=0; y<Y; y++)
+        for(int z=0; z<Z; z++) pos[curPos++] = new float[] {+lenX,y-lenY,z-lenZ};
+
+      return pos;
+      }
+
+    if( Y==1 )
+      {
+      float[][] pos = new float[X*Y*Z][];
+
+      for(int x=0; x<X; x++)
+        for(int z=0; z<Z; z++) pos[curPos++] = new float[] {x-lenX,+lenY,z-lenZ};
+
+      return pos;
+      }
+
+    if( Z==1 )
+      {
+      float[][] pos = new float[X*Y*Z][];
+
+      for(int x=0; x<X; x++)
+        for(int y=0; y<Y; y++) pos[curPos++] = new float[] {x-lenX,y-lenY,+lenZ};
+
+      return pos;
+      }
+
+    int numCubits = X*Y*Z - (X-2)*(Y-2)*(Z-2);
+    float[][] pos = new float[numCubits][];
+
+    pos[curPos++] = new float[] {-lenX,-lenY,-lenZ};
+    pos[curPos++] = new float[] {-lenX,-lenY,+lenZ};
+    pos[curPos++] = new float[] {-lenX,+lenY,-lenZ};
+    pos[curPos++] = new float[] {-lenX,+lenY,+lenZ};
+    pos[curPos++] = new float[] {+lenX,-lenY,-lenZ};
+    pos[curPos++] = new float[] {+lenX,-lenY,+lenZ};
+    pos[curPos++] = new float[] {+lenX,+lenY,-lenZ};
+    pos[curPos++] = new float[] {+lenX,+lenY,+lenZ};
+
+    for(int i=1; i<X-1; i++) pos[curPos++] = new float[] { i-lenX,  -lenY,  -lenZ };
+    for(int i=1; i<X-1; i++) pos[curPos++] = new float[] { i-lenX,  -lenY,  +lenZ };
+    for(int i=1; i<X-1; i++) pos[curPos++] = new float[] { i-lenX,  +lenY,  -lenZ };
+    for(int i=1; i<X-1; i++) pos[curPos++] = new float[] { i-lenX,  +lenY,  +lenZ };
+    for(int i=1; i<Y-1; i++) pos[curPos++] = new float[] {  -lenX, i-lenY,  -lenZ };
+    for(int i=1; i<Y-1; i++) pos[curPos++] = new float[] {  -lenX, i-lenY,  +lenZ };
+    for(int i=1; i<Y-1; i++) pos[curPos++] = new float[] {  +lenX, i-lenY,  -lenZ };
+    for(int i=1; i<Y-1; i++) pos[curPos++] = new float[] {  +lenX, i-lenY,  +lenZ };
+    for(int i=1; i<Z-1; i++) pos[curPos++] = new float[] {  -lenX,  -lenY, i-lenZ };
+    for(int i=1; i<Z-1; i++) pos[curPos++] = new float[] {  -lenX,  +lenY, i-lenZ };
+    for(int i=1; i<Z-1; i++) pos[curPos++] = new float[] {  +lenX,  -lenY, i-lenZ };
+    for(int i=1; i<Z-1; i++) pos[curPos++] = new float[] {  +lenX,  +lenY, i-lenZ };
+
+    for(int y=1; y<Y-1; y++)
+      for(int z=1; z<Z-1; z++) pos[curPos++] = new float[] {+lenX,y-lenY,z-lenZ};
+
+    for(int y=1; y<Y-1; y++)
+      for(int z=1; z<Z-1; z++) pos[curPos++] = new float[] {-lenX,y-lenY,z-lenZ};
+
+    for(int x=1; x<X-1; x++)
+      for(int z=1; z<Z-1; z++) pos[curPos++] = new float[] {x-lenX,+lenY,z-lenZ};
+
+    for(int x=1; x<X-1; x++)
+      for(int z=1; z<Z-1; z++) pos[curPos++] = new float[] {x-lenX,-lenY,z-lenZ};
+
+    for(int x=1; x<X-1; x++)
+      for(int y=1; y<Y-1; y++) pos[curPos++] = new float[] {x-lenX,y-lenY,+lenZ};
+
+    for(int x=1; x<X-1; x++)
+      for(int y=1; y<Y-1; y++) pos[curPos++] = new float[] {x-lenX,y-lenY,-lenZ};
+
+    return pos;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected Static4D getQuat(int cubit, int[] numLayers)
+    {
+    if( mQuats ==null ) initializeQuats();
+
+    int centerNum = getCenterNum(cubit,numLayers);
+
+    switch(centerNum)
+      {
+      case 0 : return mQuats[13];
+      case 1 : return mQuats[12];
+      case 2 : return mQuats[ 8];
+      case 3 : return mQuats[ 9];
+      case 4 : return mQuats[ 0];
+      case 5 : return mQuats[ 1];
+      default: return mQuats[ 0];
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected int getNumCubitVariants(int[] numLayers)
+    {
+    return numLayers[0]>2 ? 2:1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected int getCubitVariant(int cubit, int[] numLayers)
+    {
+    return cubit < getNumCornersAndEdges(numLayers) ? 0 : 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected int getFaceColor(int cubit, int cubitface, int[] numLayers)
+    {
+    int centerNum = getCenterNum(cubit,numLayers);
+
+    if( centerNum<0 )
+      {
+      int axis = cubitface/2;
+      return CUBITS[cubit].getRotRow(axis) == (cubitface%2==0 ? (1<<(numLayers[axis]-1)):1) ? cubitface : NUM_TEXTURES;
+      }
+    else
+      {
+      return cubitface == 4 ? centerNum : NUM_TEXTURES;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected ObjectSticker retSticker(int face)
+    {
+    if( mStickers==null )
+      {
+      final float[][] STICKERS = new float[][]  { { -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f } };
+      final float radius = 0.10f;
+      final float[] radii = {radius,radius,radius,radius};
+      mStickers = new ObjectSticker[STICKERS.length];
+      float stroke = 0.08f;
+
+      if( ObjectControl.isInIconMode() )
+        {
+        int[] numLayers = getNumLayers();
+
+        switch(numLayers[0])
+          {
+          case 2: stroke*=1.8f; break;
+          case 3: stroke*=2.0f; break;
+          case 4: stroke*=2.1f; break;
+          default:stroke*=2.2f; break;
+          }
+        }
+
+      mStickers[0] = new ObjectSticker(STICKERS[0],null,radii,stroke );
+      }
+
+    return mStickers[face/NUM_FACE_COLORS];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected Static4D[] getQuats()
+    {
+    if( mQuats ==null ) initializeQuats();
+    return mQuats;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected float[][] getCuts(int[] numLayers)
+    {
+    if( mCuts==null )
+      {
+      mCuts = new float[3][];
+
+      int lenX = numLayers[0];
+
+      if( lenX>=2 )
+        {
+        mCuts[0] = new float[lenX-1];
+        for(int i=0; i<lenX-1; i++) mCuts[0][i] = (2-lenX)*0.5f + i;
+        }
+      else
+        {
+        mCuts[0] = null;
+        }
+
+      int lenY = numLayers[1];
+
+      if( lenY>=2 )
+        {
+        mCuts[1] = new float[lenY-1];
+        for(int i=0; i<lenY-1; i++) mCuts[1][i] = (2-lenY)*0.5f + i;
+        }
+      else
+        {
+        mCuts[1] = null;
+        }
+
+      int lenZ = numLayers[2];
+
+      if( lenZ>=2 )
+        {
+        mCuts[2] = new float[lenZ-1];
+        for(int i=0; i<lenZ-1; i++) mCuts[2][i] = (2-lenZ)*0.5f + i;
+        }
+      else
+        {
+        mCuts[2] = null;
+        }
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int[] numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      mLayerRotatable = new boolean[numAxis][];
+
+      for(int i=0; i<numAxis; i++)
+        {
+        mLayerRotatable[i] = new boolean[numLayers[i]];
+        for(int j=0; j<numLayers[i]; j++) mLayerRotatable[i][j] = true;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected int getSolvedFunctionIndex()
+    {
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected int getNumStickerTypes(int[] numLayers)
+    {
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected int getNumCubitFaces()
+    {
+    return 6;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public Static3D[] getRotationAxis()
+    {
+    return ROT_AXIS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  public Movement getMovement()
+    {
+    if( mMovement==null )
+      {
+      int[] numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+      mMovement = new Movement6(ROT_AXIS,mCuts,mLayerRotatable,numLayers[0],TYPE_NOT_SPLIT,ENABLED);
+      }
+    return mMovement;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[] getBasicAngle()
+    {
+    if( mBasicAngle==null )
+      {
+      int[] num = getNumLayers();
+      int x = num[1]==num[2] ? 4 : 2;
+      int y = num[0]==num[2] ? 4 : 2;
+      int z = num[0]==num[1] ? 4 : 2;
+
+      mBasicAngle = new int[] { x,y,z };
+      }
+    return mBasicAngle;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  public ObjectType intGetObjectType(int[] numLayers)
+    {
+    switch(numLayers[0])
+      {
+      case 2: return numLayers[1]==2 ? ObjectType.CUBE_2 : ObjectType.CU_223;
+      case 3: return numLayers[1]==3 ? ObjectType.CUBE_3 : ObjectType.CU_334;
+      case 4: return ObjectType.CUBE_4;
+      case 5: return ObjectType.CUBE_5;
+      }
+
+    return ObjectType.CUBE_3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getObjectName(int[] numLayers)
+    {
+    switch(numLayers[0])
+      {
+      case 2: return R.string.cube2;
+      case 3: return R.string.cube3;
+      case 4: return R.string.cube4;
+      case 5: return R.string.cube5;
+      }
+    return R.string.cube3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getInventor(int[] numLayers)
+    {
+    switch(numLayers[0])
+      {
+      case 2: return R.string.cube2_inventor;
+      case 3: return R.string.cube3_inventor;
+      case 4: return R.string.cube4_inventor;
+      case 5: return R.string.cube5_inventor;
+      }
+    return R.string.cube3_inventor;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getComplexity(int[] numLayers)
+    {
+    switch(numLayers[0])
+      {
+      case 2: return 4;
+      case 3: return 6;
+      case 4: return 8;
+      case 5: return 10;
+      }
+    return 6;
+    }
+}
