commit 68f6046c9745974701718e1f9e54fdc44aee4530
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon Oct 5 00:36:42 2020 +0100

    Add the Redi Cube: part1.

diff --git a/src/main/java/org/distorted/objects/CubitFactory.java b/src/main/java/org/distorted/objects/CubitFactory.java
index 05da097d..13b79345 100644
--- a/src/main/java/org/distorted/objects/CubitFactory.java
+++ b/src/main/java/org/distorted/objects/CubitFactory.java
@@ -19,6 +19,7 @@
 
 package org.distorted.objects;
 
+import org.distorted.library.effect.VertexEffect;
 import org.distorted.library.effect.VertexEffectDeform;
 import org.distorted.library.effect.VertexEffectMove;
 import org.distorted.library.effect.VertexEffectRotate;
@@ -37,6 +38,7 @@ public class CubitFactory
   {
   private static final float SQ2 = (float)Math.sqrt(2);
   private static final float SQ3 = (float)Math.sqrt(3);
+  private static final float SQ6 = (float)Math.sqrt(6);
 
   private static CubitFactory mThis;
 
@@ -1063,6 +1065,156 @@ public class CubitFactory
 
     mesh.mergeEffComponents();
 
+    return mesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Redi cube
+
+  private void roundCorners(MeshBase mesh)
+    {
+    float cX = 0.0f;
+    float cY = -0.75f;
+    float cZ = -0.75f;
+
+    float d1=-0.05f;
+    float d2=-0.05f;
+    float r0= 0.20f;
+    float r1= 0.20f;
+
+    Static3D vert0 = new Static3D( 0.5f, 0.0f, 0.0f);
+    Static3D vert1 = new Static3D(-0.5f, 0.0f, 0.0f);
+    Static3D vert2 = new Static3D( 0.0f, 0.0f,-1.5f);
+    Static3D vert3 = new Static3D( 0.0f,-1.5f, 0.0f);
+
+    Static3D vec0 = new Static3D( d1*(+0.5f-cX), d1*(+0.0f-cY), d1*(+0.0f-cZ));
+    Static3D vec1 = new Static3D( d1*(-0.5f-cX), d1*(+0.0f-cY), d1*(+0.0f-cZ));
+    Static3D vec2 = new Static3D( d2*(+0.0f-cX), d2*(+0.0f-cY), d2*(-1.5f-cZ));
+    Static3D vec3 = new Static3D( d2*(+0.0f-cX), d2*(-1.5f-cY), d2*(+0.0f-cZ));
+
+    Static1D radius = new Static1D(0.5f);
+    Static4D reg0   = new Static4D(0,0,0,r0);
+    Static4D reg1   = new Static4D(0,0,0,r1);
+
+    VertexEffect effect0= new VertexEffectDeform(vec0,radius,vert0,reg0);
+    VertexEffect effect1= new VertexEffectDeform(vec1,radius,vert1,reg0);
+    VertexEffect effect2= new VertexEffectDeform(vec2,radius,vert2,reg1);
+    VertexEffect effect3= new VertexEffectDeform(vec3,radius,vert3,reg1);
+
+    mesh.apply(effect0);
+    mesh.apply(effect1);
+    mesh.apply(effect2);
+    mesh.apply(effect3);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MeshBase createRediEdgeMesh()
+    {
+    final int MESHES=6;
+
+    float D = 0.02f;
+    float F = 0.25f;
+
+    float[] bands0 = { 1.0f    , 0,
+                       1.0f-2*D, D*0.25f,
+                       1.0f-4*D, D*0.35f,
+                       1.0f-8*D, D*0.6f,
+                       0.60f   , D*1.0f,
+                       0.30f   , D*1.375f,
+                       0.0f    , D*1.4f };
+
+    float[] vertices0 = { -F,+F, -F,-F, 0, -2*F, +F,-F, +F,+F };
+
+    MeshBase[] meshes = new MeshPolygon[MESHES];
+    meshes[0] = new MeshPolygon(vertices0, bands0, 2, 2);
+    meshes[0].setEffectAssociation(0,1,0);
+    meshes[1] = meshes[0].copy(true);
+    meshes[1].setEffectAssociation(0,2,0);
+
+    float[] bands1 = { 1.0f    , 0,
+                       0.90f   , D,
+                       0.0f    , D };
+
+    float[] vertices1 = { -F/2, +F/2, -F/2, -1.5f*F, 1.5f*F, +F/2 };
+
+    meshes[2] = new MeshPolygon(vertices1, bands1, 1, 2);
+    meshes[2].setEffectAssociation(0,4,0);
+    meshes[3] = meshes[2].copy(true);
+    meshes[3].setEffectAssociation(0,8,0);
+
+    float[] bands2 = { 1.0f    , 0,
+                       0.90f   , D,
+                       0.0f    , D };
+
+    float[] vertices2 = { -0.5f*SQ2, SQ6/8, -0.75f*SQ2, -SQ6/8, +0.75f*SQ2, -SQ6/8, +0.5f*SQ2, SQ6/8 };
+
+    meshes[4] = new MeshPolygon(vertices2, bands2, 1, 1);
+    meshes[4].setEffectAssociation(0,16,0);
+    meshes[5] = meshes[4].copy(true);
+    meshes[5].setEffectAssociation(0,32,0);
+
+    MeshBase mesh = new MeshJoined(meshes);
+
+    Static3D move0 = new Static3D(0.0f, -0.5f, 0.0f);
+    Static3D move1 = new Static3D(0.25f, -0.25f, 0.0f);
+    Static3D move2 = new Static3D(0.5f, 0.0f, 0.0f);
+    Static3D move3 = new Static3D(0.0f, (SQ3-6)/8, (SQ3-6)/8);
+    Static3D flipZ = new Static3D(1,1,-1);
+    Static3D flipX = new Static3D(-1,1,1);
+    Static3D scale = new Static3D(2,2,2);
+    Static3D cent0 = new Static3D(0,0, 0);
+    Static3D cent1 = new Static3D(0,0, -1.5f);
+    Static3D axisX = new Static3D(1,0, 0);
+    Static3D axisY = new Static3D(0,1, 0);
+    Static3D axis  = new Static3D(0,SQ2/2,-SQ2/2);
+    Static1D angle1= new Static1D(90);
+    Static1D angle2= new Static1D(45);
+    Static1D angle3= new Static1D( (float)(180/Math.PI*Math.acos(SQ3/3)) );
+
+    VertexEffect effect0 = new VertexEffectScale(scale);
+    VertexEffect effect1 = new VertexEffectMove(move0);
+    VertexEffect effect2 = new VertexEffectScale(flipZ);
+    VertexEffect effect3 = new VertexEffectRotate(angle1,axisX,cent0);
+    VertexEffect effect4 = new VertexEffectMove(move1);
+    VertexEffect effect5 = new VertexEffectRotate(angle1,axisY,cent0);
+    VertexEffect effect6 = new VertexEffectMove(move2);
+    VertexEffect effect7 = new VertexEffectScale(flipX);
+    VertexEffect effect8 = new VertexEffectRotate(angle2,axisX,cent0);
+    VertexEffect effect9 = new VertexEffectMove(move3);
+    VertexEffect effect10= new VertexEffectRotate(angle3,axis ,cent1);
+    VertexEffect effect11= new VertexEffectScale(flipX);
+
+    effect0.setMeshAssociation(15,-1);  // meshes 0,1,2,3
+    effect1.setMeshAssociation( 3,-1);  // meshes 0,1
+    effect2.setMeshAssociation( 2,-1);  // mesh 1
+    effect3.setMeshAssociation( 2,-1);  // mesh 1
+    effect4.setMeshAssociation(12,-1);  // meshes 2,3
+    effect5.setMeshAssociation(60,-1);  // meshes 2,3,4,5
+    effect6.setMeshAssociation(12,-1);  // meshes 2,3
+    effect7.setMeshAssociation( 8,-1);  // mesh 3
+    effect8.setMeshAssociation(48,-1);  // meshes 4,5
+    effect9.setMeshAssociation(48,-1);  // meshes 4,5
+    effect10.setMeshAssociation(48,-1); // meshes 4,5
+    effect11.setMeshAssociation(32,-1); // mesh 5
+
+    mesh.apply(effect0);
+    mesh.apply(effect1);
+    mesh.apply(effect2);
+    mesh.apply(effect3);
+    mesh.apply(effect4);
+    mesh.apply(effect5);
+    mesh.apply(effect6);
+    mesh.apply(effect7);
+    mesh.apply(effect8);
+    mesh.apply(effect9);
+    mesh.apply(effect10);
+    mesh.apply(effect11);
+
+    roundCorners(mesh);
+
+    mesh.mergeEffComponents();
+
     return mesh;
     }
   }
diff --git a/src/main/java/org/distorted/objects/MovementRedi.java b/src/main/java/org/distorted/objects/MovementRedi.java
new file mode 100644
index 00000000..a91a47d5
--- /dev/null
+++ b/src/main/java/org/distorted/objects/MovementRedi.java
@@ -0,0 +1,144 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.objects;
+
+import org.distorted.library.type.Static3D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class MovementRedi extends Movement
+{
+  static final float DIST3D = 0.25f;
+  static final float DIST2D = 0.25f;
+
+  static final Static3D[] FACE_AXIS = new Static3D[]
+         {
+           new Static3D(1,0,0), new Static3D(-1,0,0),
+           new Static3D(0,1,0), new Static3D(0,-1,0),
+           new Static3D(0,0,1), new Static3D(0,0,-1)
+         };
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MovementRedi()
+    {
+    super(TwistyRedi.ROT_AXIS, FACE_AXIS, DIST3D, DIST2D);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int computeRowFromOffset(int face, int size, float offset)
+    {
+    return offset<DIST2D ? 0:1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float returnRotationFactor(int size, int row)
+    {
+    return 1.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// _____________
+// |  \  0  /  |
+// |   \   /   |
+// | 3 |   | 1 |
+// |   /   \   |
+// |  /  2  \  |
+// -------------
+
+  private int getQuarter(float[] touchPoint)
+    {
+    boolean p0 = touchPoint[1] >= touchPoint[0];
+    boolean p1 = touchPoint[1] >=-touchPoint[0];
+
+    if( p0 )  return p1 ? 0:3;
+    else      return p1 ? 1:2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean isInsideFace(int face, float[] p)
+    {
+    return ( p[0]<=DIST2D && p[0]>=-DIST2D && p[1]<=DIST2D && p[1]>=-DIST2D );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void computeEnabledAxis(int face, float[] touchPoint, int[] enabled)
+    {
+    enabled[0] = 2;
+
+    int quarter = getQuarter(touchPoint);
+
+    switch(face)
+      {
+      case 0: switch(quarter)
+                {
+                case 0: enabled[1]=0; enabled[2]=1; break;
+                case 1: enabled[1]=3; enabled[2]=1; break;
+                case 2: enabled[1]=2; enabled[2]=3; break;
+                case 3: enabled[1]=0; enabled[2]=2; break;
+                }
+              break;
+      case 1: switch(quarter)
+                {
+                case 0: enabled[1]=2; enabled[2]=3; break;
+                case 1: enabled[1]=3; enabled[2]=1; break;
+                case 2: enabled[1]=0; enabled[2]=1; break;
+                case 3: enabled[1]=0; enabled[2]=2; break;
+                }
+              break;
+      case 2: switch(quarter)
+                {
+                case 0: enabled[1]=1; enabled[2]=2; break;
+                case 1: enabled[1]=0; enabled[2]=1; break;
+                case 2: enabled[1]=0; enabled[2]=3; break;
+                case 3: enabled[1]=2; enabled[2]=3; break;
+                }
+              break;
+      case 3: switch(quarter)
+                {
+                case 0: enabled[1]=1; enabled[2]=2; break;
+                case 1: enabled[1]=2; enabled[2]=3; break;
+                case 2: enabled[1]=0; enabled[2]=3; break;
+                case 3: enabled[1]=0; enabled[2]=1; break;
+                }
+              break;
+      case 4: switch(quarter)
+                {
+                case 0: enabled[1]=0; enabled[2]=3; break;
+                case 1: enabled[1]=0; enabled[2]=2; break;
+                case 2: enabled[1]=1; enabled[2]=2; break;
+                case 3: enabled[1]=1; enabled[2]=3; break;
+                }
+              break;
+      case 5: switch(quarter)
+                {
+                case 0: enabled[1]=1; enabled[2]=2; break;
+                case 1: enabled[1]=0; enabled[2]=2; break;
+                case 2: enabled[1]=0; enabled[2]=3; break;
+                case 3: enabled[1]=1; enabled[2]=3; break;
+                }
+              break;
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/objects/ObjectList.java b/src/main/java/org/distorted/objects/ObjectList.java
index 164fe9f8..f7ef9772 100644
--- a/src/main/java/org/distorted/objects/ObjectList.java
+++ b/src/main/java/org/distorted/objects/ObjectList.java
@@ -101,6 +101,15 @@ public enum ObjectList
          new MovementHelicopter(),
          2
        ),
+
+  REDI (
+         new int[][] {
+                       {3 , 11, R.raw.heli, R.drawable.ui_small_heli, R.drawable.ui_medium_heli, R.drawable.ui_big_heli, R.drawable.ui_huge_heli} ,
+                     },
+         TwistyRedi.class,
+         new MovementRedi(),
+         3
+       ),
   ;
 
   public static final int NUM_OBJECTS = values().length;
@@ -480,6 +489,7 @@ public enum ObjectList
       case 4: return new TwistyDino4     (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 5: return new TwistySkewb     (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 6: return new TwistyHelicopter(size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case 7: return new TwistyRedi      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       }
 
     return null;
diff --git a/src/main/java/org/distorted/objects/TwistyObject.java b/src/main/java/org/distorted/objects/TwistyObject.java
index 3c490cf6..28395efe 100644
--- a/src/main/java/org/distorted/objects/TwistyObject.java
+++ b/src/main/java/org/distorted/objects/TwistyObject.java
@@ -69,7 +69,7 @@ public abstract class TwistyObject extends DistortedNode
   private static final float MAX_SIZE_CHANGE = 1.35f;
   private static final float MIN_SIZE_CHANGE = 0.8f;
 
-  private static boolean mCreateFromDMesh = true;
+  private static boolean mCreateFromDMesh = false;
 
   private static final Static3D CENTER = new Static3D(0,0,0);
   private static final int POST_ROTATION_MILLISEC = 500;
diff --git a/src/main/java/org/distorted/objects/TwistyRedi.java b/src/main/java/org/distorted/objects/TwistyRedi.java
new file mode 100644
index 00000000..babfa7ea
--- /dev/null
+++ b/src/main/java/org/distorted/objects/TwistyRedi.java
@@ -0,0 +1,424 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.objects;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import org.distorted.library.effect.MatrixEffectQuaternion;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.mesh.MeshSquare;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.main.RubikSurfaceView;
+
+import java.util.Random;
+
+import static org.distorted.effects.scramble.ScrambleEffect.START_AXIS;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class TwistyRedi extends TwistyObject
+{
+  private static final float SQ2 = (float)Math.sqrt(2);
+  private static final float SQ3 = (float)Math.sqrt(3);
+
+  private static final int FACES_PER_CUBIT =6;
+
+  // the four rotation axis of a RubikRedi. Must be normalized.
+  static final Static3D[] ROT_AXIS = new Static3D[]
+         {
+           new Static3D(+SQ3/3,+SQ3/3,+SQ3/3),
+           new Static3D(+SQ3/3,+SQ3/3,-SQ3/3),
+           new Static3D(+SQ3/3,-SQ3/3,+SQ3/3),
+           new Static3D(+SQ3/3,-SQ3/3,-SQ3/3)
+         };
+
+  private static final int[] FACE_COLORS = new int[]
+         {
+           COLOR_YELLOW, COLOR_WHITE,
+           COLOR_BLUE  , COLOR_GREEN,
+           COLOR_RED   , COLOR_BROWN
+         };
+
+  // All legal rotation quats of a RubikRedi
+  private static final Static4D[] QUATS = 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(  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 )
+         };
+
+  private static final float DIST_CORNER = 1.0f;
+  private static final float DIST_EDGE   = 1.5f;
+
+  // centers of the 8 corners + 12 edges ( i.e. of the all 20 cubits)
+  private static final Static3D[] CENTERS = new Static3D[]
+         {
+           new Static3D( DIST_CORNER, DIST_CORNER, DIST_CORNER ),
+           new Static3D( DIST_CORNER, DIST_CORNER,-DIST_CORNER ),
+           new Static3D( DIST_CORNER,-DIST_CORNER, DIST_CORNER ),
+           new Static3D( DIST_CORNER,-DIST_CORNER,-DIST_CORNER ),
+           new Static3D(-DIST_CORNER, DIST_CORNER, DIST_CORNER ),
+           new Static3D(-DIST_CORNER, DIST_CORNER,-DIST_CORNER ),
+           new Static3D(-DIST_CORNER,-DIST_CORNER, DIST_CORNER ),
+           new Static3D(-DIST_CORNER,-DIST_CORNER,-DIST_CORNER ),
+
+           new Static3D(      0.0f, DIST_EDGE, DIST_EDGE ),
+           new Static3D( DIST_EDGE,      0.0f, DIST_EDGE ),
+           new Static3D(      0.0f,-DIST_EDGE, DIST_EDGE ),
+           new Static3D(-DIST_EDGE,      0.0f, DIST_EDGE ),
+           new Static3D( DIST_EDGE, DIST_EDGE,      0.0f ),
+           new Static3D( DIST_EDGE,-DIST_EDGE,      0.0f ),
+           new Static3D(-DIST_EDGE,-DIST_EDGE,      0.0f ),
+           new Static3D(-DIST_EDGE, DIST_EDGE,      0.0f ),
+           new Static3D(      0.0f, DIST_EDGE,-DIST_EDGE ),
+           new Static3D( DIST_EDGE,      0.0f,-DIST_EDGE ),
+           new Static3D(      0.0f,-DIST_EDGE,-DIST_EDGE ),
+           new Static3D(-DIST_EDGE,      0.0f,-DIST_EDGE )
+         };
+
+  // Colors of the faces of cubits.
+  // YELLOW 0 WHITE 1 BLUE 2 GREEN 3 RED 4  BROWN 5
+  // YELLOW 6 WHITE 7 BLUE 8 GREEN 9 RED 10 BROWN 11
+  private static final int[][] mFaceMap = new int[][]
+         {
+           {  0,12, 2,12, 4,12 },
+           {  0,12, 5,12, 2,12 },
+           {  0,12, 4,12, 3,12 },
+           {  0,12, 3,12, 5,12 },
+           {  4,12, 2,12, 1,12 },
+           {  1,12, 2,12, 5,12 },
+           {  1,12, 3,12, 4,12 },
+           {  5,12, 3,12, 1,12 },
+
+           { 10, 8,12,12,12,12 },
+           {  6,10,12,12,12,12 },
+           { 10, 9,12,12,12,12 },
+           {  7,10,12,12,12,12 },
+           {  8, 6,12,12,12,12 },
+           {  9, 6,12,12,12,12 },
+           {  9, 7,12,12,12,12 },
+           {  8, 7,12,12,12,12 },
+           { 11, 8,12,12,12,12 },
+           {  6,11,12,12,12,12 },
+           { 11, 9,12,12,12,12 },
+           {  7,11,12,12,12,12 },
+         };
+
+  private static MeshBase mCornerMesh, mEdgeMesh;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  TwistyRedi(int size, Static4D quat, DistortedTexture texture, MeshSquare mesh,
+             DistortedEffects effects, int[][] moves, Resources res, int scrWidth)
+    {
+    super(size, 60, quat, texture, mesh, effects, moves, ObjectList.REDI, res, scrWidth);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float getScreenRatio()
+    {
+    return 0.50f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Static4D[] getQuats()
+    {
+    return QUATS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumFaces()
+    {
+    return FACE_COLORS.length;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumStickerTypes()
+    {
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float getBasicStep()
+    {
+    return SQ3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumCubitFaces()
+    {
+    return FACES_PER_CUBIT;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Static3D[] getCubitPositions(int size)
+    {
+    return CENTERS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private Static4D getQuat(int cubit)
+    {
+    switch(cubit)
+      {
+      case  0: return QUATS[0];                          //  unit quat
+      case  1: return new Static4D( SQ2/2,0,0,SQ2/2);    //  90 along X
+      case  2: return new Static4D(-SQ2/2,0,0,SQ2/2);    // -90 along X
+      case  3: return QUATS[1];                          // 180 along X
+      case  4: return new Static4D(0, SQ2/2,0,SQ2/2);    //  90 along Y
+      case  5: return QUATS[2];                          // 180 along Y
+      case  6: return QUATS[3];                          // 180 along Z
+      case  7: return new Static4D(SQ2/2,0,-SQ2/2,0);    // 180 along (SQ2/2,0,-SQ2/2)
+
+      case  8: return QUATS[0];
+      case  9: return QUATS[5];
+      case 10: return QUATS[3];
+      case 11: return QUATS[11];
+      case 12: return QUATS[4];
+      case 13: return QUATS[7];
+      case 14: return QUATS[9];
+      case 15: return QUATS[10];
+      case 16: return QUATS[2];
+      case 17: return QUATS[8];
+      case 18: return QUATS[1];
+      case 19: return QUATS[6];
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MeshBase createCubitMesh(int cubit)
+    {
+    MeshBase mesh;
+
+    if( cubit<8 )
+      {
+      if( mCornerMesh==null ) mCornerMesh = CubitFactory.getInstance().createCubeMesh(0);
+      mesh = mCornerMesh.copy(true);
+      }
+    else
+      {
+      if( mEdgeMesh==null ) mEdgeMesh = CubitFactory.getInstance().createRediEdgeMesh();
+      mesh = mEdgeMesh.copy(true);
+      }
+
+    MatrixEffectQuaternion quat = new MatrixEffectQuaternion( getQuat(cubit), new Static3D(0,0,0) );
+    mesh.apply(quat,0xffffffff,0);
+
+    return mesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getFaceColor(int cubit, int cubitface, int size)
+    {
+    return mFaceMap[cubit][cubitface];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void createFaceTexture(Canvas canvas, Paint paint, int face, int left)
+    {
+    int COLORS = FACE_COLORS.length;
+
+    if( face<COLORS )
+      {
+      float F =  0.5f;
+      float R = 0.10f;
+      float S = 0.10f;
+      float[] vertices = { -F,-F, +F,-F, +F,+F, -F,+F};
+
+      drawRoundedPolygon(canvas, paint, left, vertices, S, FACE_COLORS[face], R);
+      }
+    else
+      {
+      float F = 0.25f;
+      float R = 0.05f;
+      float S = 0.05f;
+      float[] vertices = { -F,+F, -F,-F, 0, -2*F, +F,-F, +F,+F };
+
+      drawRoundedPolygon(canvas, paint, left, vertices, S, FACE_COLORS[face-COLORS], R);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float returnMultiplier()
+    {
+    return 2.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[] getRowChances()
+    {
+    float[] chances = new float[3];
+
+    chances[0] = 0.5f;
+    chances[1] = 0.5f;
+    chances[2] = 1.0f;
+
+    return chances;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public Static3D[] getRotationAxis()
+    {
+    return ROT_AXIS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getBasicAngle()
+    {
+    return 3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int randomizeNewRotAxis(Random rnd, int oldRotAxis)
+    {
+    int numAxis = ROTATION_AXIS.length;
+
+    if( oldRotAxis == START_AXIS )
+      {
+      return rnd.nextInt(numAxis);
+      }
+    else
+      {
+      int newVector = rnd.nextInt(numAxis-1);
+      return (newVector>=oldRotAxis ? newVector+1 : newVector);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int randomizeNewRow(Random rnd, int oldRotAxis, int oldRow, int newRotAxis)
+    {
+    return (oldRotAxis==START_AXIS) ? (rnd.nextFloat()<=0.5f ? 0:2) : (oldRotAxis+newRotAxis==3 ? 2-oldRow : oldRow);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// remember about the double cover or unit quaternions!
+
+  private int mulQuat(int q1, int q2)
+    {
+    Static4D result = RubikSurfaceView.quatMultiply(QUATS[q1],QUATS[q2]);
+
+    float rX = result.get0();
+    float rY = result.get1();
+    float rZ = result.get2();
+    float rW = result.get3();
+
+    final float MAX_ERROR = 0.1f;
+    float dX,dY,dZ,dW;
+
+    for(int i=0; i<QUATS.length; i++)
+      {
+      dX = QUATS[i].get0() - rX;
+      dY = QUATS[i].get1() - rY;
+      dZ = QUATS[i].get2() - rZ;
+      dW = QUATS[i].get3() - rW;
+
+      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
+          dY<MAX_ERROR && dY>-MAX_ERROR &&
+          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
+          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
+
+      dX = QUATS[i].get0() + rX;
+      dY = QUATS[i].get1() + rY;
+      dZ = QUATS[i].get2() + rZ;
+      dW = QUATS[i].get3() + rW;
+
+      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
+          dY<MAX_ERROR && dY>-MAX_ERROR &&
+          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
+          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// The Redi is solved if and only if:
+//
+// ??
+
+  public boolean isSolved()
+    {
+    int q = CUBITS[0].mQuatIndex;
+
+    if ( CUBITS[1].mQuatIndex == q &&
+         CUBITS[2].mQuatIndex == q &&
+         CUBITS[3].mQuatIndex == q &&
+         CUBITS[4].mQuatIndex == q &&
+         CUBITS[5].mQuatIndex == q &&
+         CUBITS[6].mQuatIndex == q &&
+         CUBITS[7].mQuatIndex == q  )
+      {
+
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// only needed for solvers - there are no Redi solvers ATM)
+
+  public String retObjectString()
+    {
+    return "";
+    }
+}
diff --git a/src/main/java/org/distorted/states/RubikStatePlay.java b/src/main/java/org/distorted/states/RubikStatePlay.java
index 4e8eb183..183e6469 100644
--- a/src/main/java/org/distorted/states/RubikStatePlay.java
+++ b/src/main/java/org/distorted/states/RubikStatePlay.java
@@ -613,7 +613,6 @@ public class RubikStatePlay extends RubikStateAbstract implements RubikPreRender
 
     mPlayLayout.removeAllViews();
 
-    int realSize= ObjectList.getSizeIndex(mObject,mSize);
     RubikScores scores = RubikScores.getInstance();
 
     for(int i=0; i<maxLevel; i++)
@@ -624,7 +623,7 @@ public class RubikStatePlay extends RubikStateAbstract implements RubikPreRender
       button.setText(levels[i]);
       button.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
 
-      int icon = scores.isSolved(mObject, realSize, i) ? R.drawable.ui_solved : R.drawable.ui_notsolved;
+      int icon = scores.isSolved(mObject, sizeIndex, i) ? R.drawable.ui_solved : R.drawable.ui_notsolved;
       button.setCompoundDrawablesWithIntrinsicBounds(icon,0,0,0);
 
       button.setOnClickListener( new View.OnClickListener()
