commit 198c5bf0701a017a8f5e0e825b79a32f5214ebf8
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri Oct 1 23:26:59 2021 +0200

    Move more code to objectlib.

diff --git a/build.gradle b/build.gradle
index dfde2969..1f40b97c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,4 +25,5 @@ android {
 dependencies {
     api project(':distorted-library')
     implementation 'com.google.firebase:firebase-crashlytics:18.2.1'
+    implementation 'androidx.appcompat:appcompat:1.3.1'
 }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/objectlib/helpers/BlockController.java b/src/main/java/org/distorted/objectlib/helpers/BlockController.java
new file mode 100644
index 00000000..bacd08e4
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/BlockController.java
@@ -0,0 +1,276 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.helpers;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import java.lang.ref.WeakReference;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.distorted.library.message.EffectMessageSender;
+import org.distorted.objectlib.BuildConfig;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BlockController
+  {
+  public static final int RUBIK_PLACE_0 =0;
+  public static final int RUBIK_PLACE_1 =1;
+  public static final int RUBIK_PLACE_2 =2;
+  public static final int RUBIK_PLACE_3 =3;
+  public static final int RUBIK_PLACE_4 =4;
+  public static final int TUTORIAL_PLACE_0 =10;
+  public static final int TUTORIAL_PLACE_1 =11;
+  public static final int TUTORIAL_PLACE_2 =12;
+  public static final int TUTORIAL_PLACE_3 =13;
+  public static final int TUTORIAL_PLACE_4 =14;
+  public static final int CONTROL_PLACE_0 =20;
+  public static final int CONTROL_PLACE_1 =21;
+  public static final int MOVES_PLACE_0 =30;
+
+  private static final long THRESHHOLD_0 =  3000;
+  private static final long THRESHHOLD_1 = 25000;
+  private static final long THRESHHOLD_2 =  5000;
+  private static final long THRESHHOLD_3 = 45000;
+
+  private static long mPauseTime, mResumeTime;
+
+  private long mTouchBlockTime, mUIBlockTime;
+  private int mLastTouchPlace, mLastUIPlace;
+
+  private final WeakReference<TwistyActivity> mAct;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void onPause()
+    {
+    mPauseTime = System.currentTimeMillis();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void onResume()
+    {
+    mResumeTime = System.currentTimeMillis();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public BlockController(TwistyActivity act)
+    {
+    mAct = new WeakReference<>(act);
+
+    Timer timer = new Timer();
+
+    timer.scheduleAtFixedRate(new TimerTask()
+      {
+      @Override
+      public void run()
+        {
+        act.runOnUiThread(new Runnable()
+          {
+          @Override
+          public void run()
+            {
+            checkingThread();
+            }
+          });
+        }
+      }, 0, 1000);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// RUBIK_PLACE_3 and TUTORIAL_PLACE_3 are scrambles, those can take up to 20 seconds.
+// RUBIK_PLACE_4 and TUTORIAL_PLACE_4 are solves, those can (maybe) sometimes take more than 3 seconds.
+// CONTROL_PLACE_* are the visual tutorials, could take up to 45 seconds.
+
+  private long getThreshhold(int last)
+    {
+    switch(last)
+      {
+      case RUBIK_PLACE_3   :
+      case TUTORIAL_PLACE_3: return THRESHHOLD_1;
+      case RUBIK_PLACE_4   :
+      case TUTORIAL_PLACE_4: return THRESHHOLD_2;
+      case CONTROL_PLACE_0 :
+      case CONTROL_PLACE_1 : return THRESHHOLD_3;
+      default              : return THRESHHOLD_0;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void checkingThread()
+    {
+    long now = System.currentTimeMillis();
+    long touchThreshhold = getThreshhold(mLastTouchPlace);
+    long touchBlocked = now-mTouchBlockTime;
+
+    if( mTouchBlockTime>mPauseTime && touchBlocked>touchThreshhold )
+      {
+      boolean running = EffectMessageSender.isRunning();
+
+      if( !running )
+        {
+        reportThreadProblem();
+        EffectMessageSender.restartThread();
+        }
+      else
+        {
+        TwistyActivity act = mAct.get();
+        boolean reallyBlocked = true;
+
+        if( act!=null )
+          {
+          TwistyPreRender pre = act.getTwistyPreRender();
+          if( pre!=null )
+            {
+            reallyBlocked = pre.isTouchBlocked();
+            pre.unblockTouch();
+            }
+          }
+
+        reportTouchProblem(touchBlocked, reallyBlocked);
+        }
+      }
+
+    long uiThreshhold = getThreshhold(mLastUIPlace);
+    long uiBlocked = now-mUIBlockTime;
+
+    if( mUIBlockTime>mPauseTime && uiBlocked>uiThreshhold )
+      {
+      boolean running = EffectMessageSender.isRunning();
+
+      if( !running )
+        {
+        reportThreadProblem();
+        EffectMessageSender.restartThread();
+        }
+      else
+        {
+        TwistyActivity act = mAct.get();
+        boolean reallyBlocked = true;
+
+        if( act!=null )
+          {
+          TwistyPreRender pre = act.getTwistyPreRender();
+          if( pre!=null )
+            {
+            reallyBlocked = !pre.isUINotBlocked();
+            pre.unblockUI();
+            }
+          }
+
+        reportUIProblem(uiBlocked, reallyBlocked);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportUIProblem(long time, boolean reallyBlocked)
+    {
+    String error = "UI BLOCK "+mLastUIPlace+" blocked for "+time+" milliseconds ("+reallyBlocked+")";
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("D", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , mPauseTime );
+      crashlytics.setCustomKey("resume", mResumeTime );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportTouchProblem(long time, boolean reallyBlocked)
+    {
+    String error = "TOUCH BLOCK "+mLastTouchPlace+" blocked for "+time+" milliseconds ("+reallyBlocked+")";
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("D", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , mPauseTime );
+      crashlytics.setCustomKey("resume", mResumeTime );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportThreadProblem()
+    {
+    String error = EffectMessageSender.reportState();
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("D", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , mPauseTime );
+      crashlytics.setCustomKey("resume", mResumeTime );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void touchBlocked(int place)
+    {
+    mTouchBlockTime = System.currentTimeMillis();
+    mLastTouchPlace = place;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void uiBlocked(int place)
+    {
+    mUIBlockTime = System.currentTimeMillis();
+    mLastUIPlace = place;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void touchUnblocked()
+    {
+    mTouchBlockTime = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void uiUnblocked()
+    {
+    mUIBlockTime = 0;
+    }
+  }
diff --git a/src/main/java/org/distorted/objectlib/helpers/FactoryCubit.java b/src/main/java/org/distorted/objectlib/helpers/FactoryCubit.java
new file mode 100644
index 00000000..1ec1718e
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/FactoryCubit.java
@@ -0,0 +1,941 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.objectlib.helpers;
+
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.effect.MatrixEffectQuaternion;
+import org.distorted.library.effect.MatrixEffectScale;
+import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.effect.VertexEffectDeform;
+import org.distorted.library.main.QuatHelper;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.mesh.MeshJoined;
+import org.distorted.library.mesh.MeshPolygon;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class FactoryCubit
+  {
+  private static final Static1D RADIUS = new Static1D(1);
+  private static FactoryCubit mThis;
+
+  private static final double[] mBuffer = new double[3];
+  private static final double[] mQuat1  = new double[4];
+  private static final double[] mQuat2  = new double[4];
+  private static final double[] mQuat3  = new double[4];
+  private static final double[] mQuat4  = new double[4];
+
+  private static class StickerCoords
+    {
+    double[] vertices;
+    }
+
+  private static class FaceTransform
+    {
+    int sticker;
+    double vx,vy,vz;
+    double scale;
+    double qx,qy,qz,qw;
+    boolean flip;
+    }
+
+  private static final ArrayList<FaceTransform> mNewFaceTransf = new ArrayList<>();
+  private static final ArrayList<FaceTransform> mOldFaceTransf = new ArrayList<>();
+  private static final ArrayList<StickerCoords> mStickerCoords = new ArrayList<>();
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private FactoryCubit()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static FactoryCubit getInstance()
+    {
+    if( mThis==null ) mThis = new FactoryCubit();
+
+    return mThis;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// H - height of the band in the middle
+// alpha - angle of the edge  [0,90]
+// dist - often in a polygon the distance from edge to center is not 1, but something else.
+// This is the distance.
+// K - where to begin the second, much more flat part of the band. [0,1]
+// N - number of bands. N>=3
+//
+// theory: two distinct parts to the band:
+// 1) (0,B) - steep
+// 2) (B,1) - flat
+//
+// In first part, we have y = g(x) ; in second - y = g(f(x)) where
+//
+// g(x) = sqrt( R^2 - (x-D)^2 ) - R*cos(alpha)
+// f(x) = ((D-B)/(1-B)*x + B*(1-D)/(1-B)
+// h(x) = R*(sin(alpha) - sin(x))
+// R = H/(1-cos(alpha))
+// D = H*sin(alpha)
+// B = h(K*alpha)
+//
+// The N points are taken at:
+//
+// 1) in the second part, there are K2 = (N-3)/3 such points
+// 2) in the first - K1 = (N-3) - K2
+// 3) also, the 3 points 0,B,1
+//
+// so we have the sequence A[i] of N points
+//
+// 0
+// h((i+1)*(1-K)*alpha/(K1+1)) (i=0,1,...,K1-1)
+// B
+// (1-B)*(i+1)/(K2+1) + B   (i=0,i,...,K2-1)
+// 1
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float f(float D, float B, float x)
+    {
+    return ((D-B)*x + B*(1-D))/(1-B);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float g(float R, float D, float x, float cosAlpha)
+    {
+    float d = x-D;
+    return (float)(Math.sqrt(R*R-d*d)-R*cosAlpha);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float h(float R, float sinAlpha, float x)
+    {
+    return R*(sinAlpha-(float)Math.sin(x));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean areColinear(double[][] vertices, int index1, int index2, int index3)
+    {
+    double x1 = vertices[index1][0];
+    double y1 = vertices[index1][1];
+    double z1 = vertices[index1][2];
+    double x2 = vertices[index2][0];
+    double y2 = vertices[index2][1];
+    double z2 = vertices[index2][2];
+    double x3 = vertices[index3][0];
+    double y3 = vertices[index3][1];
+    double z3 = vertices[index3][2];
+
+    double v1x = x2-x1;
+    double v1y = y2-y1;
+    double v1z = z2-z1;
+    double v2x = x3-x1;
+    double v2y = y3-y1;
+    double v2z = z3-z1;
+
+    double A = Math.sqrt( (v1x*v1x+v1y*v1y+v1z*v1z) / (v2x*v2x+v2y*v2y+v2z*v2z) );
+
+    return (v1x==A*v2x && v1y==A*v2y && v1z==A*v2z);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeNormalVector(double[][] vertices, int index1, int index2, int index3)
+    {
+    double x1 = vertices[index1][0];
+    double y1 = vertices[index1][1];
+    double z1 = vertices[index1][2];
+    double x2 = vertices[index2][0];
+    double y2 = vertices[index2][1];
+    double z2 = vertices[index2][2];
+    double x3 = vertices[index3][0];
+    double y3 = vertices[index3][1];
+    double z3 = vertices[index3][2];
+
+    double v1x = x2-x1;
+    double v1y = y2-y1;
+    double v1z = z2-z1;
+    double v2x = x3-x1;
+    double v2y = y3-y1;
+    double v2z = z3-z1;
+
+    mBuffer[0] = v1y*v2z - v2y*v1z;
+    mBuffer[1] = v1z*v2x - v2z*v1x;
+    mBuffer[2] = v1x*v2y - v2x*v1y;
+
+    double len = mBuffer[0]*mBuffer[0] + mBuffer[1]*mBuffer[1] + mBuffer[2]*mBuffer[2];
+    len = Math.sqrt(len);
+    mBuffer[0] /= len;
+    mBuffer[1] /= len;
+    mBuffer[2] /= len;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return quat1*quat2
+
+  private static void quatMultiply( double[] quat1, double[] quat2, double[] result )
+    {
+    double qx = quat1[0];
+    double qy = quat1[1];
+    double qz = quat1[2];
+    double qw = quat1[3];
+
+    double rx = quat2[0];
+    double ry = quat2[1];
+    double rz = quat2[2];
+    double rw = quat2[3];
+
+    result[0] = rw*qx - rz*qy + ry*qz + rx*qw;
+    result[1] = rw*qy + rz*qx + ry*qw - rx*qz;
+    result[2] = rw*qz + rz*qw - ry*qx + rx*qy;
+    result[3] = rw*qw - rz*qz - ry*qy - rx*qx;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void fitInSquare(FaceTransform info, double[][] vert3D)
+    {
+    double minX = Double.MAX_VALUE;
+    double maxX =-Double.MAX_VALUE;
+    double minY = Double.MAX_VALUE;
+    double maxY =-Double.MAX_VALUE;
+
+    for (double[] vert : vert3D)
+      {
+      double x = vert[0];
+      double y = vert[1];
+
+      if (x > maxX) maxX = x;
+      if (x < minX) minX = x;
+      if (y > maxY) maxY = y;
+      if (y < minY) minY = y;
+      }
+
+    minX = minX<0 ? -minX:minX;
+    maxX = maxX<0 ? -maxX:maxX;
+    minY = minY<0 ? -minY:minY;
+    maxY = maxY<0 ? -maxY:maxY;
+
+    double max1 = Math.max(minX,minY);
+    double max2 = Math.max(maxX,maxY);
+    double max3 = Math.max(max1,max2);
+
+    info.scale = max3/0.5;
+
+    int len = vert3D.length;
+    StickerCoords sInfo = new StickerCoords();
+    sInfo.vertices = new double[2*len];
+
+    for( int vertex=0; vertex<len; vertex++ )
+      {
+      sInfo.vertices[2*vertex  ] = vert3D[vertex][0] / info.scale;
+      sInfo.vertices[2*vertex+1] = vert3D[vertex][1] / info.scale;
+      }
+
+    mStickerCoords.add(sInfo);
+
+    info.sticker = mStickerCoords.size() -1;
+    info.flip = false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private FaceTransform constructNewTransform(final double[][] vert3D)
+    {
+    FaceTransform ft = new FaceTransform();
+
+    // compute center of gravity
+    ft.vx = 0.0f;
+    ft.vy = 0.0f;
+    ft.vz = 0.0f;
+    int len = vert3D.length;
+
+    for (double[] vert : vert3D)
+      {
+      ft.vx += vert[0];
+      ft.vy += vert[1];
+      ft.vz += vert[2];
+      }
+
+    ft.vx /= len;
+    ft.vy /= len;
+    ft.vz /= len;
+
+    // move all vertices so that their center of gravity is at (0,0,0)
+    for (int i=0; i<len; i++)
+      {
+      vert3D[i][0] -= ft.vx;
+      vert3D[i][1] -= ft.vy;
+      vert3D[i][2] -= ft.vz;
+      }
+
+    // find 3 non-colinear vertices
+    int foundIndex = -1;
+
+    for(int vertex=2; vertex<len; vertex++)
+      {
+      if( !areColinear(vert3D,0,1,vertex) )
+        {
+        foundIndex = vertex;
+        break;
+        }
+      }
+
+    // compute the normal vector
+    if( foundIndex==-1 )
+      {
+      throw new RuntimeException("all vertices colinear");
+      }
+
+    computeNormalVector(vert3D,0,1,foundIndex);
+
+    // rotate so that the normal vector becomes (0,0,1)
+    double axisX, axisY, axisZ;
+
+    if( mBuffer[0]!=0.0f || mBuffer[1]!=0.0f )
+      {
+      axisX = -mBuffer[1];
+      axisY =  mBuffer[0];
+      axisZ = 0.0f;
+
+      double axiLen = axisX*axisX + axisY*axisY;
+      axiLen = Math.sqrt(axiLen);
+      axisX /= axiLen;
+      axisY /= axiLen;
+      axisZ /= axiLen;
+      }
+    else
+      {
+      axisX = 0.0f;
+      axisY = 1.0f;
+      axisZ = 0.0f;
+      }
+
+    double cosTheta = mBuffer[2];
+    double sinTheta = Math.sqrt(1-cosTheta*cosTheta);
+    double sinHalfTheta = computeSinHalf(cosTheta);
+    double cosHalfTheta = computeCosHalf(sinTheta,cosTheta);
+
+    mQuat1[0] = axisX*sinHalfTheta;
+    mQuat1[1] = axisY*sinHalfTheta;
+    mQuat1[2] = axisZ*sinHalfTheta;
+    mQuat1[3] = cosHalfTheta;
+    mQuat2[0] =-axisX*sinHalfTheta;
+    mQuat2[1] =-axisY*sinHalfTheta;
+    mQuat2[2] =-axisZ*sinHalfTheta;
+    mQuat2[3] = cosHalfTheta;
+
+    for (double[] vert : vert3D)
+      {
+      quatMultiply(mQuat1, vert  , mQuat3);
+      quatMultiply(mQuat3, mQuat2, vert  );
+      }
+
+    // fit the whole thing in a square and remember the scale & 2D vertices
+    fitInSquare(ft, vert3D);
+
+    // remember the rotation
+    ft.qx =-mQuat1[0];
+    ft.qy =-mQuat1[1];
+    ft.qz =-mQuat1[2];
+    ft.qw = mQuat1[3];
+
+    return ft;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void rotateAllVertices(double[] result, int len, double[] vertices, double sin, double cos)
+    {
+    for(int i=0; i<len; i++)
+      {
+      result[2*i  ] = vertices[2*i  ]*cos - vertices[2*i+1]*sin;
+      result[2*i+1] = vertices[2*i  ]*sin + vertices[2*i+1]*cos;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private double computeScale(double[] v1, double[] v2, int v1i, int v2i)
+    {
+    double v1x = v1[2*v1i];
+    double v1y = v1[2*v1i+1];
+    double v2x = v2[2*v2i];
+    double v2y = v2[2*v2i+1];
+
+    double lenSq1 = v1x*v1x + v1y*v1y;
+    double lenSq2 = v2x*v2x + v2y*v2y;
+
+    return Math.sqrt(lenSq2/lenSq1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// valid for 0<angle<2*PI
+
+  private double computeSinHalf(double cos)
+    {
+    return Math.sqrt((1-cos)/2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// valid for 0<angle<2*PI
+
+  private double computeCosHalf(double sin, double cos)
+    {
+    double cosHalf = Math.sqrt((1+cos)/2);
+    return sin<0 ? -cosHalf : cosHalf;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeRotatedIndex(int oldVertex, int len, int rotatedVertex, boolean inverted)
+    {
+    int v = (rotatedVertex + (inverted? -oldVertex : oldVertex));
+    if( v>=len ) v-=len;
+    if( v< 0   ) v+=len;
+
+    return v;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean isScaledVersionOf(double[] newVert, double[] oldVert, int len, int vertex, boolean inverted)
+    {
+    int newZeroIndex = computeRotatedIndex(0,len,vertex,inverted);
+    double EPSILON = 0.001;
+    double scale = computeScale(newVert,oldVert,newZeroIndex,0);
+
+    for(int i=1; i<len; i++)
+      {
+      int index = computeRotatedIndex(i,len,vertex,inverted);
+
+      double horz = oldVert[2*i  ] - scale*newVert[2*index  ];
+      double vert = oldVert[2*i+1] - scale*newVert[2*index+1];
+
+      if( horz>EPSILON || horz<-EPSILON || vert>EPSILON || vert<-EPSILON ) return false;
+      }
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void mirrorAllVertices(double[] output, int len, double[] input)
+    {
+    for(int vertex=0; vertex<len; vertex++)
+      {
+      output[2*vertex  ] = input[2*vertex  ];
+      output[2*vertex+1] =-input[2*vertex+1];
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void correctInfo(FaceTransform info, double scale, double sin, double cos, int oldSticker, boolean flip)
+    {
+    mStickerCoords.remove(info.sticker);
+
+    info.flip    = flip;
+    info.sticker = oldSticker;
+    info.scale  *= scale;
+
+    mQuat1[0] = info.qx;
+    mQuat1[1] = info.qy;
+    mQuat1[2] = info.qz;
+    mQuat1[3] = info.qw;
+
+    double sinHalf = computeSinHalf(cos);
+    double cosHalf = computeCosHalf(sin,cos);
+
+    if( flip )
+      {
+      mQuat3[0] = 0.0f;
+      mQuat3[1] = 0.0f;
+      mQuat3[2] = sinHalf;
+      mQuat3[3] = cosHalf;
+
+      mQuat4[0] = 1.0;
+      mQuat4[1] = 0.0;
+      mQuat4[2] = 0.0;
+      mQuat4[3] = 0.0;
+
+      quatMultiply( mQuat3, mQuat4, mQuat2 );
+      }
+    else
+      {
+      mQuat2[0] = 0.0f;
+      mQuat2[1] = 0.0f;
+      mQuat2[2] = sinHalf;
+      mQuat2[3] = cosHalf;
+      }
+
+    quatMultiply( mQuat1, mQuat2, mQuat3 );
+
+    info.qx = mQuat3[0];
+    info.qy = mQuat3[1];
+    info.qz = mQuat3[2];
+    info.qw = mQuat3[3];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void printVert(double[] buffer)
+    {
+    int len = buffer.length/2;
+    String str = "";
+
+    for(int i=0; i<len; i++)
+      {
+      str += (" ("+buffer[2*i]+" , "+buffer[2*i+1]+" ) ");
+      }
+
+    android.util.Log.d("D", str);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean foundVertex(FaceTransform info, double[] buffer, int len, double[] newVert,
+                              double[] oldVert, double lenFirstOld, int oldSticker, boolean inverted)
+    {
+    for(int vertex=0; vertex<len; vertex++)
+      {
+      double newX = newVert[2*vertex  ];
+      double newY = newVert[2*vertex+1];
+      double lenIthNew = Math.sqrt(newX*newX + newY*newY);
+      double cos = QuatHelper.computeCos( oldVert[0], oldVert[1], newX, newY, lenIthNew, lenFirstOld);
+      double sin = QuatHelper.computeSin( oldVert[0], oldVert[1], newX, newY, lenIthNew, lenFirstOld);
+
+      rotateAllVertices(buffer,len,newVert,sin,cos);
+
+      if( isScaledVersionOf(buffer,oldVert,len,vertex,inverted) )
+        {
+        int newZeroIndex = computeRotatedIndex(0,len,vertex,inverted);
+        double scale = computeScale(oldVert,newVert,0,newZeroIndex);
+        correctInfo(info,scale,sin,cos,oldSticker,inverted);
+        return true;
+        }
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean successfullyCollapsedStickers(final FaceTransform newInfo, final FaceTransform oldInfo)
+    {
+    StickerCoords sNewInfo = mStickerCoords.get(newInfo.sticker);
+    StickerCoords sOldInfo = mStickerCoords.get(oldInfo.sticker);
+    double[] newVert = sNewInfo.vertices;
+    double[] oldVert = sOldInfo.vertices;
+    int oldLen = oldVert.length;
+    int newLen = newVert.length;
+
+    if( oldLen == newLen )
+      {
+      int oldSticker = oldInfo.sticker;
+      double[] buffer1 = new double[oldLen];
+      double lenFirstOld = Math.sqrt(oldVert[0]*oldVert[0] + oldVert[1]*oldVert[1]);
+      if( foundVertex(newInfo, buffer1, oldLen/2, newVert, oldVert, lenFirstOld, oldSticker, false) ) return true;
+      double[] buffer2 = new double[oldLen];
+      mirrorAllVertices(buffer2, newLen/2, newVert);
+      if( foundVertex(newInfo, buffer1, oldLen/2, buffer2, oldVert, lenFirstOld, oldSticker, true ) ) return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private double[][] constructVert(double[][] vertices, int[] index)
+    {
+    int len = index.length;
+    double[][] ret = new double[len][4];
+
+    for(int i=0; i<len; i++)
+      {
+      ret[i][0] = vertices[index[i]][0];
+      ret[i][1] = vertices[index[i]][1];
+      ret[i][2] = vertices[index[i]][2];
+      ret[i][3] = 1.0f;
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void prepareAndRoundCorners(MeshBase mesh, double[][] vertices,
+                                      float[][] corners, int[] cornerIndexes,
+                                      float[][] centers, int[] centerIndexes )
+    {
+    int lenV = vertices.length;
+    Static3D[] staticVert = new Static3D[1];
+    Static3D center = new Static3D(0,0,0);
+
+    for(int v=0; v<lenV; v++)
+      {
+      staticVert[0] = new Static3D( (float)vertices[v][0], (float)vertices[v][1], (float)vertices[v][2]);
+
+      int cent = centerIndexes[v];
+
+      if( cent>=0 )
+        {
+        center.set( centers[cent][0], centers[cent][1], centers[cent][2]);
+
+        int corn = cornerIndexes[v];
+
+        if( corn>=0 )
+          {
+          float strength = corners[corn][0];
+          float radius   = corners[corn][1];
+          roundCorners(mesh, center, staticVert, strength, radius);
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void correctComponents(MeshBase mesh, int numComponents)
+    {
+    int numTexToBeAdded = numComponents-mesh.getNumTexComponents();
+
+    mesh.mergeEffComponents();
+
+    for(int i=0; i<numTexToBeAdded; i++ ) mesh.addEmptyTexComponent();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void printTransform(FaceTransform f)
+    {
+    android.util.Log.e("D", "q=("+f.qx+", "+f.qy+", "+f.qz+", "+f.qw+") v=("
+                       +f.vx+", "+f.vy+", "+f.vz+") scale="+f.scale+" sticker="+f.sticker);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC
+
+  public float[] computeBands(float H, int alpha, float dist, float K, int N)
+    {
+    float[] bands = new float[2*N];
+
+    bands[0] = 1.0f;
+    bands[1] = 0.0f;
+
+    float beta = (float)Math.atan(dist*Math.tan(Math.PI*alpha/180));
+    float sinBeta = (float)Math.sin(beta);
+    float cosBeta = (float)Math.cos(beta);
+    float R = cosBeta<1.0f ? H/(1.0f-cosBeta) : 0.0f;
+    float D = R*sinBeta;
+    float B = h(R,sinBeta,K*beta);
+
+    if( D>1.0f )
+      {
+      for(int i=1; i<N; i++)
+        {
+        bands[2*i  ] = (float)(N-1-i)/(N-1);
+        bands[2*i+1] = H*(1-bands[2*i]);
+        }
+      }
+    else
+      {
+      int K2 = (int)((N-3)*K);
+      int K1 = (N-3)-K2;
+
+      for(int i=0; i<=K1; i++)
+        {
+        float angle = K*beta + (1-K)*beta*(K1-i)/(K1+1);
+        float x = h(R,sinBeta,angle);
+        bands[2*i+2] = 1.0f - x;
+        bands[2*i+3] = g(R,D,x,cosBeta);
+        }
+
+      for(int i=0; i<=K2; i++)
+        {
+        float x = (1-B)*(i+1)/(K2+1) + B;
+        bands[2*K1+2 + 2*i+2] = 1.0f - x;
+        bands[2*K1+2 + 2*i+3] = g(R,D,f(D,B,x),cosBeta);
+        }
+      }
+
+    bands[2*N-2] = 0.0f;
+    bands[2*N-1] =    H;
+
+    return bands;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void roundCorners(MeshBase mesh, Static3D center, Static3D[] vertices, float strength, float regionRadius)
+    {
+    Static4D reg= new Static4D(0,0,0,regionRadius);
+
+    float centX = center.get0();
+    float centY = center.get1();
+    float centZ = center.get2();
+
+    for (Static3D vertex : vertices)
+      {
+      float x = strength*(centX - vertex.get0());
+      float y = strength*(centY - vertex.get1());
+      float z = strength*(centZ - vertex.get2());
+
+      VertexEffect effect = new VertexEffectDeform(new Static3D(x,y,z), RADIUS, vertex, reg);
+      mesh.apply(effect);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void printStickerCoords()
+    {
+    int stickers = mStickerCoords.size();
+
+    android.util.Log.d("D", "---- STICKER COORDS ----");
+
+    for(int s=0; s<stickers; s++)
+      {
+      String ver = "{ ";
+      StickerCoords info = mStickerCoords.get(s);
+      int len = info.vertices.length/2;
+
+      for(int i =0; i<len; i++)
+        {
+        if( i!=0 ) ver += ", ";
+        ver += ( (float)info.vertices[2*i]+"f, "+(float)info.vertices[2*i+1]+"f");
+        }
+
+      ver += " }";
+      android.util.Log.d("D", ver);
+      }
+
+    android.util.Log.d("D", "---- END STICKER COORDS ----");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void printFaceTransform()
+    {
+    android.util.Log.d("D", "---- OLD FACE TRANSFORM ---");
+
+    int oldfaces = mOldFaceTransf.size();
+
+    for(int f=0; f<oldfaces; f++)
+      {
+      printTransform(mOldFaceTransf.get(f));
+      }
+
+    android.util.Log.d("D", "---- NEW FACE TRANSFORM ---");
+
+    int newfaces = mNewFaceTransf.size();
+
+    for(int f=0; f<newfaces; f++)
+      {
+      printTransform(mNewFaceTransf.get(f));
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void clear()
+    {
+    mStickerCoords.clear();
+    mNewFaceTransf.clear();
+    mOldFaceTransf.clear();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void createNewFaceTransform( final double[][] vertices, final int[][] indexes)
+    {
+    FaceTransform ft;
+    int numNew = mNewFaceTransf.size();
+
+    for(int i=0; i<numNew; i++)
+      {
+      ft = mNewFaceTransf.remove(0);
+      mOldFaceTransf.add(ft);
+      }
+
+    int numFaces = indexes.length;
+    int numOld = mOldFaceTransf.size();
+
+    for (int face=0; face<numFaces; face++)
+      {
+      boolean collapsed = false;
+
+      double[][] vert = constructVert(vertices, indexes[face]);
+      FaceTransform newT = constructNewTransform(vert);
+
+      for (int old=0; !collapsed && old<numOld; old++)
+        {
+        ft = mOldFaceTransf.get(old);
+        if (successfullyCollapsedStickers(newT, ft)) collapsed = true;
+        }
+
+      for (int pre=0; !collapsed && pre<face; pre++)
+        {
+        ft = mNewFaceTransf.get(pre);
+        if (successfullyCollapsedStickers(newT, ft)) collapsed = true;
+        }
+
+      mNewFaceTransf.add(newT);
+      }
+    }
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void createNewFaceTransform(final ObjectShape shape)
+    {
+    double[][] vertices = shape.getVertices();
+    int[][] indices = shape.getVertIndices();
+    createNewFaceTransform(vertices,indices);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeConvexityCenter(double[] out, float[] in, FaceTransform ft)
+    {
+    if( in==null )
+      {
+      out[0] = out[1] = 0.0f;
+      }
+    else
+      {
+      out[0] = in[0] - ft.vx;
+      out[1] = in[1] - ft.vy;
+      out[2] = in[2] - ft.vz;
+      out[3] = 1.0f;
+
+      mQuat1[0] =-ft.qx;
+      mQuat1[1] =-ft.qy;
+      mQuat1[2] =-ft.qz;
+      mQuat1[3] = ft.qw;
+
+      mQuat2[0] = -mQuat1[0];
+      mQuat2[1] = -mQuat1[1];
+      mQuat2[2] = -mQuat1[2];
+      mQuat2[3] = +mQuat1[3];
+
+      quatMultiply(mQuat1, out  , mQuat3);
+      quatMultiply(mQuat3, mQuat2, out  );
+
+      out[0] /= ft.scale;
+      out[1] /= ft.scale;
+      out[2] /= ft.scale;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public MeshBase createRoundedSolid(final ObjectShape shape)
+    {
+    double[][] vertices     = shape.getVertices();
+    int[][] vertIndexes     = shape.getVertIndices();
+    float[][] bands         = shape.getBands();
+    int[]   bandIndexes     = shape.getBandIndices();
+    float[][] corners       = shape.getCorners();
+    int[]   cornerIndexes   = shape.getCornerIndices();
+    float[][] centers       = shape.getCenters();
+    int[]   centerIndexes   = shape.getCenterIndices();
+    int numComponents       = shape.getNumComponents();
+    float[] convexityCenter = shape.getConvexityCenter();
+
+    return createRoundedSolid(vertices,vertIndexes,bands,bandIndexes,corners,cornerIndexes,
+                              centers,centerIndexes,numComponents,convexityCenter);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public MeshBase createRoundedSolid(final double[][] vertices, final int[][] vertIndexes,
+                                     final float[][] bands    , final int[]   bandIndexes,
+                                     final float[][] corners  , final int[]   cornerIndexes,
+                                     final float[][] centers  , final int[]   centerIndexes,
+                                     final int numComponents  , final float[] convexityCenter )
+    {
+    int numFaces = vertIndexes.length;
+    float[] band, bandsComputed;
+    MeshBase[] meshes = new MeshBase[numFaces];
+    FaceTransform fInfo;
+    StickerCoords sInfo;
+    double[] convexXY = new double[4];
+
+    for(int face=0; face<numFaces; face++)
+      {
+      fInfo = mNewFaceTransf.get(face);
+      sInfo = mStickerCoords.get(fInfo.sticker);
+
+      double[] verts = sInfo.vertices;
+      int lenVerts = verts.length;
+      float[] vertsFloat = new float[lenVerts];
+      for(int i=0; i<lenVerts; i++) vertsFloat[i] = (float)verts[i];
+
+      computeConvexityCenter(convexXY,convexityCenter,fInfo);
+
+      band = bands[bandIndexes[face]];
+      bandsComputed = computeBands( band[0], (int)band[1], band[2], band[3], (int)band[4]);
+      meshes[face] = new MeshPolygon(vertsFloat,bandsComputed,(int)band[5],(int)band[6], (float)convexXY[0], (float)convexXY[1]);
+      meshes[face].setEffectAssociation(0,(1<<face),0);
+      }
+
+    MeshBase mesh = new MeshJoined(meshes);
+    Static3D center = new Static3D(0,0,0);
+
+    for(int face=0; face<numFaces; face++)
+      {
+      int assoc = (1<<face);
+      fInfo = mNewFaceTransf.get(face);
+
+      float vx = (float)fInfo.vx;
+      float vy = (float)fInfo.vy;
+      float vz = (float)fInfo.vz;
+      float sc = (float)fInfo.scale;
+      float qx = (float)fInfo.qx;
+      float qy = (float)fInfo.qy;
+      float qz = (float)fInfo.qz;
+      float qw = (float)fInfo.qw;
+
+      Static3D scale = new Static3D(sc,sc, fInfo.flip ? -sc : sc);
+      Static3D move3D= new Static3D(vx,vy,vz);
+      Static4D quat  = new Static4D(qx,qy,qz,qw);
+
+      mesh.apply(new MatrixEffectScale(scale)           ,assoc,-1);
+      mesh.apply(new MatrixEffectQuaternion(quat,center),assoc,-1);
+      mesh.apply(new MatrixEffectMove(move3D)           ,assoc,-1);
+      }
+
+    prepareAndRoundCorners(mesh, vertices, corners, cornerIndexes, centers, centerIndexes);
+
+    correctComponents(mesh,numComponents);
+
+    return mesh;
+    }
+  }
diff --git a/src/main/java/org/distorted/objectlib/helpers/FactorySticker.java b/src/main/java/org/distorted/objectlib/helpers/FactorySticker.java
new file mode 100644
index 00000000..d0857366
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/FactorySticker.java
@@ -0,0 +1,315 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.objectlib.helpers;
+
+import static org.distorted.objectlib.main.TwistyObject.COLOR_BLACK;
+import static org.distorted.objectlib.main.TwistyObject.TEXTURE_HEIGHT;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class FactorySticker
+  {
+  private static FactorySticker mThis;
+  private float mOX, mOY, mR;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private FactorySticker()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static FactorySticker getInstance()
+    {
+    if( mThis==null ) mThis = new FactorySticker();
+
+    return mThis;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float computeAngle(float dx, float dy)
+    {
+    float PI = (float)Math.PI;
+    double angle = Math.atan2(dy,dx);
+    float ret = (float)(3*PI/2-angle);
+
+    if( ret>2*PI ) ret-= 2*PI;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float getAngle(float[] angles, int index)
+    {
+    return angles==null ? 0 : angles[index];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeCircleCoords(float lX,float lY, float rX, float rY, float alpha)
+    {
+    float ctg= 1.0f/((float)Math.tan(alpha));
+    mOX = 0.5f*(lX+rX) + ctg*0.5f*(lY-rY);
+    mOY = 0.5f*(lY+rY) - ctg*0.5f*(lX-rX);
+    float dx = mOX-lX;
+    float dy = mOY-lY;
+    mR = (float)Math.sqrt(dx*dx+dy*dy);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// circle1: center (x1,y1) radius r1; circle2: center (x2,y2) radius r2.
+// Guaranteed to intersect in two points. Find the intersection. Which one? the one that's closer
+// to (nearx,neary).
+
+  private void findCircleIntersection(float x1,float y1, float r1, float x2, float y2, float r2, float nearx, float neary )
+    {
+    float dx = x2-x1;
+    float dy = y2-y1;
+    float d = (float)Math.sqrt(dx*dx+dy*dy);
+
+    if( d>0 )
+      {
+      float Dx = dx/d;
+      float Dy = dy/d;
+      float cos = (r1*r1+d*d-r2*r2)/(2*r1*d);
+      float sin = (float)Math.sqrt(1-cos*cos);
+
+      float ox1 = x1 + r1*cos*Dx + r1*sin*Dy;
+      float oy1 = y1 + r1*cos*Dy - r1*sin*Dx;
+      float ox2 = x1 + r1*cos*Dx - r1*sin*Dy;
+      float oy2 = y1 + r1*cos*Dy + r1*sin*Dx;
+
+      dx = nearx-ox1;
+      dy = neary-oy1;
+      float d1 = dx*dx+dy*dy;
+      dx = nearx-ox2;
+      dy = neary-oy2;
+      float d2 = dx*dx+dy*dy;
+
+      if( d1<d2 )
+        {
+        mOX = ox1;
+        mOY = oy1;
+        }
+      else
+        {
+        mOX = ox2;
+        mOY = oy2;
+        }
+      }
+    else
+      {
+      mOX = x1;
+      mOY = y1;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void drawCurrCurveV(Canvas canvas, Paint paint, int left, int top, float r, float stroke, float pX, float pY, float cX, float cY, float nX, float nY, float pA, float cA)
+    {
+    pX = (0.5f+pX)*TEXTURE_HEIGHT;
+    pY = (0.5f-pY)*TEXTURE_HEIGHT;
+    cX = (0.5f+cX)*TEXTURE_HEIGHT;
+    cY = (0.5f-cY)*TEXTURE_HEIGHT;
+    nX = (0.5f+nX)*TEXTURE_HEIGHT;
+    nY = (0.5f-nY)*TEXTURE_HEIGHT;
+
+    computeCircleCoords(pX,pY,cX,cY,pA);
+    float o1x = mOX;
+    float o1y = mOY;
+    float r1  = mR;
+    computeCircleCoords(cX,cY,nX,nY,cA);
+    float o2x = mOX;
+    float o2y = mOY;
+    float r2  = mR;
+
+    float dx = o1x-pX;
+    float dy = o1y-pY;
+    float startA = computeAngle(dy,dx);
+    float sweepA = 2*pA;
+
+    startA *= 180/(Math.PI);
+    sweepA *= 180/(Math.PI);
+
+    canvas.drawArc( left+o1x-r1, top+o1y-r1, left+o1x+r1, top+o1y+r1, startA, sweepA, false, paint);
+
+    float r3 = r*TEXTURE_HEIGHT + stroke/2;
+    float R1 = r1 + (pA < 0 ? r3:-r3);
+    float R2 = r2 + (cA < 0 ? r3:-r3);
+    findCircleIntersection(o1x,o1y,R1,o2x,o2y,R2,cX,cY);
+    float o3x = mOX;
+    float o3y = mOY;
+
+    dx = pA<0 ? o3x-o1x : o1x-o3x;
+    dy = pA<0 ? o3y-o1y : o1y-o3y;
+    startA = computeAngle(dy,dx);
+    dx = cA<0 ? o3x-o2x : o2x-o3x;
+    dy = cA<0 ? o3y-o2y : o2y-o3y;
+    float endA = computeAngle(dy,dx);
+
+    sweepA = endA-startA;
+    if( sweepA<0 ) sweepA += 2*Math.PI;
+
+    startA *= 180/(Math.PI);
+    sweepA *= 180/(Math.PI);
+
+    canvas.drawArc( left+o3x-r3, top+o3y-r3, left+o3x+r3, top+o3y+r3, startA, sweepA, false, paint);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void drawCurrVertex(Canvas canvas, Paint paint, int left, int top, float r, float stroke, float pX, float pY, float cX, float cY, float nX, float nY)
+    {
+    pX = (0.5f+pX)*TEXTURE_HEIGHT;
+    pY = (0.5f-pY)*TEXTURE_HEIGHT;
+    cX = (0.5f+cX)*TEXTURE_HEIGHT;
+    cY = (0.5f-cY)*TEXTURE_HEIGHT;
+    nX = (0.5f+nX)*TEXTURE_HEIGHT;
+    nY = (0.5f-nY)*TEXTURE_HEIGHT;
+
+    canvas.drawLine(left+pX,top+pY,left+cX,top+cY,paint);
+
+    float aX = pX-cX;
+    float aY = pY-cY;
+    float bX = cX-nX;
+    float bY = cY-nY;
+
+    float aLen = (float)Math.sqrt(aX*aX+aY*aY);
+    float bLen = (float)Math.sqrt(bX*bX+bY*bY);
+
+    aX /= aLen;
+    aY /= aLen;
+    bX /= bLen;
+    bY /= bLen;
+
+    float sX = (aX-bX)/2;
+    float sY = (aY-bY)/2;
+    float sLen = (float)Math.sqrt(sX*sX+sY*sY);
+    sX /= sLen;
+    sY /= sLen;
+
+    float startAngle = computeAngle(bX,-bY);
+    float endAngle   = computeAngle(aX,-aY);
+    float sweepAngle = endAngle-startAngle;
+    if( sweepAngle<0 ) sweepAngle += 2*Math.PI;
+
+    float R = r*TEXTURE_HEIGHT+stroke/2;
+    float C = (float)Math.cos(sweepAngle/2);
+    float A = R/C;
+
+    left += (cX+A*sX);
+    top  += (cY+A*sY);
+
+    if( C< (2*R-stroke)/(2*R+stroke) )
+      {
+      float alpha = startAngle + sweepAngle/2;
+      float B  = (R-stroke/2)/C;
+      float sx = (float)Math.cos(alpha);
+      float sy = (float)Math.sin(alpha);
+
+      float startX = left + R*sx;
+      float startY = top  + R*sy;
+      float stopX  = left + B*sx;
+      float stopY  = top  + B*sy;
+
+      canvas.drawLine(startX,startY,stopX,stopY,paint);
+      }
+
+    startAngle *= 180/(Math.PI);
+    sweepAngle *= 180/(Math.PI);
+
+    canvas.drawArc( left-R, top-R, left+R, top+R, startAngle, sweepAngle, false, paint);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC
+
+  public void drawRoundedPolygon(Canvas canvas, Paint paint, int left, int top, int color, ObjectSticker sticker)
+    {
+    float stroke = sticker.getStroke();
+    float[] vertices = sticker.getCoords();
+    float[] angles = sticker.getCurvature();
+    float[] radii = sticker.getRadii();
+
+    stroke *= TEXTURE_HEIGHT;
+
+    paint.setAntiAlias(true);
+    paint.setStrokeWidth(stroke);
+    paint.setColor(color);
+    paint.setStyle(Paint.Style.FILL);
+
+    canvas.drawRect(left,top,left+TEXTURE_HEIGHT,top+TEXTURE_HEIGHT,paint);
+
+    paint.setColor(COLOR_BLACK);
+    paint.setStyle(Paint.Style.STROKE);
+
+    int length = vertices.length;
+    int numVertices = length/2;
+
+    float prevX = vertices[length-2];
+    float prevY = vertices[length-1];
+    float currX = vertices[0];
+    float currY = vertices[1];
+    float nextX = vertices[2];
+    float nextY = vertices[3];
+
+    float prevA = getAngle(angles,numVertices-1);
+    float currA = getAngle(angles,0);
+
+    for(int vert=0; vert<numVertices; vert++)
+      {
+      if( prevA==0 )
+        {
+        drawCurrVertex(canvas, paint, left, top, radii[vert], stroke, prevX,prevY,currX,currY,nextX,nextY);
+        }
+      else
+        {
+        drawCurrCurveV(canvas, paint, left, top, radii[vert], stroke, prevX,prevY,currX,currY,nextX,nextY,prevA,currA);
+        }
+
+      prevX = currX;
+      prevY = currY;
+      currX = nextX;
+      currY = nextY;
+
+      prevA = currA;
+      currA = getAngle(angles, vert==numVertices-1 ? 0 : vert+1);
+
+      if( 2*(vert+2)+1 < length )
+        {
+        nextX = vertices[2*(vert+2)  ];
+        nextY = vertices[2*(vert+2)+1];
+        }
+      else
+        {
+        nextX = vertices[0];
+        nextY = vertices[1];
+        }
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/objectlib/helpers/MovesFinished.java b/src/main/java/org/distorted/objectlib/helpers/MovesFinished.java
new file mode 100644
index 00000000..65f58bc6
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/MovesFinished.java
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.helpers;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public interface MovesFinished
+  {
+  void onActionFinished(long effectID);
+  }
diff --git a/src/main/java/org/distorted/objectlib/helpers/ObjectShape.java b/src/main/java/org/distorted/objectlib/helpers/ObjectShape.java
new file mode 100644
index 00000000..9c4139da
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/ObjectShape.java
@@ -0,0 +1,124 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.helpers;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ObjectShape
+  {
+  private final double[][] mVertices;
+  private final int[][] mVertIndices;
+  private final float[][] mBands;
+  private final int[] mBandIndices;
+  private final float[][] mCorners;
+  private final int[] mCornerIndices;
+  private final float[][] mCenters;
+  private final int[] mCenterIndices;
+  private final int mNumComponents;
+  private final float[] mConvexityCenter;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectShape(double[][] vertices, int[][] vertIndices, float[][] bands, int[] bandIndices,
+                     float[][] corners, int[] cornIndices, float[][] centers, int[] centIndices,
+                     int numComponents, float[] convexityCenter)
+    {
+    mVertices        = vertices;
+    mVertIndices     = vertIndices;
+    mBands           = bands;
+    mBandIndices     = bandIndices;
+    mCorners         = corners;
+    mCornerIndices   = cornIndices;
+    mCenters         = centers;
+    mCenterIndices   = centIndices;
+    mNumComponents   = numComponents;
+    mConvexityCenter = convexityCenter;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public double[][] getVertices()
+    {
+    return mVertices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] getVertIndices()
+    {
+    return mVertIndices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[][] getBands()
+    {
+    return mBands;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[] getBandIndices()
+    {
+    return mBandIndices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[][] getCorners()
+    {
+    return mCorners;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[] getCornerIndices()
+    {
+    return mCornerIndices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[][] getCenters()
+    {
+    return mCenters;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[] getCenterIndices()
+    {
+    return mCenterIndices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumComponents()
+    {
+    return mNumComponents;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[] getConvexityCenter()
+    {
+    return mConvexityCenter;
+    }
+  }
diff --git a/src/main/java/org/distorted/objectlib/helpers/ObjectSticker.java b/src/main/java/org/distorted/objectlib/helpers/ObjectSticker.java
new file mode 100644
index 00000000..0bccf247
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/ObjectSticker.java
@@ -0,0 +1,68 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.helpers;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ObjectSticker
+  {
+  private final float[] mCoords;
+  private final float[] mCurvature;
+  private final float[] mRadii;
+  private final float mStroke;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectSticker(float[] coords, float[] curvature, float[] radii, float stroke)
+    {
+    mCoords    = coords;
+    mCurvature = curvature;
+    mRadii     = radii;
+    mStroke    = stroke;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[] getCoords()
+    {
+    return mCoords;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[] getCurvature()
+    {
+    return mCurvature;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[] getRadii()
+    {
+    return mRadii;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float getStroke()
+    {
+    return mStroke;
+    }
+  }
diff --git a/src/main/java/org/distorted/objectlib/helpers/ScrambleState.java b/src/main/java/org/distorted/objectlib/helpers/ScrambleState.java
new file mode 100644
index 00000000..24c712fa
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/ScrambleState.java
@@ -0,0 +1,141 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.helpers;
+
+import java.util.Random;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ScrambleState
+{
+  private final int mTotal, mNumAxis;
+  private final int[] mNum;
+  private final int[] mInfo;
+  private final int[] mTmp;
+  private final int LEN = 4;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ScrambleState(int[][] axis)
+    {
+    mTmp = new int[LEN];
+
+    mNumAxis = axis.length;
+    mNum = new int[mNumAxis];
+    int total =0;
+
+    for(int i=0; i<mNumAxis; i++)
+      {
+      mNum[i] = axis[i]==null ? 0 : axis[i].length/(LEN-1);
+      total += mNum[i];
+      }
+
+    mTotal = total;
+
+    mInfo = new int[LEN*total];
+    int start = 0;
+
+    for(int i=0; i<mNumAxis; i++)
+      {
+      for(int j=0; j<mNum[i]; j++)
+        {
+        mInfo[LEN*j   + start] = i;
+        mInfo[LEN*j+1 + start] = axis[i][(LEN-1)*j  ];
+        mInfo[LEN*j+2 + start] = axis[i][(LEN-1)*j+1];
+        mInfo[LEN*j+3 + start] = axis[i][(LEN-1)*j+2];
+        }
+
+      start += LEN*mNum[i];
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getIndex(int num, int indexExcluded)
+    {
+    int current= -1;
+
+    for(int i=0; i<mTotal; i++)
+      if( mInfo[LEN*i]!=indexExcluded && ++current==num ) return i;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[] getRandom(Random rnd, int indexExcluded, int[][] scrambleTable, int[] numOccurences)
+    {
+    int num=0, total=0, max=0, ax, layer;
+
+    for(int i=0; i<mTotal; i++)
+      {
+      ax = mInfo[LEN*i];
+
+      if( ax!=indexExcluded )
+        {
+        layer = mInfo[LEN*i+1];
+        int value = scrambleTable[ax][layer];
+        if( value>max ) max=value;
+        }
+      }
+
+    for(int i=0; i<mTotal; i++)
+      {
+      ax = mInfo[LEN*i];
+
+      if( ax!=indexExcluded )
+        {
+        layer = mInfo[LEN*i+1];
+        int value = scrambleTable[ax][layer];
+        numOccurences[total] = 1 + max - value + (total==0 ? 0 : numOccurences[total-1]);
+        total++;
+        }
+      }
+
+    float random= rnd.nextFloat()*numOccurences[total-1];
+
+    for(int i=0; i<total; i++)
+      {
+      if( random <= numOccurences[i] )
+        {
+        num=i;
+        break;
+        }
+      }
+
+    int index = getIndex(num,indexExcluded);
+
+    mTmp[0] = mInfo[LEN*index  ];   // axis
+    mTmp[1] = mInfo[LEN*index+1];   // row
+    mTmp[2] = mInfo[LEN*index+2];   // angle
+    mTmp[3] = mInfo[LEN*index+3];   // next state
+
+    scrambleTable[mTmp[0]][mTmp[1]]++;
+
+    return mTmp;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getTotal(int indexExcluded)
+    {
+    return ( indexExcluded>=0 && indexExcluded<mNumAxis ) ? mTotal-mNum[indexExcluded] : mTotal;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/objectlib/helpers/ScrambleStateBandagedEvil.java b/src/main/java/org/distorted/objectlib/helpers/ScrambleStateBandagedEvil.java
new file mode 100644
index 00000000..5557bc60
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/ScrambleStateBandagedEvil.java
@@ -0,0 +1,669 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.helpers;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// producer of the ScrambleStateGraph - but only for a single Twisty Puzzle, the 'BandagedEvil'.
+
+public class ScrambleStateBandagedEvil
+{
+  private static final int INVALID_MOVE = -1;
+
+  private static final int CORNER_S = 0;
+  private static final int CORNER_X = 1;
+  private static final int CORNER_Y = 2;
+  private static final int CORNER_Z = 3;
+
+  private static final int CENTER_0 = 0;
+  private static final int CENTER_1 = 1;
+  private static final int CENTER_2 = 2;
+  private static final int CENTER_3 = 3;
+
+  private int mID;
+  private final int[] mMoves;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ScrambleStateBandagedEvil(int id)
+    {
+    mID = id;
+    mMoves = createMoves(mID);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void computeGraph()
+    {
+    ArrayList<ScrambleStateBandagedEvil> graph;
+
+    int id = 0;
+    int id1 = setCenter(id  , CENTER_2, 0);
+    int id2 = setCenter(id1 , CENTER_2, 1);
+    int id3 = setCenter(id2 , CENTER_3, 2);
+    int id4 = setCenter(id3 , CENTER_2, 3);
+
+    int id5 = setCorner(id4 , CORNER_X, 0);
+    int id6 = setCorner(id5 , CORNER_Y, 1);
+    int id7 = setCorner(id6 , CORNER_X, 2);
+    int id8 = setCorner(id7 , CORNER_Z, 3);
+    int id9 = setCorner(id8 , CORNER_Y, 4);
+    int id10= setCorner(id9 , CORNER_Y, 5);
+    int id11= setCorner(id10, CORNER_S, 6);
+    int id12= setCorner(id11, CORNER_Z, 7);
+
+    ScrambleStateBandagedEvil bsg = new ScrambleStateBandagedEvil(id12);
+    graph = new ArrayList<>();
+    graph.add(bsg);
+
+    insertChildren(graph,id12);
+    pruneGraph(graph);
+    remapGraph(graph);
+
+    int num = graph.size();
+    android.util.Log.e("D", "\n"+num+" states\n");
+
+    for(int i=0; i<num; i++)
+      {
+      bsg = graph.get(i);
+      android.util.Log.e("D", formatMoves(bsg));
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void insertChildren(ArrayList<ScrambleStateBandagedEvil> list, int id)
+    {
+    ScrambleStateBandagedEvil bsg = findState(list,id);
+
+    if( bsg==null )
+      {
+      android.util.Log.e("D", "error: "+id+" doesn't exist");
+      return;
+      }
+
+    for(int i=0; i<12; i++)
+      {
+      int move = bsg.getMove(i);
+
+      if( move!=INVALID_MOVE )
+        {
+        ScrambleStateBandagedEvil tmp = findState(list,move);
+
+        if( tmp==null )
+          {
+          tmp = new ScrambleStateBandagedEvil(move);
+          list.add(tmp);
+          insertChildren(list,move);
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void pruneGraph(ArrayList<ScrambleStateBandagedEvil> list)
+    {
+    int num = list.size(), numAxis;
+    boolean pruned = false;
+    ScrambleStateBandagedEvil bsg;
+
+    for(int i=0; i<num; i++)
+      {
+      bsg = list.get(i);
+      numAxis = bsg.numAxis();
+
+      if( numAxis<2 )
+        {
+        list.remove(i);
+        int id = bsg.getID();
+        pruned = true;
+        remapID(list,id,INVALID_MOVE);
+        break;
+        }
+      }
+
+    if( pruned ) pruneGraph(list);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void remapGraph(ArrayList<ScrambleStateBandagedEvil> list)
+    {
+    int id, num = list.size();
+    ScrambleStateBandagedEvil bsg;
+
+    for(int i=0; i<num; i++ )
+      {
+      bsg = list.get(i);
+      id = bsg.getID();
+      bsg.setID(i);
+      remapID(list,id,i);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void remapID(ArrayList<ScrambleStateBandagedEvil> list, int id, int newId)
+    {
+    ScrambleStateBandagedEvil bsg;
+    int size = list.size();
+
+    for(int i=0; i<size; i++)
+      {
+      bsg = list.get(i);
+
+      for(int j=0; j<12; j++)
+        {
+        if( bsg.getMove(j)==id ) bsg.setMove(j,newId);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static ScrambleStateBandagedEvil findState(ArrayList<ScrambleStateBandagedEvil> list, int id)
+    {
+    ScrambleStateBandagedEvil bsg;
+    int num = list.size();
+
+    for(int i=0; i<num; i++)
+      {
+      bsg= list.get(i);
+      if( bsg.getID() == id ) return bsg;
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String formatMoves(ScrambleStateBandagedEvil bsg)
+    {
+    String x = getTable(bsg,0);
+    String y = getTable(bsg,3);
+    String z = getTable(bsg,6);
+
+    return "    new ScrambleStateGraph( new int[][] { "+x+", "+y+", "+z+" } ),";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String getTable(ScrambleStateBandagedEvil sc, int index)
+    {
+    String ret = "";
+
+    if( index==0 || index==3 )
+      {
+      int m0 = sc.getMove(index  );
+      int m1 = sc.getMove(index+1);
+      int m2 = sc.getMove(index+2);
+
+      if( m0==INVALID_MOVE && m1==INVALID_MOVE && m2==INVALID_MOVE ) return formatL("{}");
+
+      if( m0!=INVALID_MOVE ) ret += formatRet(ret,2, 1,m0);
+      if( m1!=INVALID_MOVE ) ret += formatRet(ret,2, 2,m1);
+      if( m2!=INVALID_MOVE ) ret += formatRet(ret,2,-1,m2);
+      }
+    else
+      {
+      int m0 = sc.getMove(index  );
+      int m1 = sc.getMove(index+1);
+      int m2 = sc.getMove(index+2);
+      int m3 = sc.getMove(index+3);
+      int m4 = sc.getMove(index+4);
+      int m5 = sc.getMove(index+5);
+
+      if( m0==INVALID_MOVE && m1==INVALID_MOVE && m2==INVALID_MOVE &&
+          m3==INVALID_MOVE && m4==INVALID_MOVE && m5==INVALID_MOVE  )
+        {
+        return formatL("{}");
+        }
+
+      if( m0!=INVALID_MOVE ) ret += formatRet(ret,0, 1,m0);
+      if( m1!=INVALID_MOVE ) ret += formatRet(ret,0, 2,m1);
+      if( m2!=INVALID_MOVE ) ret += formatRet(ret,0,-1,m2);
+      if( m3!=INVALID_MOVE ) ret += formatRet(ret,2, 1,m3);
+      if( m4!=INVALID_MOVE ) ret += formatRet(ret,2, 2,m4);
+      if( m5!=INVALID_MOVE ) ret += formatRet(ret,2,-1,m5);
+      }
+
+    return formatL("{" + ret + "}");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String formatRet(String str, int row, int angle, int id)
+    {
+    String ret = str.length()!=0 ? ",":"";
+
+    ret += row;
+    ret += angle<0 ? "," : ", ";
+    ret += angle;
+
+         if( id< 10 ) ret += (",  "+id);
+    else if( id<100 ) ret += (", " +id);
+    else              ret += (","  +id);
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static final int LENGTH = 28;
+
+  private static String formatL(String input)
+    {
+    int len = input.length();
+    String ret = input;
+    for(int i=0 ;i<LENGTH-len; i++) ret += " ";
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getID()
+    {
+    return mID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setID(int id)
+    {
+    mID = id;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getMove(int index)
+    {
+    return (index>=0 && index<12) ? mMoves[index] : -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int numAxis()
+    {
+    int num = 0;
+
+    if( mMoves[ 0]!=INVALID_MOVE || mMoves[ 1]!=INVALID_MOVE || mMoves[ 2]!=INVALID_MOVE ) num++;
+    if( mMoves[ 3]!=INVALID_MOVE || mMoves[ 4]!=INVALID_MOVE || mMoves[ 5]!=INVALID_MOVE ) num++;
+    if( mMoves[ 6]!=INVALID_MOVE || mMoves[ 7]!=INVALID_MOVE || mMoves[ 8]!=INVALID_MOVE ) num++;
+    if( mMoves[ 9]!=INVALID_MOVE || mMoves[10]!=INVALID_MOVE || mMoves[11]!=INVALID_MOVE ) num++;
+
+    return num;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setMove(int index, int newMove)
+    {
+    if( index>=0 && index<12 ) mMoves[index] = newMove;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String debug(int id)
+    {
+    int ce0 = getCenter(id,0);
+    int ce1 = getCenter(id,1);
+    int ce2 = getCenter(id,2);
+    int ce3 = getCenter(id,3);
+
+    int co0 = getCorner(id,0);
+    int co1 = getCorner(id,1);
+    int co2 = getCorner(id,2);
+    int co3 = getCorner(id,3);
+    int co4 = getCorner(id,4);
+    int co5 = getCorner(id,5);
+    int co6 = getCorner(id,6);
+    int co7 = getCorner(id,7);
+
+    String center = centerString(ce0) + centerString(ce1) + centerString(ce2) + centerString(ce3);
+    String corner = cornerString(co0) + cornerString(co1) + cornerString(co2) + cornerString(co3) +
+                    cornerString(co4) + cornerString(co5) + cornerString(co6) + cornerString(co7);
+
+    return center + " -" + corner;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String centerString(int center)
+    {
+    return " "+center;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String cornerString(int corner)
+    {
+    switch(corner)
+      {
+      case CORNER_S: return " S";
+      case CORNER_X: return " X";
+      case CORNER_Y: return " Y";
+      case CORNER_Z: return " Z";
+      }
+
+    return "?";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int[] createMoves(int id)
+    {
+    int[] ret = new int[12];
+
+    boolean moveX  = xPossible(id);
+    boolean moveY  = yPossible(id);
+    boolean moveZ0 = z0Possible(id);
+    boolean moveZ2 = z2Possible(id);
+
+    if( moveX ) createXmoves(id,ret);
+    else        { ret[ 0] = INVALID_MOVE; ret[ 1] = INVALID_MOVE; ret[ 2] = INVALID_MOVE; }
+    if( moveY ) createYmoves(id,ret);
+    else        { ret[ 3] = INVALID_MOVE; ret[ 4] = INVALID_MOVE; ret[ 5] = INVALID_MOVE; }
+    if( moveZ0) createZ0moves(id,ret);
+    else        { ret[ 6] = INVALID_MOVE; ret[ 7] = INVALID_MOVE; ret[ 8] = INVALID_MOVE; }
+    if( moveZ2) createZ2moves(id,ret);
+    else        { ret[ 9] = INVALID_MOVE; ret[10] = INVALID_MOVE; ret[11] = INVALID_MOVE; }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static boolean xPossible(int id)
+    {
+    if( getCorner(id,4)==CORNER_X ) return false;
+    if( getCorner(id,5)==CORNER_X ) return false;
+    if( getCorner(id,6)==CORNER_X ) return false;
+    if( getCorner(id,7)==CORNER_X ) return false;
+
+    if( getCenter(id,1)==CENTER_1 ) return false;
+    if( getCenter(id,2)==CENTER_1 ) return false;
+    if( getCenter(id,3)==CENTER_1 ) return false;
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static boolean yPossible(int id)
+    {
+    if( getCorner(id,2)==CORNER_Y ) return false;
+    if( getCorner(id,3)==CORNER_Y ) return false;
+    if( getCorner(id,6)==CORNER_Y ) return false;
+    if( getCorner(id,7)==CORNER_Y ) return false;
+
+    if( getCenter(id,0)==CENTER_0 ) return false;
+    if( getCenter(id,2)==CENTER_0 ) return false;
+    if( getCenter(id,3)==CENTER_0 ) return false;
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static boolean z0Possible(int id)
+    {
+    if( getCorner(id,0)==CORNER_Z ) return false;
+    if( getCorner(id,2)==CORNER_Z ) return false;
+    if( getCorner(id,4)==CORNER_Z ) return false;
+    if( getCorner(id,6)==CORNER_Z ) return false;
+
+    if( getCenter(id,0)==CENTER_1 ) return false;
+    if( getCenter(id,1)==CENTER_0 ) return false;
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static boolean z2Possible(int id)
+    {
+    if( getCorner(id,1)==CORNER_Z ) return false;
+    if( getCorner(id,3)==CORNER_Z ) return false;
+    if( getCorner(id,5)==CORNER_Z ) return false;
+    if( getCorner(id,7)==CORNER_Z ) return false;
+
+    if( getCenter(id,0)==CENTER_3 ) return false;
+    if( getCenter(id,1)==CENTER_2 ) return false;
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int getCorner(int id, int index)
+    {
+    return (id>>(14-2*index))&3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int getCenter(int id, int index)
+    {
+    return (id>>(22-2*index))&3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int setCorner(int id, int corner, int index)
+    {
+    return id + ((corner-getCorner(id,index))<<(14-2*index));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int setCenter(int id, int center, int index)
+    {
+    return id + ((center-getCenter(id,index))<<(22-2*index));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createXmoves(int id, int[] moves)
+    {
+    int id1 = rotateX(id);
+    moves[0] = id1;
+    int id2 = rotateX(id1);
+    moves[1] = id2;
+    int id3 = rotateX(id2);
+    moves[2] = id3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createYmoves(int id, int[] moves)
+    {
+    int id1 = rotateY(id);
+    moves[3] = id1;
+    int id2 = rotateY(id1);
+    moves[4] = id2;
+    int id3 = rotateY(id2);
+    moves[5] = id3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createZ0moves(int id, int[] moves)
+    {
+    int id1 = rotateZ0(id);
+    moves[6] = id1;
+    int id2 = rotateZ0(id1);
+    moves[7] = id2;
+    int id3 = rotateZ0(id2);
+    moves[8] = id3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createZ2moves(int id, int[] moves)
+    {
+    int id1 = rotateZ2(id);
+    moves[ 9] = id1;
+    int id2 = rotateZ2(id1);
+    moves[10] = id2;
+    int id3 = rotateZ2(id2);
+    moves[11] = id3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int rotateX(int id)
+    {
+    int newCorner4 = rotCornerX(getCorner(id,5));
+    int newCorner5 = rotCornerX(getCorner(id,7));
+    int newCorner6 = rotCornerX(getCorner(id,4));
+    int newCorner7 = rotCornerX(getCorner(id,6));
+    int newCenter  = rotCenter (getCenter(id,0));
+
+    int id1 = setCorner(id ,newCorner4,4);
+    int id2 = setCorner(id1,newCorner5,5);
+    int id3 = setCorner(id2,newCorner6,6);
+    int id4 = setCorner(id3,newCorner7,7);
+    int id5 = setCenter(id4,newCenter ,0);
+
+    return id5;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int rotateY(int id)
+    {
+    int newCorner2 = rotCornerY(getCorner(id,6));
+    int newCorner3 = rotCornerY(getCorner(id,2));
+    int newCorner6 = rotCornerY(getCorner(id,7));
+    int newCorner7 = rotCornerY(getCorner(id,3));
+    int newCenter  = rotCenter (getCenter(id,1));
+
+    int id1 = setCorner(id ,newCorner2,2);
+    int id2 = setCorner(id1,newCorner3,3);
+    int id3 = setCorner(id2,newCorner6,6);
+    int id4 = setCorner(id3,newCorner7,7);
+    int id5 = setCenter(id4,newCenter ,1);
+
+    return id5;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int rotateZ0(int id)
+    {
+    int newCorner0 = rotCornerZ(getCorner(id,2));
+    int newCorner2 = rotCornerZ(getCorner(id,6));
+    int newCorner4 = rotCornerZ(getCorner(id,0));
+    int newCorner6 = rotCornerZ(getCorner(id,4));
+    int newCenter  = rotCenter (getCenter(id,2));
+
+    int id1 = setCorner(id ,newCorner0,0);
+    int id2 = setCorner(id1,newCorner2,2);
+    int id3 = setCorner(id2,newCorner4,4);
+    int id4 = setCorner(id3,newCorner6,6);
+    int id5 = setCenter(id4,newCenter ,2);
+
+    return id5;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int rotateZ2(int id)
+    {
+    int newCorner1 = rotCornerZ(getCorner(id,3));
+    int newCorner3 = rotCornerZ(getCorner(id,7));
+    int newCorner5 = rotCornerZ(getCorner(id,1));
+    int newCorner7 = rotCornerZ(getCorner(id,5));
+    int newCenter  = rotCenter (getCenter(id,3));
+
+    int id1 = setCorner(id ,newCorner1,1);
+    int id2 = setCorner(id1,newCorner3,3);
+    int id3 = setCorner(id2,newCorner5,5);
+    int id4 = setCorner(id3,newCorner7,7);
+    int id5 = setCenter(id4,newCenter ,3);
+
+    return id5;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int rotCornerX(int corner)
+    {
+    switch(corner)
+      {
+      case CORNER_S: return CORNER_S;
+      case CORNER_X: android.util.Log.e("DIST", "rotateX: ERROR");
+                     return CORNER_S;
+      case CORNER_Y: return CORNER_Z;
+      case CORNER_Z: return CORNER_Y;
+      }
+
+    return CORNER_S;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int rotCornerY(int corner)
+    {
+    switch(corner)
+      {
+      case CORNER_S: return CORNER_S;
+      case CORNER_X: return CORNER_Z;
+      case CORNER_Y: android.util.Log.e("DIST", "rotateY: ERROR");
+                     return CORNER_S;
+      case CORNER_Z: return CORNER_X;
+      }
+
+    return CORNER_S;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int rotCornerZ(int corner)
+    {
+    switch(corner)
+      {
+      case CORNER_S: return CORNER_S;
+      case CORNER_X: return CORNER_Y;
+      case CORNER_Y: return CORNER_X;
+      case CORNER_Z: android.util.Log.e("DIST", "rotateZ: ERROR");
+                     return CORNER_S;
+      }
+
+    return CORNER_S;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int rotCenter(int center)
+    {
+    switch(center)
+      {
+      case CENTER_0: return CENTER_3;
+      case CENTER_1: return CENTER_0;
+      case CENTER_2: return CENTER_1;
+      case CENTER_3: return CENTER_2;
+      }
+
+    return CENTER_0;
+    }
+}
diff --git a/src/main/java/org/distorted/objectlib/helpers/ScrambleStateSquare1.java b/src/main/java/org/distorted/objectlib/helpers/ScrambleStateSquare1.java
new file mode 100644
index 00000000..21399fb7
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/ScrambleStateSquare1.java
@@ -0,0 +1,593 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.helpers;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// producer of the ScrambleStateGraph - but only for a single Twisty Puzzle, the 'Square-1'.
+
+public class ScrambleStateSquare1
+{
+  private static final int INVALID_MOVE = -1;
+  private static final int NUM_MOVES = 23;
+
+  private int mDist;
+  private boolean mFresh;
+  private long mID;
+  private final long[] mMoves;
+
+  private final int[][] mPermittedAngles;
+  private final int[] mCornerQuat, mTmp;
+
+  // QUATS[i]*QUATS[j] = QUATS[QUAT_MULT[i][j]]
+  static final int[][] QUAT_MULT = new int[][]
+    {
+      {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,},
+      {  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,  0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 12,},
+      {  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,  0,  1, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 12, 13,},
+      {  3,  4,  5,  6,  7,  8,  9, 10, 11,  0,  1,  2, 15, 16, 17, 18, 19, 20, 21, 22, 23, 12, 13, 14,},
+      {  4,  5,  6,  7,  8,  9, 10, 11,  0,  1,  2,  3, 16, 17, 18, 19, 20, 21, 22, 23, 12, 13, 14, 15,},
+      {  5,  6,  7,  8,  9, 10, 11,  0,  1,  2,  3,  4, 17, 18, 19, 20, 21, 22, 23, 12, 13, 14, 15, 16,},
+      {  6,  7,  8,  9, 10, 11,  0,  1,  2,  3,  4,  5, 18, 19, 20, 21, 22, 23, 12, 13, 14, 15, 16, 17,},
+      {  7,  8,  9, 10, 11,  0,  1,  2,  3,  4,  5,  6, 19, 20, 21, 22, 23, 12, 13, 14, 15, 16, 17, 18,},
+      {  8,  9, 10, 11,  0,  1,  2,  3,  4,  5,  6,  7, 20, 21, 22, 23, 12, 13, 14, 15, 16, 17, 18, 19,},
+      {  9, 10, 11,  0,  1,  2,  3,  4,  5,  6,  7,  8, 21, 22, 23, 12, 13, 14, 15, 16, 17, 18, 19, 20,},
+      { 10, 11,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 22, 23, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,},
+      { 11,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 23, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,},
+      { 12, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13,  0, 11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,},
+      { 13, 12, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14,  1,  0, 11, 10,  9,  8,  7,  6,  5,  4,  3,  2,},
+      { 14, 13, 12, 23, 22, 21, 20, 19, 18, 17, 16, 15,  2,  1,  0, 11, 10,  9,  8,  7,  6,  5,  4,  3,},
+      { 15, 14, 13, 12, 23, 22, 21, 20, 19, 18, 17, 16,  3,  2,  1,  0, 11, 10,  9,  8,  7,  6,  5,  4,},
+      { 16, 15, 14, 13, 12, 23, 22, 21, 20, 19, 18, 17,  4,  3,  2,  1,  0, 11, 10,  9,  8,  7,  6,  5,},
+      { 17, 16, 15, 14, 13, 12, 23, 22, 21, 20, 19, 18,  5,  4,  3,  2,  1,  0, 11, 10,  9,  8,  7,  6,},
+      { 18, 17, 16, 15, 14, 13, 12, 23, 22, 21, 20, 19,  6,  5,  4,  3,  2,  1,  0, 11, 10,  9,  8,  7,},
+      { 19, 18, 17, 16, 15, 14, 13, 12, 23, 22, 21, 20,  7,  6,  5,  4,  3,  2,  1,  0, 11, 10,  9,  8,},
+      { 20, 19, 18, 17, 16, 15, 14, 13, 12, 23, 22, 21,  8,  7,  6,  5,  4,  3,  2,  1,  0, 11, 10,  9,},
+      { 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 23, 22,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0, 11, 10,},
+      { 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 23, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0, 11,},
+      { 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0,}
+    };
+
+  // quat indices that make corner cubits bandage the puzzle.
+  private static final int[][] BAD_CORNER_QUATS = new int[][]
+    {
+      { 2, 8,17,23},
+      { 5,11,14,20},
+    };
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ScrambleStateSquare1(long id, int dist)
+    {
+    mCornerQuat = new int[8];
+    mTmp        = new int[8];
+    mPermittedAngles = new int[2][12];
+
+    mDist = dist;
+    mFresh = true;
+    mID = id;
+    mMoves = createMoves(mID);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setNotFresh()
+    {
+    mFresh = false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean isFresh()
+    {
+    return mFresh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void computeGraph()
+    {
+    ArrayList<ScrambleStateSquare1> graph;
+
+    ScrambleStateSquare1 bsg = new ScrambleStateSquare1(0,0);
+    graph = new ArrayList<>();
+    graph.add(bsg);
+
+android.util.Log.e("D", "INSERTING");
+
+    insertChildren(graph,0);
+
+//android.util.Log.e("D", "PRUNING "+graph.size());
+
+//    pruneGraph(graph);
+
+android.util.Log.e("D", "REMAPPING "+graph.size());
+
+    remapGraph(graph);
+
+    int num = graph.size();
+    android.util.Log.e("D", "\n"+num+" states\n");
+
+    for(int i=0; i<num; i++)
+      {
+      bsg = graph.get(i);
+      android.util.Log.e("D", formatMoves(bsg));
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void insertChildren(ArrayList<ScrambleStateSquare1> list, long id)
+    {
+    ScrambleStateSquare1 bsg = findState(list,id);
+
+    if( bsg==null )
+      {
+      android.util.Log.e("D", "error: "+id+" doesn't exist");
+      return;
+      }
+
+    int dist = bsg.mDist;
+    if( dist>6 ) return;
+
+    for(int i=0; i<NUM_MOVES; i++)
+      {
+      long moveID = bsg.getMoveID(i);
+
+      if( moveID!=INVALID_MOVE )
+        {
+        ScrambleStateSquare1 tmp = findState(list,moveID);
+
+        if( tmp==null )
+          {
+          tmp = new ScrambleStateSquare1(moveID,dist+1);
+          list.add(tmp);
+          }
+        }
+      }
+
+    for(int i=0; i<NUM_MOVES; i++)
+      {
+      long moveID = bsg.getMoveID(i);
+
+      if( moveID!=INVALID_MOVE )
+        {
+        ScrambleStateSquare1 tmp = findState(list,moveID);
+
+        if( tmp.isFresh() )
+          {
+          tmp.setNotFresh();
+          insertChildren(list,moveID);
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void pruneGraph(ArrayList<ScrambleStateSquare1> list)
+    {
+    int num = list.size(), numAxis;
+    boolean pruned = false;
+    ScrambleStateSquare1 bsg;
+
+    for(int i=0; i<num; i++)
+      {
+      bsg = list.get(i);
+      numAxis = bsg.numAxis();
+
+      if( numAxis<2 )
+        {
+        list.remove(i);
+        long id = bsg.getID();
+        pruned = true;
+        remapID(list,id,INVALID_MOVE);
+        break;
+        }
+      }
+
+    if( pruned ) pruneGraph(list);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void remapGraph(ArrayList<ScrambleStateSquare1> list)
+    {
+    long id;
+    int num = list.size();
+    ScrambleStateSquare1 bsg;
+
+    for(int i=0; i<num; i++ )
+      {
+      bsg = list.get(i);
+      id = bsg.getID();
+      bsg.setID(i);
+      remapID(list,id,i);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void remapID(ArrayList<ScrambleStateSquare1> list, long id, long newId)
+    {
+    ScrambleStateSquare1 bsg;
+    int size = list.size();
+
+    for(int i=0; i<size; i++)
+      {
+      bsg = list.get(i);
+
+      for(int j=0; j<NUM_MOVES; j++)
+        {
+        if( bsg.getMoveID(j)==id ) bsg.setMoveID(j,newId);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static ScrambleStateSquare1 findState(ArrayList<ScrambleStateSquare1> list, long id)
+    {
+    ScrambleStateSquare1 bsg;
+    int num = list.size();
+
+    for(int i=0; i<num; i++)
+      {
+      bsg= list.get(i);
+      if( bsg.getID() == id ) return bsg;
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String formatMoves(ScrambleStateSquare1 bsg)
+    {
+    String x = getTable(bsg,0);
+    String y = getTable(bsg,11);
+    String z = getTable(bsg,12);
+
+    //return "    new ScrambleStateGraph( new int[][] { "+x+", "+y+", "+z+" } ),";
+
+    long id = bsg.getID();
+
+    return id+" "+x+" , "+y+" , "+z;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String getTable(ScrambleStateSquare1 sc, int index)
+    {
+    String ret = "";
+    long m;
+    int half = (NUM_MOVES)/2;
+
+    if( index==11 )
+      {
+      m = sc.getMoveID(index);
+      if( m==INVALID_MOVE ) return formatL("{}");
+      ret += formatRet(ret,0,1,m);
+      }
+    else
+      {
+      for(int i=0; i<half; i++)
+        {
+        m = sc.getMoveID(index+i);
+        int row = index==0 ? 0:2;
+        int angle = i+1;
+        if( m!=INVALID_MOVE ) ret += formatRet(ret,row,angle,m);
+        }
+      }
+
+    return formatL("{" + ret + "}");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String formatRet(String str, int row, int angle, long id)
+    {
+    String ret = str.length()!=0 ? "  ,":"  ";
+
+    ret += row;
+    ret += angle<0 ? "," : ",";
+    ret += angle;
+
+         if( id< 10 ) ret += (",  "+id);
+    else if( id<100 ) ret += (", " +id);
+    else              ret += (","  +id);
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static final int LENGTH = 28;
+
+  private static String formatL(String input)
+    {
+    int len = input.length();
+    String ret = input;
+    for(int i=0 ;i<LENGTH-len; i++) ret += " ";
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private long getID()
+    {
+    return mID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setID(long id)
+    {
+    mID = id;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private long getMoveID(int index)
+    {
+    return (index>=0 && index<NUM_MOVES) ? mMoves[index] : -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setMoveID(int index, long newID)
+    {
+    if( index>=0 && index<NUM_MOVES ) mMoves[index] = newID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int numAxis()
+    {
+    int num = 0;
+    int half = (NUM_MOVES)/2;
+
+    for(int i=0; i<half; i++)
+      {
+      if( mMoves[i]!=INVALID_MOVE )
+        {
+        num++;
+        break;
+        }
+      }
+
+    for(int i=half; i<NUM_MOVES-1; i++)
+      {
+      if( mMoves[i]!=INVALID_MOVE )
+        {
+        num++;
+        break;
+        }
+      }
+
+    if( mMoves[NUM_MOVES-1]!=INVALID_MOVE ) num++;
+
+    return num;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private long[] createMoves(long id)
+    {
+    long[] ret = new long[NUM_MOVES];
+    fillCornerQuat(id);
+    computePermittedAngles();
+
+    generateUMoves(ret, 0);
+    generateSMoves(ret,11);
+    generateLMoves(ret,12);
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void generateUMoves(long[] table, int index)
+    {
+    for(int angle=1; angle<12; angle++)
+      {
+      table[index+angle-1] = mPermittedAngles[1][angle]==1 ? updateCornerQuats(0,2,angle) : INVALID_MOVE;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void generateLMoves(long[] table, int index)
+    {
+    for(int angle=1; angle<12; angle++)
+      {
+      table[index+angle-1] = mPermittedAngles[0][angle]==1 ? updateCornerQuats(0,0,angle) : INVALID_MOVE;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void generateSMoves(long[] table, int index)
+    {
+    table[index] = updateCornerQuats(1,1,1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void fillCornerQuat(long id)
+    {
+    int NUM_QUATS = 24;
+
+    for(int i=0; i<8; i++)
+      {
+      mCornerQuat[i] = (int)(id%NUM_QUATS);
+      id/=NUM_QUATS;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static long returnID(int[] cornerQuat)
+    {
+    int NUM_QUATS = 24;
+    long mult=1,ret=0;
+
+    for(int i=0; i<8; i++)
+      {
+      ret += mult*cornerQuat[i];
+      mult*=NUM_QUATS;
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cornerIsUp(int index)
+    {
+    return ((index<4) ^ (mCornerQuat[index]>=12));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cornerIsLeft(int index)
+    {
+    int q = mCornerQuat[index];
+
+    switch(index)
+      {
+      case 0:
+      case 4: return ((q>=3 && q<= 7) || (q>=18 && q<=22));
+      case 1:
+      case 5: return ((q>=6 && q<=10) || (q>=15 && q<=19));
+      case 2:
+      case 6: return ((q==0 || q==1 || (q>=9 && q<=11)) || (q>=12 && q<=16));
+      case 3:
+      case 7: return ((q>=0 && q<=4) || (q==12 || q==13 || (q>=21 && q<=23)));
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int makeQuat(int axis,int index)
+    {
+    if( axis==1 ) return 13;
+    if( index<0 ) index+=12;
+    return index;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cornerBelongs(int index, int axis, int layer)
+    {
+    if( axis==0 )
+      {
+      boolean up = cornerIsUp(index);
+      return ((up && layer==2) || (!up && layer==0));
+      }
+    else
+      {
+      boolean le = cornerIsLeft(index);
+      return ((le && layer==0) || (!le && layer==1));
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private long updateCornerQuats(int axis, int layer, int index)
+    {
+    int quat = makeQuat(axis,index);
+
+    for(int corner=0; corner<8; corner++)
+      {
+      if( cornerBelongs(corner,axis,layer) )
+        {
+        int curr = mCornerQuat[corner];
+        mTmp[corner] = QUAT_MULT[quat][curr];
+        }
+      else
+        {
+        mTmp[corner] = mCornerQuat[corner];
+        }
+      }
+
+    return returnID(mTmp);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean quatIsBad(int quatIndex, int corner)
+    {
+    int index = (corner%2);
+
+    return ( quatIndex==BAD_CORNER_QUATS[index][0] ||
+             quatIndex==BAD_CORNER_QUATS[index][1] ||
+             quatIndex==BAD_CORNER_QUATS[index][2] ||
+             quatIndex==BAD_CORNER_QUATS[index][3]  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int isPermittedDo(int angle)
+    {
+    for(int corner=0; corner<8; corner++)
+      {
+      if( !cornerIsUp(corner) )
+        {
+        int currQuat = mCornerQuat[corner];
+        int finalQuat= QUAT_MULT[angle][currQuat];
+        if( quatIsBad(finalQuat,corner) ) return 0;
+        }
+      }
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int isPermittedUp(int angle)
+    {
+    for(int corner=0; corner<8; corner++)
+      {
+      if( cornerIsUp(corner) )
+        {
+        int currQuat = mCornerQuat[corner];
+        int finalQuat= QUAT_MULT[angle][currQuat];
+        if( quatIsBad(finalQuat,corner) ) return 0;
+        }
+      }
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computePermittedAngles()
+    {
+    for(int angle=0; angle<12; angle++)
+      {
+      mPermittedAngles[0][angle] = isPermittedDo(angle);
+      mPermittedAngles[1][angle] = isPermittedUp(angle);
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/objectlib/helpers/TwistyActivity.java b/src/main/java/org/distorted/objectlib/helpers/TwistyActivity.java
new file mode 100644
index 00000000..5435ef3e
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/TwistyActivity.java
@@ -0,0 +1,69 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.helpers;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+abstract public class TwistyActivity extends AppCompatActivity
+  {
+  boolean mIsLocked, mRemLocked;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public abstract TwistyPreRender getTwistyPreRender();
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean retLocked()
+      {
+      return mIsLocked;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void toggleLock()
+      {
+      mIsLocked = !mIsLocked;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void unlock()
+    {
+    mIsLocked = false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setLock()
+    {
+    mRemLocked = mIsLocked;
+    mIsLocked = true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void unsetLock()
+    {
+    mIsLocked = mRemLocked;
+    }
+  }
diff --git a/src/main/java/org/distorted/objectlib/helpers/TwistyPreRender.java b/src/main/java/org/distorted/objectlib/helpers/TwistyPreRender.java
new file mode 100644
index 00000000..1dd9adcb
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/TwistyPreRender.java
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.helpers;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import org.distorted.objectlib.helpers.MovesFinished;
+
+public interface TwistyPreRender
+  {
+  boolean isTouchBlocked();
+  boolean isUINotBlocked();
+  void blockTouch(int place);
+  void unblockTouch();
+  void blockEverything(int place);
+  void unblockEverything();
+  void unblockUI();
+  void addRotation(MovesFinished listener, int axis, int rowBitmap, int angle, long duration);
+  void solveObject();
+  }
diff --git a/src/main/java/org/distorted/objectlib/main/FactoryCubit.java b/src/main/java/org/distorted/objectlib/main/FactoryCubit.java
deleted file mode 100644
index 4b27a72d..00000000
--- a/src/main/java/org/distorted/objectlib/main/FactoryCubit.java
+++ /dev/null
@@ -1,941 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.objectlib.main;
-
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.effect.MatrixEffectQuaternion;
-import org.distorted.library.effect.MatrixEffectScale;
-import org.distorted.library.effect.VertexEffect;
-import org.distorted.library.effect.VertexEffectDeform;
-import org.distorted.library.main.QuatHelper;
-import org.distorted.library.mesh.MeshBase;
-import org.distorted.library.mesh.MeshJoined;
-import org.distorted.library.mesh.MeshPolygon;
-import org.distorted.library.type.Static1D;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-
-import java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class FactoryCubit
-  {
-  private static final Static1D RADIUS = new Static1D(1);
-  private static FactoryCubit mThis;
-
-  private static final double[] mBuffer = new double[3];
-  private static final double[] mQuat1  = new double[4];
-  private static final double[] mQuat2  = new double[4];
-  private static final double[] mQuat3  = new double[4];
-  private static final double[] mQuat4  = new double[4];
-
-  private static class StickerCoords
-    {
-    double[] vertices;
-    }
-
-  private static class FaceTransform
-    {
-    int sticker;
-    double vx,vy,vz;
-    double scale;
-    double qx,qy,qz,qw;
-    boolean flip;
-    }
-
-  private static final ArrayList<FaceTransform> mNewFaceTransf = new ArrayList<>();
-  private static final ArrayList<FaceTransform> mOldFaceTransf = new ArrayList<>();
-  private static final ArrayList<StickerCoords> mStickerCoords = new ArrayList<>();
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private FactoryCubit()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static FactoryCubit getInstance()
-    {
-    if( mThis==null ) mThis = new FactoryCubit();
-
-    return mThis;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// H - height of the band in the middle
-// alpha - angle of the edge  [0,90]
-// dist - often in a polygon the distance from edge to center is not 1, but something else.
-// This is the distance.
-// K - where to begin the second, much more flat part of the band. [0,1]
-// N - number of bands. N>=3
-//
-// theory: two distinct parts to the band:
-// 1) (0,B) - steep
-// 2) (B,1) - flat
-//
-// In first part, we have y = g(x) ; in second - y = g(f(x)) where
-//
-// g(x) = sqrt( R^2 - (x-D)^2 ) - R*cos(alpha)
-// f(x) = ((D-B)/(1-B)*x + B*(1-D)/(1-B)
-// h(x) = R*(sin(alpha) - sin(x))
-// R = H/(1-cos(alpha))
-// D = H*sin(alpha)
-// B = h(K*alpha)
-//
-// The N points are taken at:
-//
-// 1) in the second part, there are K2 = (N-3)/3 such points
-// 2) in the first - K1 = (N-3) - K2
-// 3) also, the 3 points 0,B,1
-//
-// so we have the sequence A[i] of N points
-//
-// 0
-// h((i+1)*(1-K)*alpha/(K1+1)) (i=0,1,...,K1-1)
-// B
-// (1-B)*(i+1)/(K2+1) + B   (i=0,i,...,K2-1)
-// 1
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float f(float D, float B, float x)
-    {
-    return ((D-B)*x + B*(1-D))/(1-B);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float g(float R, float D, float x, float cosAlpha)
-    {
-    float d = x-D;
-    return (float)(Math.sqrt(R*R-d*d)-R*cosAlpha);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float h(float R, float sinAlpha, float x)
-    {
-    return R*(sinAlpha-(float)Math.sin(x));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean areColinear(double[][] vertices, int index1, int index2, int index3)
-    {
-    double x1 = vertices[index1][0];
-    double y1 = vertices[index1][1];
-    double z1 = vertices[index1][2];
-    double x2 = vertices[index2][0];
-    double y2 = vertices[index2][1];
-    double z2 = vertices[index2][2];
-    double x3 = vertices[index3][0];
-    double y3 = vertices[index3][1];
-    double z3 = vertices[index3][2];
-
-    double v1x = x2-x1;
-    double v1y = y2-y1;
-    double v1z = z2-z1;
-    double v2x = x3-x1;
-    double v2y = y3-y1;
-    double v2z = z3-z1;
-
-    double A = Math.sqrt( (v1x*v1x+v1y*v1y+v1z*v1z) / (v2x*v2x+v2y*v2y+v2z*v2z) );
-
-    return (v1x==A*v2x && v1y==A*v2y && v1z==A*v2z);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computeNormalVector(double[][] vertices, int index1, int index2, int index3)
-    {
-    double x1 = vertices[index1][0];
-    double y1 = vertices[index1][1];
-    double z1 = vertices[index1][2];
-    double x2 = vertices[index2][0];
-    double y2 = vertices[index2][1];
-    double z2 = vertices[index2][2];
-    double x3 = vertices[index3][0];
-    double y3 = vertices[index3][1];
-    double z3 = vertices[index3][2];
-
-    double v1x = x2-x1;
-    double v1y = y2-y1;
-    double v1z = z2-z1;
-    double v2x = x3-x1;
-    double v2y = y3-y1;
-    double v2z = z3-z1;
-
-    mBuffer[0] = v1y*v2z - v2y*v1z;
-    mBuffer[1] = v1z*v2x - v2z*v1x;
-    mBuffer[2] = v1x*v2y - v2x*v1y;
-
-    double len = mBuffer[0]*mBuffer[0] + mBuffer[1]*mBuffer[1] + mBuffer[2]*mBuffer[2];
-    len = Math.sqrt(len);
-    mBuffer[0] /= len;
-    mBuffer[1] /= len;
-    mBuffer[2] /= len;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return quat1*quat2
-
-  private static void quatMultiply( double[] quat1, double[] quat2, double[] result )
-    {
-    double qx = quat1[0];
-    double qy = quat1[1];
-    double qz = quat1[2];
-    double qw = quat1[3];
-
-    double rx = quat2[0];
-    double ry = quat2[1];
-    double rz = quat2[2];
-    double rw = quat2[3];
-
-    result[0] = rw*qx - rz*qy + ry*qz + rx*qw;
-    result[1] = rw*qy + rz*qx + ry*qw - rx*qz;
-    result[2] = rw*qz + rz*qw - ry*qx + rx*qy;
-    result[3] = rw*qw - rz*qz - ry*qy - rx*qx;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void fitInSquare(FaceTransform info, double[][] vert3D)
-    {
-    double minX = Double.MAX_VALUE;
-    double maxX =-Double.MAX_VALUE;
-    double minY = Double.MAX_VALUE;
-    double maxY =-Double.MAX_VALUE;
-
-    for (double[] vert : vert3D)
-      {
-      double x = vert[0];
-      double y = vert[1];
-
-      if (x > maxX) maxX = x;
-      if (x < minX) minX = x;
-      if (y > maxY) maxY = y;
-      if (y < minY) minY = y;
-      }
-
-    minX = minX<0 ? -minX:minX;
-    maxX = maxX<0 ? -maxX:maxX;
-    minY = minY<0 ? -minY:minY;
-    maxY = maxY<0 ? -maxY:maxY;
-
-    double max1 = Math.max(minX,minY);
-    double max2 = Math.max(maxX,maxY);
-    double max3 = Math.max(max1,max2);
-
-    info.scale = max3/0.5;
-
-    int len = vert3D.length;
-    StickerCoords sInfo = new StickerCoords();
-    sInfo.vertices = new double[2*len];
-
-    for( int vertex=0; vertex<len; vertex++ )
-      {
-      sInfo.vertices[2*vertex  ] = vert3D[vertex][0] / info.scale;
-      sInfo.vertices[2*vertex+1] = vert3D[vertex][1] / info.scale;
-      }
-
-    mStickerCoords.add(sInfo);
-
-    info.sticker = mStickerCoords.size() -1;
-    info.flip = false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private FaceTransform constructNewTransform(final double[][] vert3D)
-    {
-    FaceTransform ft = new FaceTransform();
-
-    // compute center of gravity
-    ft.vx = 0.0f;
-    ft.vy = 0.0f;
-    ft.vz = 0.0f;
-    int len = vert3D.length;
-
-    for (double[] vert : vert3D)
-      {
-      ft.vx += vert[0];
-      ft.vy += vert[1];
-      ft.vz += vert[2];
-      }
-
-    ft.vx /= len;
-    ft.vy /= len;
-    ft.vz /= len;
-
-    // move all vertices so that their center of gravity is at (0,0,0)
-    for (int i=0; i<len; i++)
-      {
-      vert3D[i][0] -= ft.vx;
-      vert3D[i][1] -= ft.vy;
-      vert3D[i][2] -= ft.vz;
-      }
-
-    // find 3 non-colinear vertices
-    int foundIndex = -1;
-
-    for(int vertex=2; vertex<len; vertex++)
-      {
-      if( !areColinear(vert3D,0,1,vertex) )
-        {
-        foundIndex = vertex;
-        break;
-        }
-      }
-
-    // compute the normal vector
-    if( foundIndex==-1 )
-      {
-      throw new RuntimeException("all vertices colinear");
-      }
-
-    computeNormalVector(vert3D,0,1,foundIndex);
-
-    // rotate so that the normal vector becomes (0,0,1)
-    double axisX, axisY, axisZ;
-
-    if( mBuffer[0]!=0.0f || mBuffer[1]!=0.0f )
-      {
-      axisX = -mBuffer[1];
-      axisY =  mBuffer[0];
-      axisZ = 0.0f;
-
-      double axiLen = axisX*axisX + axisY*axisY;
-      axiLen = Math.sqrt(axiLen);
-      axisX /= axiLen;
-      axisY /= axiLen;
-      axisZ /= axiLen;
-      }
-    else
-      {
-      axisX = 0.0f;
-      axisY = 1.0f;
-      axisZ = 0.0f;
-      }
-
-    double cosTheta = mBuffer[2];
-    double sinTheta = Math.sqrt(1-cosTheta*cosTheta);
-    double sinHalfTheta = computeSinHalf(cosTheta);
-    double cosHalfTheta = computeCosHalf(sinTheta,cosTheta);
-
-    mQuat1[0] = axisX*sinHalfTheta;
-    mQuat1[1] = axisY*sinHalfTheta;
-    mQuat1[2] = axisZ*sinHalfTheta;
-    mQuat1[3] = cosHalfTheta;
-    mQuat2[0] =-axisX*sinHalfTheta;
-    mQuat2[1] =-axisY*sinHalfTheta;
-    mQuat2[2] =-axisZ*sinHalfTheta;
-    mQuat2[3] = cosHalfTheta;
-
-    for (double[] vert : vert3D)
-      {
-      quatMultiply(mQuat1, vert  , mQuat3);
-      quatMultiply(mQuat3, mQuat2, vert  );
-      }
-
-    // fit the whole thing in a square and remember the scale & 2D vertices
-    fitInSquare(ft, vert3D);
-
-    // remember the rotation
-    ft.qx =-mQuat1[0];
-    ft.qy =-mQuat1[1];
-    ft.qz =-mQuat1[2];
-    ft.qw = mQuat1[3];
-
-    return ft;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void rotateAllVertices(double[] result, int len, double[] vertices, double sin, double cos)
-    {
-    for(int i=0; i<len; i++)
-      {
-      result[2*i  ] = vertices[2*i  ]*cos - vertices[2*i+1]*sin;
-      result[2*i+1] = vertices[2*i  ]*sin + vertices[2*i+1]*cos;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private double computeScale(double[] v1, double[] v2, int v1i, int v2i)
-    {
-    double v1x = v1[2*v1i];
-    double v1y = v1[2*v1i+1];
-    double v2x = v2[2*v2i];
-    double v2y = v2[2*v2i+1];
-
-    double lenSq1 = v1x*v1x + v1y*v1y;
-    double lenSq2 = v2x*v2x + v2y*v2y;
-
-    return Math.sqrt(lenSq2/lenSq1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// valid for 0<angle<2*PI
-
-  private double computeSinHalf(double cos)
-    {
-    return Math.sqrt((1-cos)/2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// valid for 0<angle<2*PI
-
-  private double computeCosHalf(double sin, double cos)
-    {
-    double cosHalf = Math.sqrt((1+cos)/2);
-    return sin<0 ? -cosHalf : cosHalf;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeRotatedIndex(int oldVertex, int len, int rotatedVertex, boolean inverted)
-    {
-    int v = (rotatedVertex + (inverted? -oldVertex : oldVertex));
-    if( v>=len ) v-=len;
-    if( v< 0   ) v+=len;
-
-    return v;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean isScaledVersionOf(double[] newVert, double[] oldVert, int len, int vertex, boolean inverted)
-    {
-    int newZeroIndex = computeRotatedIndex(0,len,vertex,inverted);
-    double EPSILON = 0.001;
-    double scale = computeScale(newVert,oldVert,newZeroIndex,0);
-
-    for(int i=1; i<len; i++)
-      {
-      int index = computeRotatedIndex(i,len,vertex,inverted);
-
-      double horz = oldVert[2*i  ] - scale*newVert[2*index  ];
-      double vert = oldVert[2*i+1] - scale*newVert[2*index+1];
-
-      if( horz>EPSILON || horz<-EPSILON || vert>EPSILON || vert<-EPSILON ) return false;
-      }
-
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void mirrorAllVertices(double[] output, int len, double[] input)
-    {
-    for(int vertex=0; vertex<len; vertex++)
-      {
-      output[2*vertex  ] = input[2*vertex  ];
-      output[2*vertex+1] =-input[2*vertex+1];
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void correctInfo(FaceTransform info, double scale, double sin, double cos, int oldSticker, boolean flip)
-    {
-    mStickerCoords.remove(info.sticker);
-
-    info.flip    = flip;
-    info.sticker = oldSticker;
-    info.scale  *= scale;
-
-    mQuat1[0] = info.qx;
-    mQuat1[1] = info.qy;
-    mQuat1[2] = info.qz;
-    mQuat1[3] = info.qw;
-
-    double sinHalf = computeSinHalf(cos);
-    double cosHalf = computeCosHalf(sin,cos);
-
-    if( flip )
-      {
-      mQuat3[0] = 0.0f;
-      mQuat3[1] = 0.0f;
-      mQuat3[2] = sinHalf;
-      mQuat3[3] = cosHalf;
-
-      mQuat4[0] = 1.0;
-      mQuat4[1] = 0.0;
-      mQuat4[2] = 0.0;
-      mQuat4[3] = 0.0;
-
-      quatMultiply( mQuat3, mQuat4, mQuat2 );
-      }
-    else
-      {
-      mQuat2[0] = 0.0f;
-      mQuat2[1] = 0.0f;
-      mQuat2[2] = sinHalf;
-      mQuat2[3] = cosHalf;
-      }
-
-    quatMultiply( mQuat1, mQuat2, mQuat3 );
-
-    info.qx = mQuat3[0];
-    info.qy = mQuat3[1];
-    info.qz = mQuat3[2];
-    info.qw = mQuat3[3];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void printVert(double[] buffer)
-    {
-    int len = buffer.length/2;
-    String str = "";
-
-    for(int i=0; i<len; i++)
-      {
-      str += (" ("+buffer[2*i]+" , "+buffer[2*i+1]+" ) ");
-      }
-
-    android.util.Log.d("D", str);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean foundVertex(FaceTransform info, double[] buffer, int len, double[] newVert,
-                              double[] oldVert, double lenFirstOld, int oldSticker, boolean inverted)
-    {
-    for(int vertex=0; vertex<len; vertex++)
-      {
-      double newX = newVert[2*vertex  ];
-      double newY = newVert[2*vertex+1];
-      double lenIthNew = Math.sqrt(newX*newX + newY*newY);
-      double cos = QuatHelper.computeCos( oldVert[0], oldVert[1], newX, newY, lenIthNew, lenFirstOld);
-      double sin = QuatHelper.computeSin( oldVert[0], oldVert[1], newX, newY, lenIthNew, lenFirstOld);
-
-      rotateAllVertices(buffer,len,newVert,sin,cos);
-
-      if( isScaledVersionOf(buffer,oldVert,len,vertex,inverted) )
-        {
-        int newZeroIndex = computeRotatedIndex(0,len,vertex,inverted);
-        double scale = computeScale(oldVert,newVert,0,newZeroIndex);
-        correctInfo(info,scale,sin,cos,oldSticker,inverted);
-        return true;
-        }
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean successfullyCollapsedStickers(final FaceTransform newInfo, final FaceTransform oldInfo)
-    {
-    StickerCoords sNewInfo = mStickerCoords.get(newInfo.sticker);
-    StickerCoords sOldInfo = mStickerCoords.get(oldInfo.sticker);
-    double[] newVert = sNewInfo.vertices;
-    double[] oldVert = sOldInfo.vertices;
-    int oldLen = oldVert.length;
-    int newLen = newVert.length;
-
-    if( oldLen == newLen )
-      {
-      int oldSticker = oldInfo.sticker;
-      double[] buffer1 = new double[oldLen];
-      double lenFirstOld = Math.sqrt(oldVert[0]*oldVert[0] + oldVert[1]*oldVert[1]);
-      if( foundVertex(newInfo, buffer1, oldLen/2, newVert, oldVert, lenFirstOld, oldSticker, false) ) return true;
-      double[] buffer2 = new double[oldLen];
-      mirrorAllVertices(buffer2, newLen/2, newVert);
-      if( foundVertex(newInfo, buffer1, oldLen/2, buffer2, oldVert, lenFirstOld, oldSticker, true ) ) return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private double[][] constructVert(double[][] vertices, int[] index)
-    {
-    int len = index.length;
-    double[][] ret = new double[len][4];
-
-    for(int i=0; i<len; i++)
-      {
-      ret[i][0] = vertices[index[i]][0];
-      ret[i][1] = vertices[index[i]][1];
-      ret[i][2] = vertices[index[i]][2];
-      ret[i][3] = 1.0f;
-      }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void prepareAndRoundCorners(MeshBase mesh, double[][] vertices,
-                                      float[][] corners, int[] cornerIndexes,
-                                      float[][] centers, int[] centerIndexes )
-    {
-    int lenV = vertices.length;
-    Static3D[] staticVert = new Static3D[1];
-    Static3D center = new Static3D(0,0,0);
-
-    for(int v=0; v<lenV; v++)
-      {
-      staticVert[0] = new Static3D( (float)vertices[v][0], (float)vertices[v][1], (float)vertices[v][2]);
-
-      int cent = centerIndexes[v];
-
-      if( cent>=0 )
-        {
-        center.set( centers[cent][0], centers[cent][1], centers[cent][2]);
-
-        int corn = cornerIndexes[v];
-
-        if( corn>=0 )
-          {
-          float strength = corners[corn][0];
-          float radius   = corners[corn][1];
-          roundCorners(mesh, center, staticVert, strength, radius);
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void correctComponents(MeshBase mesh, int numComponents)
-    {
-    int numTexToBeAdded = numComponents-mesh.getNumTexComponents();
-
-    mesh.mergeEffComponents();
-
-    for(int i=0; i<numTexToBeAdded; i++ ) mesh.addEmptyTexComponent();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void printTransform(FaceTransform f)
-    {
-    android.util.Log.e("D", "q=("+f.qx+", "+f.qy+", "+f.qz+", "+f.qw+") v=("
-                       +f.vx+", "+f.vy+", "+f.vz+") scale="+f.scale+" sticker="+f.sticker);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC
-
-  public float[] computeBands(float H, int alpha, float dist, float K, int N)
-    {
-    float[] bands = new float[2*N];
-
-    bands[0] = 1.0f;
-    bands[1] = 0.0f;
-
-    float beta = (float)Math.atan(dist*Math.tan(Math.PI*alpha/180));
-    float sinBeta = (float)Math.sin(beta);
-    float cosBeta = (float)Math.cos(beta);
-    float R = cosBeta<1.0f ? H/(1.0f-cosBeta) : 0.0f;
-    float D = R*sinBeta;
-    float B = h(R,sinBeta,K*beta);
-
-    if( D>1.0f )
-      {
-      for(int i=1; i<N; i++)
-        {
-        bands[2*i  ] = (float)(N-1-i)/(N-1);
-        bands[2*i+1] = H*(1-bands[2*i]);
-        }
-      }
-    else
-      {
-      int K2 = (int)((N-3)*K);
-      int K1 = (N-3)-K2;
-
-      for(int i=0; i<=K1; i++)
-        {
-        float angle = K*beta + (1-K)*beta*(K1-i)/(K1+1);
-        float x = h(R,sinBeta,angle);
-        bands[2*i+2] = 1.0f - x;
-        bands[2*i+3] = g(R,D,x,cosBeta);
-        }
-
-      for(int i=0; i<=K2; i++)
-        {
-        float x = (1-B)*(i+1)/(K2+1) + B;
-        bands[2*K1+2 + 2*i+2] = 1.0f - x;
-        bands[2*K1+2 + 2*i+3] = g(R,D,f(D,B,x),cosBeta);
-        }
-      }
-
-    bands[2*N-2] = 0.0f;
-    bands[2*N-1] =    H;
-
-    return bands;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void roundCorners(MeshBase mesh, Static3D center, Static3D[] vertices, float strength, float regionRadius)
-    {
-    Static4D reg= new Static4D(0,0,0,regionRadius);
-
-    float centX = center.get0();
-    float centY = center.get1();
-    float centZ = center.get2();
-
-    for (Static3D vertex : vertices)
-      {
-      float x = strength*(centX - vertex.get0());
-      float y = strength*(centY - vertex.get1());
-      float z = strength*(centZ - vertex.get2());
-
-      VertexEffect effect = new VertexEffectDeform(new Static3D(x,y,z), RADIUS, vertex, reg);
-      mesh.apply(effect);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void printStickerCoords()
-    {
-    int stickers = mStickerCoords.size();
-
-    android.util.Log.d("D", "---- STICKER COORDS ----");
-
-    for(int s=0; s<stickers; s++)
-      {
-      String ver = "{ ";
-      StickerCoords info = mStickerCoords.get(s);
-      int len = info.vertices.length/2;
-
-      for(int i =0; i<len; i++)
-        {
-        if( i!=0 ) ver += ", ";
-        ver += ( (float)info.vertices[2*i]+"f, "+(float)info.vertices[2*i+1]+"f");
-        }
-
-      ver += " }";
-      android.util.Log.d("D", ver);
-      }
-
-    android.util.Log.d("D", "---- END STICKER COORDS ----");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void printFaceTransform()
-    {
-    android.util.Log.d("D", "---- OLD FACE TRANSFORM ---");
-
-    int oldfaces = mOldFaceTransf.size();
-
-    for(int f=0; f<oldfaces; f++)
-      {
-      printTransform(mOldFaceTransf.get(f));
-      }
-
-    android.util.Log.d("D", "---- NEW FACE TRANSFORM ---");
-
-    int newfaces = mNewFaceTransf.size();
-
-    for(int f=0; f<newfaces; f++)
-      {
-      printTransform(mNewFaceTransf.get(f));
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void clear()
-    {
-    mStickerCoords.clear();
-    mNewFaceTransf.clear();
-    mOldFaceTransf.clear();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void createNewFaceTransform( final double[][] vertices, final int[][] indexes)
-    {
-    FaceTransform ft;
-    int numNew = mNewFaceTransf.size();
-
-    for(int i=0; i<numNew; i++)
-      {
-      ft = mNewFaceTransf.remove(0);
-      mOldFaceTransf.add(ft);
-      }
-
-    int numFaces = indexes.length;
-    int numOld = mOldFaceTransf.size();
-
-    for (int face=0; face<numFaces; face++)
-      {
-      boolean collapsed = false;
-
-      double[][] vert = constructVert(vertices, indexes[face]);
-      FaceTransform newT = constructNewTransform(vert);
-
-      for (int old=0; !collapsed && old<numOld; old++)
-        {
-        ft = mOldFaceTransf.get(old);
-        if (successfullyCollapsedStickers(newT, ft)) collapsed = true;
-        }
-
-      for (int pre=0; !collapsed && pre<face; pre++)
-        {
-        ft = mNewFaceTransf.get(pre);
-        if (successfullyCollapsedStickers(newT, ft)) collapsed = true;
-        }
-
-      mNewFaceTransf.add(newT);
-      }
-    }
-
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void createNewFaceTransform(final ObjectShape shape)
-    {
-    double[][] vertices = shape.getVertices();
-    int[][] indices = shape.getVertIndices();
-    createNewFaceTransform(vertices,indices);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computeConvexityCenter(double[] out, float[] in, FaceTransform ft)
-    {
-    if( in==null )
-      {
-      out[0] = out[1] = 0.0f;
-      }
-    else
-      {
-      out[0] = in[0] - ft.vx;
-      out[1] = in[1] - ft.vy;
-      out[2] = in[2] - ft.vz;
-      out[3] = 1.0f;
-
-      mQuat1[0] =-ft.qx;
-      mQuat1[1] =-ft.qy;
-      mQuat1[2] =-ft.qz;
-      mQuat1[3] = ft.qw;
-
-      mQuat2[0] = -mQuat1[0];
-      mQuat2[1] = -mQuat1[1];
-      mQuat2[2] = -mQuat1[2];
-      mQuat2[3] = +mQuat1[3];
-
-      quatMultiply(mQuat1, out  , mQuat3);
-      quatMultiply(mQuat3, mQuat2, out  );
-
-      out[0] /= ft.scale;
-      out[1] /= ft.scale;
-      out[2] /= ft.scale;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public MeshBase createRoundedSolid(final ObjectShape shape)
-    {
-    double[][] vertices     = shape.getVertices();
-    int[][] vertIndexes     = shape.getVertIndices();
-    float[][] bands         = shape.getBands();
-    int[]   bandIndexes     = shape.getBandIndices();
-    float[][] corners       = shape.getCorners();
-    int[]   cornerIndexes   = shape.getCornerIndices();
-    float[][] centers       = shape.getCenters();
-    int[]   centerIndexes   = shape.getCenterIndices();
-    int numComponents       = shape.getNumComponents();
-    float[] convexityCenter = shape.getConvexityCenter();
-
-    return createRoundedSolid(vertices,vertIndexes,bands,bandIndexes,corners,cornerIndexes,
-                              centers,centerIndexes,numComponents,convexityCenter);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public MeshBase createRoundedSolid(final double[][] vertices, final int[][] vertIndexes,
-                                     final float[][] bands    , final int[]   bandIndexes,
-                                     final float[][] corners  , final int[]   cornerIndexes,
-                                     final float[][] centers  , final int[]   centerIndexes,
-                                     final int numComponents  , final float[] convexityCenter )
-    {
-    int numFaces = vertIndexes.length;
-    float[] band, bandsComputed;
-    MeshBase[] meshes = new MeshBase[numFaces];
-    FaceTransform fInfo;
-    StickerCoords sInfo;
-    double[] convexXY = new double[4];
-
-    for(int face=0; face<numFaces; face++)
-      {
-      fInfo = mNewFaceTransf.get(face);
-      sInfo = mStickerCoords.get(fInfo.sticker);
-
-      double[] verts = sInfo.vertices;
-      int lenVerts = verts.length;
-      float[] vertsFloat = new float[lenVerts];
-      for(int i=0; i<lenVerts; i++) vertsFloat[i] = (float)verts[i];
-
-      computeConvexityCenter(convexXY,convexityCenter,fInfo);
-
-      band = bands[bandIndexes[face]];
-      bandsComputed = computeBands( band[0], (int)band[1], band[2], band[3], (int)band[4]);
-      meshes[face] = new MeshPolygon(vertsFloat,bandsComputed,(int)band[5],(int)band[6], (float)convexXY[0], (float)convexXY[1]);
-      meshes[face].setEffectAssociation(0,(1<<face),0);
-      }
-
-    MeshBase mesh = new MeshJoined(meshes);
-    Static3D center = new Static3D(0,0,0);
-
-    for(int face=0; face<numFaces; face++)
-      {
-      int assoc = (1<<face);
-      fInfo = mNewFaceTransf.get(face);
-
-      float vx = (float)fInfo.vx;
-      float vy = (float)fInfo.vy;
-      float vz = (float)fInfo.vz;
-      float sc = (float)fInfo.scale;
-      float qx = (float)fInfo.qx;
-      float qy = (float)fInfo.qy;
-      float qz = (float)fInfo.qz;
-      float qw = (float)fInfo.qw;
-
-      Static3D scale = new Static3D(sc,sc, fInfo.flip ? -sc : sc);
-      Static3D move3D= new Static3D(vx,vy,vz);
-      Static4D quat  = new Static4D(qx,qy,qz,qw);
-
-      mesh.apply(new MatrixEffectScale(scale)           ,assoc,-1);
-      mesh.apply(new MatrixEffectQuaternion(quat,center),assoc,-1);
-      mesh.apply(new MatrixEffectMove(move3D)           ,assoc,-1);
-      }
-
-    prepareAndRoundCorners(mesh, vertices, corners, cornerIndexes, centers, centerIndexes);
-
-    correctComponents(mesh,numComponents);
-
-    return mesh;
-    }
-  }
diff --git a/src/main/java/org/distorted/objectlib/main/FactorySticker.java b/src/main/java/org/distorted/objectlib/main/FactorySticker.java
deleted file mode 100644
index c7ca7cde..00000000
--- a/src/main/java/org/distorted/objectlib/main/FactorySticker.java
+++ /dev/null
@@ -1,315 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.objectlib.main;
-
-import static org.distorted.objectlib.main.TwistyObject.COLOR_BLACK;
-import static org.distorted.objectlib.main.TwistyObject.TEXTURE_HEIGHT;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class FactorySticker
-  {
-  private static FactorySticker mThis;
-  private float mOX, mOY, mR;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private FactorySticker()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static FactorySticker getInstance()
-    {
-    if( mThis==null ) mThis = new FactorySticker();
-
-    return mThis;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float computeAngle(float dx, float dy)
-    {
-    float PI = (float)Math.PI;
-    double angle = Math.atan2(dy,dx);
-    float ret = (float)(3*PI/2-angle);
-
-    if( ret>2*PI ) ret-= 2*PI;
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float getAngle(float[] angles, int index)
-    {
-    return angles==null ? 0 : angles[index];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computeCircleCoords(float lX,float lY, float rX, float rY, float alpha)
-    {
-    float ctg= 1.0f/((float)Math.tan(alpha));
-    mOX = 0.5f*(lX+rX) + ctg*0.5f*(lY-rY);
-    mOY = 0.5f*(lY+rY) - ctg*0.5f*(lX-rX);
-    float dx = mOX-lX;
-    float dy = mOY-lY;
-    mR = (float)Math.sqrt(dx*dx+dy*dy);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// circle1: center (x1,y1) radius r1; circle2: center (x2,y2) radius r2.
-// Guaranteed to intersect in two points. Find the intersection. Which one? the one that's closer
-// to (nearx,neary).
-
-  private void findCircleIntersection(float x1,float y1, float r1, float x2, float y2, float r2, float nearx, float neary )
-    {
-    float dx = x2-x1;
-    float dy = y2-y1;
-    float d = (float)Math.sqrt(dx*dx+dy*dy);
-
-    if( d>0 )
-      {
-      float Dx = dx/d;
-      float Dy = dy/d;
-      float cos = (r1*r1+d*d-r2*r2)/(2*r1*d);
-      float sin = (float)Math.sqrt(1-cos*cos);
-
-      float ox1 = x1 + r1*cos*Dx + r1*sin*Dy;
-      float oy1 = y1 + r1*cos*Dy - r1*sin*Dx;
-      float ox2 = x1 + r1*cos*Dx - r1*sin*Dy;
-      float oy2 = y1 + r1*cos*Dy + r1*sin*Dx;
-
-      dx = nearx-ox1;
-      dy = neary-oy1;
-      float d1 = dx*dx+dy*dy;
-      dx = nearx-ox2;
-      dy = neary-oy2;
-      float d2 = dx*dx+dy*dy;
-
-      if( d1<d2 )
-        {
-        mOX = ox1;
-        mOY = oy1;
-        }
-      else
-        {
-        mOX = ox2;
-        mOY = oy2;
-        }
-      }
-    else
-      {
-      mOX = x1;
-      mOY = y1;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void drawCurrCurveV(Canvas canvas, Paint paint, int left, int top, float r, float stroke, float pX, float pY, float cX, float cY, float nX, float nY, float pA, float cA)
-    {
-    pX = (0.5f+pX)*TEXTURE_HEIGHT;
-    pY = (0.5f-pY)*TEXTURE_HEIGHT;
-    cX = (0.5f+cX)*TEXTURE_HEIGHT;
-    cY = (0.5f-cY)*TEXTURE_HEIGHT;
-    nX = (0.5f+nX)*TEXTURE_HEIGHT;
-    nY = (0.5f-nY)*TEXTURE_HEIGHT;
-
-    computeCircleCoords(pX,pY,cX,cY,pA);
-    float o1x = mOX;
-    float o1y = mOY;
-    float r1  = mR;
-    computeCircleCoords(cX,cY,nX,nY,cA);
-    float o2x = mOX;
-    float o2y = mOY;
-    float r2  = mR;
-
-    float dx = o1x-pX;
-    float dy = o1y-pY;
-    float startA = computeAngle(dy,dx);
-    float sweepA = 2*pA;
-
-    startA *= 180/(Math.PI);
-    sweepA *= 180/(Math.PI);
-
-    canvas.drawArc( left+o1x-r1, top+o1y-r1, left+o1x+r1, top+o1y+r1, startA, sweepA, false, paint);
-
-    float r3 = r*TEXTURE_HEIGHT + stroke/2;
-    float R1 = r1 + (pA < 0 ? r3:-r3);
-    float R2 = r2 + (cA < 0 ? r3:-r3);
-    findCircleIntersection(o1x,o1y,R1,o2x,o2y,R2,cX,cY);
-    float o3x = mOX;
-    float o3y = mOY;
-
-    dx = pA<0 ? o3x-o1x : o1x-o3x;
-    dy = pA<0 ? o3y-o1y : o1y-o3y;
-    startA = computeAngle(dy,dx);
-    dx = cA<0 ? o3x-o2x : o2x-o3x;
-    dy = cA<0 ? o3y-o2y : o2y-o3y;
-    float endA = computeAngle(dy,dx);
-
-    sweepA = endA-startA;
-    if( sweepA<0 ) sweepA += 2*Math.PI;
-
-    startA *= 180/(Math.PI);
-    sweepA *= 180/(Math.PI);
-
-    canvas.drawArc( left+o3x-r3, top+o3y-r3, left+o3x+r3, top+o3y+r3, startA, sweepA, false, paint);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void drawCurrVertex(Canvas canvas, Paint paint, int left, int top, float r, float stroke, float pX, float pY, float cX, float cY, float nX, float nY)
-    {
-    pX = (0.5f+pX)*TEXTURE_HEIGHT;
-    pY = (0.5f-pY)*TEXTURE_HEIGHT;
-    cX = (0.5f+cX)*TEXTURE_HEIGHT;
-    cY = (0.5f-cY)*TEXTURE_HEIGHT;
-    nX = (0.5f+nX)*TEXTURE_HEIGHT;
-    nY = (0.5f-nY)*TEXTURE_HEIGHT;
-
-    canvas.drawLine(left+pX,top+pY,left+cX,top+cY,paint);
-
-    float aX = pX-cX;
-    float aY = pY-cY;
-    float bX = cX-nX;
-    float bY = cY-nY;
-
-    float aLen = (float)Math.sqrt(aX*aX+aY*aY);
-    float bLen = (float)Math.sqrt(bX*bX+bY*bY);
-
-    aX /= aLen;
-    aY /= aLen;
-    bX /= bLen;
-    bY /= bLen;
-
-    float sX = (aX-bX)/2;
-    float sY = (aY-bY)/2;
-    float sLen = (float)Math.sqrt(sX*sX+sY*sY);
-    sX /= sLen;
-    sY /= sLen;
-
-    float startAngle = computeAngle(bX,-bY);
-    float endAngle   = computeAngle(aX,-aY);
-    float sweepAngle = endAngle-startAngle;
-    if( sweepAngle<0 ) sweepAngle += 2*Math.PI;
-
-    float R = r*TEXTURE_HEIGHT+stroke/2;
-    float C = (float)Math.cos(sweepAngle/2);
-    float A = R/C;
-
-    left += (cX+A*sX);
-    top  += (cY+A*sY);
-
-    if( C< (2*R-stroke)/(2*R+stroke) )
-      {
-      float alpha = startAngle + sweepAngle/2;
-      float B  = (R-stroke/2)/C;
-      float sx = (float)Math.cos(alpha);
-      float sy = (float)Math.sin(alpha);
-
-      float startX = left + R*sx;
-      float startY = top  + R*sy;
-      float stopX  = left + B*sx;
-      float stopY  = top  + B*sy;
-
-      canvas.drawLine(startX,startY,stopX,stopY,paint);
-      }
-
-    startAngle *= 180/(Math.PI);
-    sweepAngle *= 180/(Math.PI);
-
-    canvas.drawArc( left-R, top-R, left+R, top+R, startAngle, sweepAngle, false, paint);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC
-
-  public void drawRoundedPolygon(Canvas canvas, Paint paint, int left, int top, int color, ObjectSticker sticker)
-    {
-    float stroke = sticker.getStroke();
-    float[] vertices = sticker.getCoords();
-    float[] angles = sticker.getCurvature();
-    float[] radii = sticker.getRadii();
-
-    stroke *= TEXTURE_HEIGHT;
-
-    paint.setAntiAlias(true);
-    paint.setStrokeWidth(stroke);
-    paint.setColor(color);
-    paint.setStyle(Paint.Style.FILL);
-
-    canvas.drawRect(left,top,left+TEXTURE_HEIGHT,top+TEXTURE_HEIGHT,paint);
-
-    paint.setColor(COLOR_BLACK);
-    paint.setStyle(Paint.Style.STROKE);
-
-    int length = vertices.length;
-    int numVertices = length/2;
-
-    float prevX = vertices[length-2];
-    float prevY = vertices[length-1];
-    float currX = vertices[0];
-    float currY = vertices[1];
-    float nextX = vertices[2];
-    float nextY = vertices[3];
-
-    float prevA = getAngle(angles,numVertices-1);
-    float currA = getAngle(angles,0);
-
-    for(int vert=0; vert<numVertices; vert++)
-      {
-      if( prevA==0 )
-        {
-        drawCurrVertex(canvas, paint, left, top, radii[vert], stroke, prevX,prevY,currX,currY,nextX,nextY);
-        }
-      else
-        {
-        drawCurrCurveV(canvas, paint, left, top, radii[vert], stroke, prevX,prevY,currX,currY,nextX,nextY,prevA,currA);
-        }
-
-      prevX = currX;
-      prevY = currY;
-      currX = nextX;
-      currY = nextY;
-
-      prevA = currA;
-      currA = getAngle(angles, vert==numVertices-1 ? 0 : vert+1);
-
-      if( 2*(vert+2)+1 < length )
-        {
-        nextX = vertices[2*(vert+2)  ];
-        nextY = vertices[2*(vert+2)+1];
-        }
-      else
-        {
-        nextX = vertices[0];
-        nextY = vertices[1];
-        }
-      }
-    }
-  }
diff --git a/src/main/java/org/distorted/objectlib/main/ObjectShape.java b/src/main/java/org/distorted/objectlib/main/ObjectShape.java
deleted file mode 100644
index 7136dd6f..00000000
--- a/src/main/java/org/distorted/objectlib/main/ObjectShape.java
+++ /dev/null
@@ -1,124 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2021 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.main;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class ObjectShape
-  {
-  private final double[][] mVertices;
-  private final int[][] mVertIndices;
-  private final float[][] mBands;
-  private final int[] mBandIndices;
-  private final float[][] mCorners;
-  private final int[] mCornerIndices;
-  private final float[][] mCenters;
-  private final int[] mCenterIndices;
-  private final int mNumComponents;
-  private final float[] mConvexityCenter;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ObjectShape(double[][] vertices, int[][] vertIndices, float[][] bands, int[] bandIndices,
-                     float[][] corners, int[] cornIndices, float[][] centers, int[] centIndices,
-                     int numComponents, float[] convexityCenter)
-    {
-    mVertices        = vertices;
-    mVertIndices     = vertIndices;
-    mBands           = bands;
-    mBandIndices     = bandIndices;
-    mCorners         = corners;
-    mCornerIndices   = cornIndices;
-    mCenters         = centers;
-    mCenterIndices   = centIndices;
-    mNumComponents   = numComponents;
-    mConvexityCenter = convexityCenter;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public double[][] getVertices()
-    {
-    return mVertices;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] getVertIndices()
-    {
-    return mVertIndices;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float[][] getBands()
-    {
-    return mBands;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[] getBandIndices()
-    {
-    return mBandIndices;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float[][] getCorners()
-    {
-    return mCorners;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[] getCornerIndices()
-    {
-    return mCornerIndices;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float[][] getCenters()
-    {
-    return mCenters;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[] getCenterIndices()
-    {
-    return mCenterIndices;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getNumComponents()
-    {
-    return mNumComponents;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float[] getConvexityCenter()
-    {
-    return mConvexityCenter;
-    }
-  }
diff --git a/src/main/java/org/distorted/objectlib/main/ObjectSticker.java b/src/main/java/org/distorted/objectlib/main/ObjectSticker.java
deleted file mode 100644
index e9d1514c..00000000
--- a/src/main/java/org/distorted/objectlib/main/ObjectSticker.java
+++ /dev/null
@@ -1,68 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2021 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.main;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class ObjectSticker
-  {
-  private final float[] mCoords;
-  private final float[] mCurvature;
-  private final float[] mRadii;
-  private final float mStroke;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ObjectSticker(float[] coords, float[] curvature, float[] radii, float stroke)
-    {
-    mCoords    = coords;
-    mCurvature = curvature;
-    mRadii     = radii;
-    mStroke    = stroke;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float[] getCoords()
-    {
-    return mCoords;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float[] getCurvature()
-    {
-    return mCurvature;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float[] getRadii()
-    {
-    return mRadii;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float getStroke()
-    {
-    return mStroke;
-    }
-  }
diff --git a/src/main/java/org/distorted/objectlib/main/ScrambleState.java b/src/main/java/org/distorted/objectlib/main/ScrambleState.java
deleted file mode 100644
index 63aac047..00000000
--- a/src/main/java/org/distorted/objectlib/main/ScrambleState.java
+++ /dev/null
@@ -1,141 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2021 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.main;
-
-import java.util.Random;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class ScrambleState
-{
-  private final int mTotal, mNumAxis;
-  private final int[] mNum;
-  private final int[] mInfo;
-  private final int[] mTmp;
-  private final int LEN = 4;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ScrambleState(int[][] axis)
-    {
-    mTmp = new int[LEN];
-
-    mNumAxis = axis.length;
-    mNum = new int[mNumAxis];
-    int total =0;
-
-    for(int i=0; i<mNumAxis; i++)
-      {
-      mNum[i] = axis[i]==null ? 0 : axis[i].length/(LEN-1);
-      total += mNum[i];
-      }
-
-    mTotal = total;
-
-    mInfo = new int[LEN*total];
-    int start = 0;
-
-    for(int i=0; i<mNumAxis; i++)
-      {
-      for(int j=0; j<mNum[i]; j++)
-        {
-        mInfo[LEN*j   + start] = i;
-        mInfo[LEN*j+1 + start] = axis[i][(LEN-1)*j  ];
-        mInfo[LEN*j+2 + start] = axis[i][(LEN-1)*j+1];
-        mInfo[LEN*j+3 + start] = axis[i][(LEN-1)*j+2];
-        }
-
-      start += LEN*mNum[i];
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getIndex(int num, int indexExcluded)
-    {
-    int current= -1;
-
-    for(int i=0; i<mTotal; i++)
-      if( mInfo[LEN*i]!=indexExcluded && ++current==num ) return i;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[] getRandom(Random rnd, int indexExcluded, int[][] scrambleTable, int[] numOccurences)
-    {
-    int num=0, total=0, max=0, ax, layer;
-
-    for(int i=0; i<mTotal; i++)
-      {
-      ax = mInfo[LEN*i];
-
-      if( ax!=indexExcluded )
-        {
-        layer = mInfo[LEN*i+1];
-        int value = scrambleTable[ax][layer];
-        if( value>max ) max=value;
-        }
-      }
-
-    for(int i=0; i<mTotal; i++)
-      {
-      ax = mInfo[LEN*i];
-
-      if( ax!=indexExcluded )
-        {
-        layer = mInfo[LEN*i+1];
-        int value = scrambleTable[ax][layer];
-        numOccurences[total] = 1 + max - value + (total==0 ? 0 : numOccurences[total-1]);
-        total++;
-        }
-      }
-
-    float random= rnd.nextFloat()*numOccurences[total-1];
-
-    for(int i=0; i<total; i++)
-      {
-      if( random <= numOccurences[i] )
-        {
-        num=i;
-        break;
-        }
-      }
-
-    int index = getIndex(num,indexExcluded);
-
-    mTmp[0] = mInfo[LEN*index  ];   // axis
-    mTmp[1] = mInfo[LEN*index+1];   // row
-    mTmp[2] = mInfo[LEN*index+2];   // angle
-    mTmp[3] = mInfo[LEN*index+3];   // next state
-
-    scrambleTable[mTmp[0]][mTmp[1]]++;
-
-    return mTmp;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getTotal(int indexExcluded)
-    {
-    return ( indexExcluded>=0 && indexExcluded<mNumAxis ) ? mTotal-mNum[indexExcluded] : mTotal;
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/objectlib/main/ScrambleStateBandagedEvil.java b/src/main/java/org/distorted/objectlib/main/ScrambleStateBandagedEvil.java
deleted file mode 100644
index 69f1df33..00000000
--- a/src/main/java/org/distorted/objectlib/main/ScrambleStateBandagedEvil.java
+++ /dev/null
@@ -1,669 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2021 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.main;
-
-import java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// producer of the ScrambleStateGraph - but only for a single Twisty Puzzle, the 'BandagedEvil'.
-
-public class ScrambleStateBandagedEvil
-{
-  private static final int INVALID_MOVE = -1;
-
-  private static final int CORNER_S = 0;
-  private static final int CORNER_X = 1;
-  private static final int CORNER_Y = 2;
-  private static final int CORNER_Z = 3;
-
-  private static final int CENTER_0 = 0;
-  private static final int CENTER_1 = 1;
-  private static final int CENTER_2 = 2;
-  private static final int CENTER_3 = 3;
-
-  private int mID;
-  private final int[] mMoves;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ScrambleStateBandagedEvil(int id)
-    {
-    mID = id;
-    mMoves = createMoves(mID);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void computeGraph()
-    {
-    ArrayList<ScrambleStateBandagedEvil> graph;
-
-    int id = 0;
-    int id1 = setCenter(id  , CENTER_2, 0);
-    int id2 = setCenter(id1 , CENTER_2, 1);
-    int id3 = setCenter(id2 , CENTER_3, 2);
-    int id4 = setCenter(id3 , CENTER_2, 3);
-
-    int id5 = setCorner(id4 , CORNER_X, 0);
-    int id6 = setCorner(id5 , CORNER_Y, 1);
-    int id7 = setCorner(id6 , CORNER_X, 2);
-    int id8 = setCorner(id7 , CORNER_Z, 3);
-    int id9 = setCorner(id8 , CORNER_Y, 4);
-    int id10= setCorner(id9 , CORNER_Y, 5);
-    int id11= setCorner(id10, CORNER_S, 6);
-    int id12= setCorner(id11, CORNER_Z, 7);
-
-    ScrambleStateBandagedEvil bsg = new ScrambleStateBandagedEvil(id12);
-    graph = new ArrayList<>();
-    graph.add(bsg);
-
-    insertChildren(graph,id12);
-    pruneGraph(graph);
-    remapGraph(graph);
-
-    int num = graph.size();
-    android.util.Log.e("D", "\n"+num+" states\n");
-
-    for(int i=0; i<num; i++)
-      {
-      bsg = graph.get(i);
-      android.util.Log.e("D", formatMoves(bsg));
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void insertChildren(ArrayList<ScrambleStateBandagedEvil> list, int id)
-    {
-    ScrambleStateBandagedEvil bsg = findState(list,id);
-
-    if( bsg==null )
-      {
-      android.util.Log.e("D", "error: "+id+" doesn't exist");
-      return;
-      }
-
-    for(int i=0; i<12; i++)
-      {
-      int move = bsg.getMove(i);
-
-      if( move!=INVALID_MOVE )
-        {
-        ScrambleStateBandagedEvil tmp = findState(list,move);
-
-        if( tmp==null )
-          {
-          tmp = new ScrambleStateBandagedEvil(move);
-          list.add(tmp);
-          insertChildren(list,move);
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void pruneGraph(ArrayList<ScrambleStateBandagedEvil> list)
-    {
-    int num = list.size(), numAxis;
-    boolean pruned = false;
-    ScrambleStateBandagedEvil bsg;
-
-    for(int i=0; i<num; i++)
-      {
-      bsg = list.get(i);
-      numAxis = bsg.numAxis();
-
-      if( numAxis<2 )
-        {
-        list.remove(i);
-        int id = bsg.getID();
-        pruned = true;
-        remapID(list,id,INVALID_MOVE);
-        break;
-        }
-      }
-
-    if( pruned ) pruneGraph(list);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void remapGraph(ArrayList<ScrambleStateBandagedEvil> list)
-    {
-    int id, num = list.size();
-    ScrambleStateBandagedEvil bsg;
-
-    for(int i=0; i<num; i++ )
-      {
-      bsg = list.get(i);
-      id = bsg.getID();
-      bsg.setID(i);
-      remapID(list,id,i);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void remapID(ArrayList<ScrambleStateBandagedEvil> list, int id, int newId)
-    {
-    ScrambleStateBandagedEvil bsg;
-    int size = list.size();
-
-    for(int i=0; i<size; i++)
-      {
-      bsg = list.get(i);
-
-      for(int j=0; j<12; j++)
-        {
-        if( bsg.getMove(j)==id ) bsg.setMove(j,newId);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static ScrambleStateBandagedEvil findState(ArrayList<ScrambleStateBandagedEvil> list, int id)
-    {
-    ScrambleStateBandagedEvil bsg;
-    int num = list.size();
-
-    for(int i=0; i<num; i++)
-      {
-      bsg= list.get(i);
-      if( bsg.getID() == id ) return bsg;
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static String formatMoves(ScrambleStateBandagedEvil bsg)
-    {
-    String x = getTable(bsg,0);
-    String y = getTable(bsg,3);
-    String z = getTable(bsg,6);
-
-    return "    new ScrambleStateGraph( new int[][] { "+x+", "+y+", "+z+" } ),";
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static String getTable(ScrambleStateBandagedEvil sc, int index)
-    {
-    String ret = "";
-
-    if( index==0 || index==3 )
-      {
-      int m0 = sc.getMove(index  );
-      int m1 = sc.getMove(index+1);
-      int m2 = sc.getMove(index+2);
-
-      if( m0==INVALID_MOVE && m1==INVALID_MOVE && m2==INVALID_MOVE ) return formatL("{}");
-
-      if( m0!=INVALID_MOVE ) ret += formatRet(ret,2, 1,m0);
-      if( m1!=INVALID_MOVE ) ret += formatRet(ret,2, 2,m1);
-      if( m2!=INVALID_MOVE ) ret += formatRet(ret,2,-1,m2);
-      }
-    else
-      {
-      int m0 = sc.getMove(index  );
-      int m1 = sc.getMove(index+1);
-      int m2 = sc.getMove(index+2);
-      int m3 = sc.getMove(index+3);
-      int m4 = sc.getMove(index+4);
-      int m5 = sc.getMove(index+5);
-
-      if( m0==INVALID_MOVE && m1==INVALID_MOVE && m2==INVALID_MOVE &&
-          m3==INVALID_MOVE && m4==INVALID_MOVE && m5==INVALID_MOVE  )
-        {
-        return formatL("{}");
-        }
-
-      if( m0!=INVALID_MOVE ) ret += formatRet(ret,0, 1,m0);
-      if( m1!=INVALID_MOVE ) ret += formatRet(ret,0, 2,m1);
-      if( m2!=INVALID_MOVE ) ret += formatRet(ret,0,-1,m2);
-      if( m3!=INVALID_MOVE ) ret += formatRet(ret,2, 1,m3);
-      if( m4!=INVALID_MOVE ) ret += formatRet(ret,2, 2,m4);
-      if( m5!=INVALID_MOVE ) ret += formatRet(ret,2,-1,m5);
-      }
-
-    return formatL("{" + ret + "}");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static String formatRet(String str, int row, int angle, int id)
-    {
-    String ret = str.length()!=0 ? ",":"";
-
-    ret += row;
-    ret += angle<0 ? "," : ", ";
-    ret += angle;
-
-         if( id< 10 ) ret += (",  "+id);
-    else if( id<100 ) ret += (", " +id);
-    else              ret += (","  +id);
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static final int LENGTH = 28;
-
-  private static String formatL(String input)
-    {
-    int len = input.length();
-    String ret = input;
-    for(int i=0 ;i<LENGTH-len; i++) ret += " ";
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getID()
-    {
-    return mID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setID(int id)
-    {
-    mID = id;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getMove(int index)
-    {
-    return (index>=0 && index<12) ? mMoves[index] : -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int numAxis()
-    {
-    int num = 0;
-
-    if( mMoves[ 0]!=INVALID_MOVE || mMoves[ 1]!=INVALID_MOVE || mMoves[ 2]!=INVALID_MOVE ) num++;
-    if( mMoves[ 3]!=INVALID_MOVE || mMoves[ 4]!=INVALID_MOVE || mMoves[ 5]!=INVALID_MOVE ) num++;
-    if( mMoves[ 6]!=INVALID_MOVE || mMoves[ 7]!=INVALID_MOVE || mMoves[ 8]!=INVALID_MOVE ) num++;
-    if( mMoves[ 9]!=INVALID_MOVE || mMoves[10]!=INVALID_MOVE || mMoves[11]!=INVALID_MOVE ) num++;
-
-    return num;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setMove(int index, int newMove)
-    {
-    if( index>=0 && index<12 ) mMoves[index] = newMove;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static String debug(int id)
-    {
-    int ce0 = getCenter(id,0);
-    int ce1 = getCenter(id,1);
-    int ce2 = getCenter(id,2);
-    int ce3 = getCenter(id,3);
-
-    int co0 = getCorner(id,0);
-    int co1 = getCorner(id,1);
-    int co2 = getCorner(id,2);
-    int co3 = getCorner(id,3);
-    int co4 = getCorner(id,4);
-    int co5 = getCorner(id,5);
-    int co6 = getCorner(id,6);
-    int co7 = getCorner(id,7);
-
-    String center = centerString(ce0) + centerString(ce1) + centerString(ce2) + centerString(ce3);
-    String corner = cornerString(co0) + cornerString(co1) + cornerString(co2) + cornerString(co3) +
-                    cornerString(co4) + cornerString(co5) + cornerString(co6) + cornerString(co7);
-
-    return center + " -" + corner;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static String centerString(int center)
-    {
-    return " "+center;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static String cornerString(int corner)
-    {
-    switch(corner)
-      {
-      case CORNER_S: return " S";
-      case CORNER_X: return " X";
-      case CORNER_Y: return " Y";
-      case CORNER_Z: return " Z";
-      }
-
-    return "?";
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int[] createMoves(int id)
-    {
-    int[] ret = new int[12];
-
-    boolean moveX  = xPossible(id);
-    boolean moveY  = yPossible(id);
-    boolean moveZ0 = z0Possible(id);
-    boolean moveZ2 = z2Possible(id);
-
-    if( moveX ) createXmoves(id,ret);
-    else        { ret[ 0] = INVALID_MOVE; ret[ 1] = INVALID_MOVE; ret[ 2] = INVALID_MOVE; }
-    if( moveY ) createYmoves(id,ret);
-    else        { ret[ 3] = INVALID_MOVE; ret[ 4] = INVALID_MOVE; ret[ 5] = INVALID_MOVE; }
-    if( moveZ0) createZ0moves(id,ret);
-    else        { ret[ 6] = INVALID_MOVE; ret[ 7] = INVALID_MOVE; ret[ 8] = INVALID_MOVE; }
-    if( moveZ2) createZ2moves(id,ret);
-    else        { ret[ 9] = INVALID_MOVE; ret[10] = INVALID_MOVE; ret[11] = INVALID_MOVE; }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static boolean xPossible(int id)
-    {
-    if( getCorner(id,4)==CORNER_X ) return false;
-    if( getCorner(id,5)==CORNER_X ) return false;
-    if( getCorner(id,6)==CORNER_X ) return false;
-    if( getCorner(id,7)==CORNER_X ) return false;
-
-    if( getCenter(id,1)==CENTER_1 ) return false;
-    if( getCenter(id,2)==CENTER_1 ) return false;
-    if( getCenter(id,3)==CENTER_1 ) return false;
-
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static boolean yPossible(int id)
-    {
-    if( getCorner(id,2)==CORNER_Y ) return false;
-    if( getCorner(id,3)==CORNER_Y ) return false;
-    if( getCorner(id,6)==CORNER_Y ) return false;
-    if( getCorner(id,7)==CORNER_Y ) return false;
-
-    if( getCenter(id,0)==CENTER_0 ) return false;
-    if( getCenter(id,2)==CENTER_0 ) return false;
-    if( getCenter(id,3)==CENTER_0 ) return false;
-
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static boolean z0Possible(int id)
-    {
-    if( getCorner(id,0)==CORNER_Z ) return false;
-    if( getCorner(id,2)==CORNER_Z ) return false;
-    if( getCorner(id,4)==CORNER_Z ) return false;
-    if( getCorner(id,6)==CORNER_Z ) return false;
-
-    if( getCenter(id,0)==CENTER_1 ) return false;
-    if( getCenter(id,1)==CENTER_0 ) return false;
-
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static boolean z2Possible(int id)
-    {
-    if( getCorner(id,1)==CORNER_Z ) return false;
-    if( getCorner(id,3)==CORNER_Z ) return false;
-    if( getCorner(id,5)==CORNER_Z ) return false;
-    if( getCorner(id,7)==CORNER_Z ) return false;
-
-    if( getCenter(id,0)==CENTER_3 ) return false;
-    if( getCenter(id,1)==CENTER_2 ) return false;
-
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int getCorner(int id, int index)
-    {
-    return (id>>(14-2*index))&3;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int getCenter(int id, int index)
-    {
-    return (id>>(22-2*index))&3;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int setCorner(int id, int corner, int index)
-    {
-    return id + ((corner-getCorner(id,index))<<(14-2*index));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int setCenter(int id, int center, int index)
-    {
-    return id + ((center-getCenter(id,index))<<(22-2*index));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void createXmoves(int id, int[] moves)
-    {
-    int id1 = rotateX(id);
-    moves[0] = id1;
-    int id2 = rotateX(id1);
-    moves[1] = id2;
-    int id3 = rotateX(id2);
-    moves[2] = id3;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void createYmoves(int id, int[] moves)
-    {
-    int id1 = rotateY(id);
-    moves[3] = id1;
-    int id2 = rotateY(id1);
-    moves[4] = id2;
-    int id3 = rotateY(id2);
-    moves[5] = id3;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void createZ0moves(int id, int[] moves)
-    {
-    int id1 = rotateZ0(id);
-    moves[6] = id1;
-    int id2 = rotateZ0(id1);
-    moves[7] = id2;
-    int id3 = rotateZ0(id2);
-    moves[8] = id3;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void createZ2moves(int id, int[] moves)
-    {
-    int id1 = rotateZ2(id);
-    moves[ 9] = id1;
-    int id2 = rotateZ2(id1);
-    moves[10] = id2;
-    int id3 = rotateZ2(id2);
-    moves[11] = id3;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int rotateX(int id)
-    {
-    int newCorner4 = rotCornerX(getCorner(id,5));
-    int newCorner5 = rotCornerX(getCorner(id,7));
-    int newCorner6 = rotCornerX(getCorner(id,4));
-    int newCorner7 = rotCornerX(getCorner(id,6));
-    int newCenter  = rotCenter (getCenter(id,0));
-
-    int id1 = setCorner(id ,newCorner4,4);
-    int id2 = setCorner(id1,newCorner5,5);
-    int id3 = setCorner(id2,newCorner6,6);
-    int id4 = setCorner(id3,newCorner7,7);
-    int id5 = setCenter(id4,newCenter ,0);
-
-    return id5;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int rotateY(int id)
-    {
-    int newCorner2 = rotCornerY(getCorner(id,6));
-    int newCorner3 = rotCornerY(getCorner(id,2));
-    int newCorner6 = rotCornerY(getCorner(id,7));
-    int newCorner7 = rotCornerY(getCorner(id,3));
-    int newCenter  = rotCenter (getCenter(id,1));
-
-    int id1 = setCorner(id ,newCorner2,2);
-    int id2 = setCorner(id1,newCorner3,3);
-    int id3 = setCorner(id2,newCorner6,6);
-    int id4 = setCorner(id3,newCorner7,7);
-    int id5 = setCenter(id4,newCenter ,1);
-
-    return id5;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int rotateZ0(int id)
-    {
-    int newCorner0 = rotCornerZ(getCorner(id,2));
-    int newCorner2 = rotCornerZ(getCorner(id,6));
-    int newCorner4 = rotCornerZ(getCorner(id,0));
-    int newCorner6 = rotCornerZ(getCorner(id,4));
-    int newCenter  = rotCenter (getCenter(id,2));
-
-    int id1 = setCorner(id ,newCorner0,0);
-    int id2 = setCorner(id1,newCorner2,2);
-    int id3 = setCorner(id2,newCorner4,4);
-    int id4 = setCorner(id3,newCorner6,6);
-    int id5 = setCenter(id4,newCenter ,2);
-
-    return id5;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int rotateZ2(int id)
-    {
-    int newCorner1 = rotCornerZ(getCorner(id,3));
-    int newCorner3 = rotCornerZ(getCorner(id,7));
-    int newCorner5 = rotCornerZ(getCorner(id,1));
-    int newCorner7 = rotCornerZ(getCorner(id,5));
-    int newCenter  = rotCenter (getCenter(id,3));
-
-    int id1 = setCorner(id ,newCorner1,1);
-    int id2 = setCorner(id1,newCorner3,3);
-    int id3 = setCorner(id2,newCorner5,5);
-    int id4 = setCorner(id3,newCorner7,7);
-    int id5 = setCenter(id4,newCenter ,3);
-
-    return id5;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int rotCornerX(int corner)
-    {
-    switch(corner)
-      {
-      case CORNER_S: return CORNER_S;
-      case CORNER_X: android.util.Log.e("DIST", "rotateX: ERROR");
-                     return CORNER_S;
-      case CORNER_Y: return CORNER_Z;
-      case CORNER_Z: return CORNER_Y;
-      }
-
-    return CORNER_S;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int rotCornerY(int corner)
-    {
-    switch(corner)
-      {
-      case CORNER_S: return CORNER_S;
-      case CORNER_X: return CORNER_Z;
-      case CORNER_Y: android.util.Log.e("DIST", "rotateY: ERROR");
-                     return CORNER_S;
-      case CORNER_Z: return CORNER_X;
-      }
-
-    return CORNER_S;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int rotCornerZ(int corner)
-    {
-    switch(corner)
-      {
-      case CORNER_S: return CORNER_S;
-      case CORNER_X: return CORNER_Y;
-      case CORNER_Y: return CORNER_X;
-      case CORNER_Z: android.util.Log.e("DIST", "rotateZ: ERROR");
-                     return CORNER_S;
-      }
-
-    return CORNER_S;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int rotCenter(int center)
-    {
-    switch(center)
-      {
-      case CENTER_0: return CENTER_3;
-      case CENTER_1: return CENTER_0;
-      case CENTER_2: return CENTER_1;
-      case CENTER_3: return CENTER_2;
-      }
-
-    return CENTER_0;
-    }
-}
diff --git a/src/main/java/org/distorted/objectlib/main/ScrambleStateSquare1.java b/src/main/java/org/distorted/objectlib/main/ScrambleStateSquare1.java
deleted file mode 100644
index 9c8cc91a..00000000
--- a/src/main/java/org/distorted/objectlib/main/ScrambleStateSquare1.java
+++ /dev/null
@@ -1,593 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2021 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.main;
-
-import java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// producer of the ScrambleStateGraph - but only for a single Twisty Puzzle, the 'Square-1'.
-
-public class ScrambleStateSquare1
-{
-  private static final int INVALID_MOVE = -1;
-  private static final int NUM_MOVES = 23;
-
-  private int mDist;
-  private boolean mFresh;
-  private long mID;
-  private final long[] mMoves;
-
-  private final int[][] mPermittedAngles;
-  private final int[] mCornerQuat, mTmp;
-
-  // QUATS[i]*QUATS[j] = QUATS[QUAT_MULT[i][j]]
-  static final int[][] QUAT_MULT = new int[][]
-    {
-      {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,},
-      {  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,  0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 12,},
-      {  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,  0,  1, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 12, 13,},
-      {  3,  4,  5,  6,  7,  8,  9, 10, 11,  0,  1,  2, 15, 16, 17, 18, 19, 20, 21, 22, 23, 12, 13, 14,},
-      {  4,  5,  6,  7,  8,  9, 10, 11,  0,  1,  2,  3, 16, 17, 18, 19, 20, 21, 22, 23, 12, 13, 14, 15,},
-      {  5,  6,  7,  8,  9, 10, 11,  0,  1,  2,  3,  4, 17, 18, 19, 20, 21, 22, 23, 12, 13, 14, 15, 16,},
-      {  6,  7,  8,  9, 10, 11,  0,  1,  2,  3,  4,  5, 18, 19, 20, 21, 22, 23, 12, 13, 14, 15, 16, 17,},
-      {  7,  8,  9, 10, 11,  0,  1,  2,  3,  4,  5,  6, 19, 20, 21, 22, 23, 12, 13, 14, 15, 16, 17, 18,},
-      {  8,  9, 10, 11,  0,  1,  2,  3,  4,  5,  6,  7, 20, 21, 22, 23, 12, 13, 14, 15, 16, 17, 18, 19,},
-      {  9, 10, 11,  0,  1,  2,  3,  4,  5,  6,  7,  8, 21, 22, 23, 12, 13, 14, 15, 16, 17, 18, 19, 20,},
-      { 10, 11,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 22, 23, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,},
-      { 11,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 23, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,},
-      { 12, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13,  0, 11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,},
-      { 13, 12, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14,  1,  0, 11, 10,  9,  8,  7,  6,  5,  4,  3,  2,},
-      { 14, 13, 12, 23, 22, 21, 20, 19, 18, 17, 16, 15,  2,  1,  0, 11, 10,  9,  8,  7,  6,  5,  4,  3,},
-      { 15, 14, 13, 12, 23, 22, 21, 20, 19, 18, 17, 16,  3,  2,  1,  0, 11, 10,  9,  8,  7,  6,  5,  4,},
-      { 16, 15, 14, 13, 12, 23, 22, 21, 20, 19, 18, 17,  4,  3,  2,  1,  0, 11, 10,  9,  8,  7,  6,  5,},
-      { 17, 16, 15, 14, 13, 12, 23, 22, 21, 20, 19, 18,  5,  4,  3,  2,  1,  0, 11, 10,  9,  8,  7,  6,},
-      { 18, 17, 16, 15, 14, 13, 12, 23, 22, 21, 20, 19,  6,  5,  4,  3,  2,  1,  0, 11, 10,  9,  8,  7,},
-      { 19, 18, 17, 16, 15, 14, 13, 12, 23, 22, 21, 20,  7,  6,  5,  4,  3,  2,  1,  0, 11, 10,  9,  8,},
-      { 20, 19, 18, 17, 16, 15, 14, 13, 12, 23, 22, 21,  8,  7,  6,  5,  4,  3,  2,  1,  0, 11, 10,  9,},
-      { 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 23, 22,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0, 11, 10,},
-      { 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 23, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0, 11,},
-      { 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0,}
-    };
-
-  // quat indices that make corner cubits bandage the puzzle.
-  private static final int[][] BAD_CORNER_QUATS = new int[][]
-    {
-      { 2, 8,17,23},
-      { 5,11,14,20},
-    };
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ScrambleStateSquare1(long id, int dist)
-    {
-    mCornerQuat = new int[8];
-    mTmp        = new int[8];
-    mPermittedAngles = new int[2][12];
-
-    mDist = dist;
-    mFresh = true;
-    mID = id;
-    mMoves = createMoves(mID);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setNotFresh()
-    {
-    mFresh = false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean isFresh()
-    {
-    return mFresh;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void computeGraph()
-    {
-    ArrayList<ScrambleStateSquare1> graph;
-
-    ScrambleStateSquare1 bsg = new ScrambleStateSquare1(0,0);
-    graph = new ArrayList<>();
-    graph.add(bsg);
-
-android.util.Log.e("D", "INSERTING");
-
-    insertChildren(graph,0);
-
-//android.util.Log.e("D", "PRUNING "+graph.size());
-
-//    pruneGraph(graph);
-
-android.util.Log.e("D", "REMAPPING "+graph.size());
-
-    remapGraph(graph);
-
-    int num = graph.size();
-    android.util.Log.e("D", "\n"+num+" states\n");
-
-    for(int i=0; i<num; i++)
-      {
-      bsg = graph.get(i);
-      android.util.Log.e("D", formatMoves(bsg));
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void insertChildren(ArrayList<ScrambleStateSquare1> list, long id)
-    {
-    ScrambleStateSquare1 bsg = findState(list,id);
-
-    if( bsg==null )
-      {
-      android.util.Log.e("D", "error: "+id+" doesn't exist");
-      return;
-      }
-
-    int dist = bsg.mDist;
-    if( dist>6 ) return;
-
-    for(int i=0; i<NUM_MOVES; i++)
-      {
-      long moveID = bsg.getMoveID(i);
-
-      if( moveID!=INVALID_MOVE )
-        {
-        ScrambleStateSquare1 tmp = findState(list,moveID);
-
-        if( tmp==null )
-          {
-          tmp = new ScrambleStateSquare1(moveID,dist+1);
-          list.add(tmp);
-          }
-        }
-      }
-
-    for(int i=0; i<NUM_MOVES; i++)
-      {
-      long moveID = bsg.getMoveID(i);
-
-      if( moveID!=INVALID_MOVE )
-        {
-        ScrambleStateSquare1 tmp = findState(list,moveID);
-
-        if( tmp.isFresh() )
-          {
-          tmp.setNotFresh();
-          insertChildren(list,moveID);
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void pruneGraph(ArrayList<ScrambleStateSquare1> list)
-    {
-    int num = list.size(), numAxis;
-    boolean pruned = false;
-    ScrambleStateSquare1 bsg;
-
-    for(int i=0; i<num; i++)
-      {
-      bsg = list.get(i);
-      numAxis = bsg.numAxis();
-
-      if( numAxis<2 )
-        {
-        list.remove(i);
-        long id = bsg.getID();
-        pruned = true;
-        remapID(list,id,INVALID_MOVE);
-        break;
-        }
-      }
-
-    if( pruned ) pruneGraph(list);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void remapGraph(ArrayList<ScrambleStateSquare1> list)
-    {
-    long id;
-    int num = list.size();
-    ScrambleStateSquare1 bsg;
-
-    for(int i=0; i<num; i++ )
-      {
-      bsg = list.get(i);
-      id = bsg.getID();
-      bsg.setID(i);
-      remapID(list,id,i);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void remapID(ArrayList<ScrambleStateSquare1> list, long id, long newId)
-    {
-    ScrambleStateSquare1 bsg;
-    int size = list.size();
-
-    for(int i=0; i<size; i++)
-      {
-      bsg = list.get(i);
-
-      for(int j=0; j<NUM_MOVES; j++)
-        {
-        if( bsg.getMoveID(j)==id ) bsg.setMoveID(j,newId);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static ScrambleStateSquare1 findState(ArrayList<ScrambleStateSquare1> list, long id)
-    {
-    ScrambleStateSquare1 bsg;
-    int num = list.size();
-
-    for(int i=0; i<num; i++)
-      {
-      bsg= list.get(i);
-      if( bsg.getID() == id ) return bsg;
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static String formatMoves(ScrambleStateSquare1 bsg)
-    {
-    String x = getTable(bsg,0);
-    String y = getTable(bsg,11);
-    String z = getTable(bsg,12);
-
-    //return "    new ScrambleStateGraph( new int[][] { "+x+", "+y+", "+z+" } ),";
-
-    long id = bsg.getID();
-
-    return id+" "+x+" , "+y+" , "+z;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static String getTable(ScrambleStateSquare1 sc, int index)
-    {
-    String ret = "";
-    long m;
-    int half = (NUM_MOVES)/2;
-
-    if( index==11 )
-      {
-      m = sc.getMoveID(index);
-      if( m==INVALID_MOVE ) return formatL("{}");
-      ret += formatRet(ret,0,1,m);
-      }
-    else
-      {
-      for(int i=0; i<half; i++)
-        {
-        m = sc.getMoveID(index+i);
-        int row = index==0 ? 0:2;
-        int angle = i+1;
-        if( m!=INVALID_MOVE ) ret += formatRet(ret,row,angle,m);
-        }
-      }
-
-    return formatL("{" + ret + "}");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static String formatRet(String str, int row, int angle, long id)
-    {
-    String ret = str.length()!=0 ? "  ,":"  ";
-
-    ret += row;
-    ret += angle<0 ? "," : ",";
-    ret += angle;
-
-         if( id< 10 ) ret += (",  "+id);
-    else if( id<100 ) ret += (", " +id);
-    else              ret += (","  +id);
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static final int LENGTH = 28;
-
-  private static String formatL(String input)
-    {
-    int len = input.length();
-    String ret = input;
-    for(int i=0 ;i<LENGTH-len; i++) ret += " ";
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private long getID()
-    {
-    return mID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setID(long id)
-    {
-    mID = id;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private long getMoveID(int index)
-    {
-    return (index>=0 && index<NUM_MOVES) ? mMoves[index] : -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setMoveID(int index, long newID)
-    {
-    if( index>=0 && index<NUM_MOVES ) mMoves[index] = newID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int numAxis()
-    {
-    int num = 0;
-    int half = (NUM_MOVES)/2;
-
-    for(int i=0; i<half; i++)
-      {
-      if( mMoves[i]!=INVALID_MOVE )
-        {
-        num++;
-        break;
-        }
-      }
-
-    for(int i=half; i<NUM_MOVES-1; i++)
-      {
-      if( mMoves[i]!=INVALID_MOVE )
-        {
-        num++;
-        break;
-        }
-      }
-
-    if( mMoves[NUM_MOVES-1]!=INVALID_MOVE ) num++;
-
-    return num;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private long[] createMoves(long id)
-    {
-    long[] ret = new long[NUM_MOVES];
-    fillCornerQuat(id);
-    computePermittedAngles();
-
-    generateUMoves(ret, 0);
-    generateSMoves(ret,11);
-    generateLMoves(ret,12);
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void generateUMoves(long[] table, int index)
-    {
-    for(int angle=1; angle<12; angle++)
-      {
-      table[index+angle-1] = mPermittedAngles[1][angle]==1 ? updateCornerQuats(0,2,angle) : INVALID_MOVE;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void generateLMoves(long[] table, int index)
-    {
-    for(int angle=1; angle<12; angle++)
-      {
-      table[index+angle-1] = mPermittedAngles[0][angle]==1 ? updateCornerQuats(0,0,angle) : INVALID_MOVE;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void generateSMoves(long[] table, int index)
-    {
-    table[index] = updateCornerQuats(1,1,1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void fillCornerQuat(long id)
-    {
-    int NUM_QUATS = 24;
-
-    for(int i=0; i<8; i++)
-      {
-      mCornerQuat[i] = (int)(id%NUM_QUATS);
-      id/=NUM_QUATS;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static long returnID(int[] cornerQuat)
-    {
-    int NUM_QUATS = 24;
-    long mult=1,ret=0;
-
-    for(int i=0; i<8; i++)
-      {
-      ret += mult*cornerQuat[i];
-      mult*=NUM_QUATS;
-      }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean cornerIsUp(int index)
-    {
-    return ((index<4) ^ (mCornerQuat[index]>=12));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean cornerIsLeft(int index)
-    {
-    int q = mCornerQuat[index];
-
-    switch(index)
-      {
-      case 0:
-      case 4: return ((q>=3 && q<= 7) || (q>=18 && q<=22));
-      case 1:
-      case 5: return ((q>=6 && q<=10) || (q>=15 && q<=19));
-      case 2:
-      case 6: return ((q==0 || q==1 || (q>=9 && q<=11)) || (q>=12 && q<=16));
-      case 3:
-      case 7: return ((q>=0 && q<=4) || (q==12 || q==13 || (q>=21 && q<=23)));
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int makeQuat(int axis,int index)
-    {
-    if( axis==1 ) return 13;
-    if( index<0 ) index+=12;
-    return index;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean cornerBelongs(int index, int axis, int layer)
-    {
-    if( axis==0 )
-      {
-      boolean up = cornerIsUp(index);
-      return ((up && layer==2) || (!up && layer==0));
-      }
-    else
-      {
-      boolean le = cornerIsLeft(index);
-      return ((le && layer==0) || (!le && layer==1));
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private long updateCornerQuats(int axis, int layer, int index)
-    {
-    int quat = makeQuat(axis,index);
-
-    for(int corner=0; corner<8; corner++)
-      {
-      if( cornerBelongs(corner,axis,layer) )
-        {
-        int curr = mCornerQuat[corner];
-        mTmp[corner] = QUAT_MULT[quat][curr];
-        }
-      else
-        {
-        mTmp[corner] = mCornerQuat[corner];
-        }
-      }
-
-    return returnID(mTmp);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean quatIsBad(int quatIndex, int corner)
-    {
-    int index = (corner%2);
-
-    return ( quatIndex==BAD_CORNER_QUATS[index][0] ||
-             quatIndex==BAD_CORNER_QUATS[index][1] ||
-             quatIndex==BAD_CORNER_QUATS[index][2] ||
-             quatIndex==BAD_CORNER_QUATS[index][3]  );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int isPermittedDo(int angle)
-    {
-    for(int corner=0; corner<8; corner++)
-      {
-      if( !cornerIsUp(corner) )
-        {
-        int currQuat = mCornerQuat[corner];
-        int finalQuat= QUAT_MULT[angle][currQuat];
-        if( quatIsBad(finalQuat,corner) ) return 0;
-        }
-      }
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int isPermittedUp(int angle)
-    {
-    for(int corner=0; corner<8; corner++)
-      {
-      if( cornerIsUp(corner) )
-        {
-        int currQuat = mCornerQuat[corner];
-        int finalQuat= QUAT_MULT[angle][currQuat];
-        if( quatIsBad(finalQuat,corner) ) return 0;
-        }
-      }
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computePermittedAngles()
-    {
-    for(int angle=0; angle<12; angle++)
-      {
-      mPermittedAngles[0][angle] = isPermittedDo(angle);
-      mPermittedAngles[1][angle] = isPermittedUp(angle);
-      }
-    }
-}
diff --git a/src/main/java/org/distorted/objectlib/main/TwistyObject.java b/src/main/java/org/distorted/objectlib/main/TwistyObject.java
index 8e433895..3fefb90d 100644
--- a/src/main/java/org/distorted/objectlib/main/TwistyObject.java
+++ b/src/main/java/org/distorted/objectlib/main/TwistyObject.java
@@ -48,6 +48,11 @@ import org.distorted.library.type.Static1D;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 import org.distorted.objectlib.BuildConfig;
+import org.distorted.objectlib.helpers.FactoryCubit;
+import org.distorted.objectlib.helpers.FactorySticker;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 
 import java.io.DataInputStream;
 import java.io.IOException;
@@ -838,44 +843,6 @@ public abstract class TwistyObject extends DistortedNode
     return -1;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// the getFaceColors + final black in a grid (so that we do not exceed the maximum texture size)
-
-  private void createTexture()
-    {
-    Bitmap bitmap;
-
-    Paint paint = new Paint();
-    bitmap = Bitmap.createBitmap( mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, Bitmap.Config.ARGB_8888);
-    Canvas canvas = new Canvas(bitmap);
-
-    paint.setAntiAlias(true);
-    paint.setTextAlign(Paint.Align.CENTER);
-    paint.setStyle(Paint.Style.FILL);
-
-    paint.setColor(COLOR_BLACK);
-    canvas.drawRect(0, 0, mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, paint);
-
-    int face = 0;
-    FactorySticker factory = FactorySticker.getInstance();
-
-    for(int row=0; row<mNumTexRows; row++)
-      for(int col=0; col<mNumTexCols; col++)
-        {
-        if( face>=NUM_TEXTURES ) break;
-        ObjectSticker sticker = retSticker(face);
-        factory.drawRoundedPolygon(canvas, paint, col*TEXTURE_HEIGHT, row*TEXTURE_HEIGHT, getColor(face%NUM_FACE_COLORS), sticker);
-        face++;
-        }
-
-    if( !mTexture.setTexture(bitmap) )
-      {
-      int max = DistortedLibrary.getMaxTextureSize();
-      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-      crashlytics.log("failed to set texture of size "+bitmap.getWidth()+"x"+bitmap.getHeight()+" max is "+max);
-      }
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   private float getAngle()
@@ -937,6 +904,44 @@ public abstract class TwistyObject extends DistortedNode
     return false;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// the getFaceColors + final black in a grid (so that we do not exceed the maximum texture size)
+
+  public void createTexture()
+    {
+    Bitmap bitmap;
+
+    Paint paint = new Paint();
+    bitmap = Bitmap.createBitmap( mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, Bitmap.Config.ARGB_8888);
+    Canvas canvas = new Canvas(bitmap);
+
+    paint.setAntiAlias(true);
+    paint.setTextAlign(Paint.Align.CENTER);
+    paint.setStyle(Paint.Style.FILL);
+
+    paint.setColor(COLOR_BLACK);
+    canvas.drawRect(0, 0, mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, paint);
+
+    int face = 0;
+    FactorySticker factory = FactorySticker.getInstance();
+
+    for(int row=0; row<mNumTexRows; row++)
+      for(int col=0; col<mNumTexCols; col++)
+        {
+        if( face>=NUM_TEXTURES ) break;
+        ObjectSticker sticker = retSticker(face);
+        factory.drawRoundedPolygon(canvas, paint, col*TEXTURE_HEIGHT, row*TEXTURE_HEIGHT, getColor(face%NUM_FACE_COLORS), sticker);
+        face++;
+        }
+
+    if( !mTexture.setTexture(bitmap) )
+      {
+      int max = DistortedLibrary.getMaxTextureSize();
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.log("failed to set texture of size "+bitmap.getWidth()+"x"+bitmap.getHeight()+" max is "+max);
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void setObjectRatio(float sizeChange)
diff --git a/src/main/java/org/distorted/objectlib/main/TwistyObjectScrambler.java b/src/main/java/org/distorted/objectlib/main/TwistyObjectScrambler.java
index ca22c4f0..752baf27 100644
--- a/src/main/java/org/distorted/objectlib/main/TwistyObjectScrambler.java
+++ b/src/main/java/org/distorted/objectlib/main/TwistyObjectScrambler.java
@@ -19,6 +19,8 @@
 
 package org.distorted.objectlib.main;
 
+import org.distorted.objectlib.helpers.ScrambleState;
+
 import java.util.Random;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyBandaged2Bar.java b/src/main/java/org/distorted/objectlib/objects/TwistyBandaged2Bar.java
index 003e41b4..3c8503e1 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyBandaged2Bar.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyBandaged2Bar.java
@@ -28,7 +28,7 @@ import org.distorted.library.type.Static4D;
 
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ScrambleState;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyBandaged3Plate.java b/src/main/java/org/distorted/objectlib/objects/TwistyBandaged3Plate.java
index 5ea51d7f..98bd7a49 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyBandaged3Plate.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyBandaged3Plate.java
@@ -28,7 +28,7 @@ import org.distorted.library.type.Static4D;
 
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ScrambleState;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyBandagedAbstract.java b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedAbstract.java
index bf0857e2..8a8074a3 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyBandagedAbstract.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedAbstract.java
@@ -31,9 +31,9 @@ import org.distorted.library.type.Static4D;
 
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement6;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty6;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyBandagedEvil.java b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedEvil.java
index 2368690e..676ae750 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyBandagedEvil.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedEvil.java
@@ -28,7 +28,7 @@ import org.distorted.library.type.Static4D;
 
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ScrambleState;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyBandagedFused.java b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedFused.java
index 6e5cbf1a..6d1a3b15 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyBandagedFused.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedFused.java
@@ -28,7 +28,7 @@ import org.distorted.library.type.Static4D;
 
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ScrambleState;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyCube.java b/src/main/java/org/distorted/objectlib/objects/TwistyCube.java
index 97808d60..7327acef 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyCube.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyCube.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement6;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty6;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyDiamond.java b/src/main/java/org/distorted/objectlib/objects/TwistyDiamond.java
index 0d564c23..40b720ac 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyDiamond.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyDiamond.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement8;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty8;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyDino.java b/src/main/java/org/distorted/objectlib/objects/TwistyDino.java
index d3fc6d25..c27c76ea 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyDino.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyDino.java
@@ -32,9 +32,9 @@ 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.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty6;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyDino4.java b/src/main/java/org/distorted/objectlib/objects/TwistyDino4.java
index 35997905..bc4e95c8 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyDino4.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyDino4.java
@@ -28,7 +28,7 @@ import org.distorted.library.type.Static4D;
 
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ScrambleState;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyDino6.java b/src/main/java/org/distorted/objectlib/objects/TwistyDino6.java
index 588acd41..4ca8fd50 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyDino6.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyDino6.java
@@ -28,7 +28,7 @@ import org.distorted.library.type.Static4D;
 
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ScrambleState;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyHelicopter.java b/src/main/java/org/distorted/objectlib/objects/TwistyHelicopter.java
index 4c57b33c..c2aaeb6d 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyHelicopter.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyHelicopter.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement6;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty6;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyIvy.java b/src/main/java/org/distorted/objectlib/objects/TwistyIvy.java
index f43a1ca6..8596aed5 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyIvy.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyIvy.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement6;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty6;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyJing.java b/src/main/java/org/distorted/objectlib/objects/TwistyJing.java
index 5397dae9..36cfab2e 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyJing.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyJing.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement4;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty4;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyKilominx.java b/src/main/java/org/distorted/objectlib/objects/TwistyKilominx.java
index 8a78fb3c..89d2303e 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyKilominx.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyKilominx.java
@@ -32,8 +32,8 @@ import org.distorted.library.main.QuatHelper;
 
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyMegaminx.java b/src/main/java/org/distorted/objectlib/objects/TwistyMegaminx.java
index cc7833dc..55655ecf 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyMegaminx.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyMegaminx.java
@@ -32,8 +32,8 @@ import org.distorted.library.main.QuatHelper;
 
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyMinx.java b/src/main/java/org/distorted/objectlib/objects/TwistyMinx.java
index 59062d36..7e444144 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyMinx.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyMinx.java
@@ -33,8 +33,8 @@ import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement12;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty12;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyMirror.java b/src/main/java/org/distorted/objectlib/objects/TwistyMirror.java
index 900b02f1..966f7475 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyMirror.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyMirror.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement6;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty6;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyPyraminx.java b/src/main/java/org/distorted/objectlib/objects/TwistyPyraminx.java
index fae4965b..9fe18253 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyPyraminx.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyPyraminx.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement4;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty4;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyRedi.java b/src/main/java/org/distorted/objectlib/objects/TwistyRedi.java
index 83e556be..710f5ea1 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyRedi.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyRedi.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement6;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty6;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyRex.java b/src/main/java/org/distorted/objectlib/objects/TwistyRex.java
index 1235a754..41bdf427 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyRex.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyRex.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement6;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty6;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistySkewb.java b/src/main/java/org/distorted/objectlib/objects/TwistySkewb.java
index f0400c6d..8142051e 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistySkewb.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistySkewb.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement6;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty6;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistySquare1.java b/src/main/java/org/distorted/objectlib/objects/TwistySquare1.java
index bd20c7d6..c6beb056 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistySquare1.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistySquare1.java
@@ -29,9 +29,9 @@ import org.distorted.library.type.Static4D;
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement6;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistySquare2.java b/src/main/java/org/distorted/objectlib/objects/TwistySquare2.java
index fbe4f7b8..691901b2 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistySquare2.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistySquare2.java
@@ -28,9 +28,9 @@ import org.distorted.library.type.Static4D;
 
 import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyUltimate.java b/src/main/java/org/distorted/objectlib/objects/TwistyUltimate.java
index 2f0a9d21..2e16332f 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyUltimate.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyUltimate.java
@@ -33,9 +33,9 @@ import org.distorted.objectlib.R;
 import org.distorted.objectlib.main.Movement;
 import org.distorted.objectlib.main.Movement12;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.ObjectShape;
-import org.distorted.objectlib.main.ObjectSticker;
-import org.distorted.objectlib.main.ScrambleState;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSticker;
+import org.distorted.objectlib.helpers.ScrambleState;
 import org.distorted.objectlib.main.Twisty12;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
