commit ef018c1b03a49455ab384054e8d2d3306d0555b0
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Sep 23 02:11:20 2021 +0200

    Abstract the next function, computeRowFromOffset(), out of the individual Movement classes.
    Still two issues:
    
    1) mysterious 1.5 multiplier in Movement12
    2) in Movement8, moving the offset works only if the rotAxis are face-turning, i.e. they connect the centers of the opposing faces of the octahedron.

diff --git a/src/main/java/org/distorted/objects/Movement.java b/src/main/java/org/distorted/objects/Movement.java
index 9b5232b5..da2561e3 100644
--- a/src/main/java/org/distorted/objects/Movement.java
+++ b/src/main/java/org/distorted/objects/Movement.java
@@ -247,6 +247,58 @@ public abstract class Movement
     output[1] = v0*y0 + v1*y1 + v2*y2;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[] computeBorder(float scale, float[] cuts, boolean[] rotatable)
+    {
+    int len = cuts.length;
+    float[] border = new float[len];
+
+    for(int i=0; i<len; i++)
+      {
+      if( !rotatable[i] )
+        {
+        border[i] = i>0 ? border[i-1] : -Float.MAX_VALUE;
+        }
+      else
+        {
+        if( rotatable[i+1] ) border[i] = scale*cuts[i];
+        else
+          {
+          int found = -1;
+
+          for(int j=i+2; j<=len; j++)
+            {
+            if( rotatable[j] )
+              {
+              found=j;
+              break;
+              }
+            }
+
+          border[i] = found>0 ? scale*(cuts[i]+cuts[found-1])/2 : Float.MAX_VALUE;
+          }
+        }
+      }
+
+    return border;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[][] computeBorders(float coeff, float[][] cuts, boolean[][] rotatable)
+    {
+    int numCuts = cuts.length;
+    float[][] borders = new float[numCuts][];
+
+    for(int i=0; i<numCuts; i++)
+      {
+      borders[i] = computeBorder(coeff,cuts[i],rotatable[i]);
+      }
+
+    return borders;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/Movement12.java b/src/main/java/org/distorted/objects/Movement12.java
index 6fc6c809..ce7b45c8 100644
--- a/src/main/java/org/distorted/objects/Movement12.java
+++ b/src/main/java/org/distorted/objects/Movement12.java
@@ -51,11 +51,30 @@ abstract class Movement12 extends Movement
            new Static3D(-SIN54/LEN,    0     ,   -C2/LEN )
          };
 
+  private final float[][] mTouchBorders;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  Movement12(Static3D[] rotAxis)
+  Movement12(Static3D[] rotAxis,float[][] cuts, boolean[][] rotatable, int numLayers)
     {
     super(rotAxis, FACE_AXIS, DIST3D, DIST2D);
+    float scale = (DIST2D*(1.5f)/(2*DIST3D))/numLayers; // SQ5/2 is 1/cos(dihedral-90)
+    mTouchBorders = computeBorders(scale,cuts,rotatable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
+    {
+    float[] borders = mTouchBorders[axisIndex];
+    int len = borders.length;
+
+    for(int i=0; i<len; i++)
+      {
+      if( offset<borders[i] ) return i;
+      }
+
+    return len;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/Movement4.java b/src/main/java/org/distorted/objects/Movement4.java
index 0c8f756c..135904f9 100644
--- a/src/main/java/org/distorted/objects/Movement4.java
+++ b/src/main/java/org/distorted/objects/Movement4.java
@@ -19,6 +19,8 @@
 
 package org.distorted.objects;
 
+import static org.distorted.objects.TwistyObject.SQ2;
+
 import org.distorted.library.type.Static3D;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -37,11 +39,30 @@ abstract class Movement4 extends Movement
            new Static3D(+SQ6/3,-SQ3/3,     0),
          };
 
+  private final float[][] mTouchBorders;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  Movement4(Static3D[] rotAxis)
+  Movement4(Static3D[] rotAxis,float[][] cuts, boolean[][] rotatable,int numLayers)
     {
     super(rotAxis, FACE_AXIS, DIST3D, DIST2D);
+    float scale = (3*SQ2/4)/numLayers; // 3*SQ2/4 is height(face)/height(tetra)
+    mTouchBorders = computeBorders(scale,cuts,rotatable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
+    {
+    float[] borders = mTouchBorders[axisIndex];
+    int len = borders.length;
+
+    for(int i=0; i<len; i++)
+      {
+      if( offset<borders[i] ) return i;
+      }
+
+    return len;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/Movement6.java b/src/main/java/org/distorted/objects/Movement6.java
index e96780c2..355f8fe5 100644
--- a/src/main/java/org/distorted/objects/Movement6.java
+++ b/src/main/java/org/distorted/objects/Movement6.java
@@ -19,6 +19,8 @@
 
 package org.distorted.objects;
 
+import static org.distorted.objects.TwistyObject.SQ2;
+
 import org.distorted.library.type.Static3D;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -36,11 +38,30 @@ abstract class Movement6 extends Movement
            new Static3D(0,0,1), new Static3D(0,0,-1)
          };
 
+  private final float[][] mTouchBorders;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  Movement6(Static3D[] rotAxis)
+  Movement6(Static3D[] rotAxis,float[][] cuts, boolean[][] rotatable, int numLayers)
     {
     super(rotAxis, FACE_AXIS, DIST3D, DIST2D);
+    float scale = 1.0f/numLayers; // 1.0 is (2*DIST3D)/(2*DIST2D)
+    mTouchBorders = computeBorders(scale,cuts,rotatable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
+    {
+    float[] borders = mTouchBorders[axisIndex];
+    int len = borders.length;
+
+    for(int i=0; i<len; i++)
+      {
+      if( offset<borders[i] ) return i;
+      }
+
+    return len;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/Movement8.java b/src/main/java/org/distorted/objects/Movement8.java
index 9eec9c77..7e6c17f7 100644
--- a/src/main/java/org/distorted/objects/Movement8.java
+++ b/src/main/java/org/distorted/objects/Movement8.java
@@ -19,6 +19,8 @@
 
 package org.distorted.objects;
 
+import static org.distorted.objects.TwistyObject.SQ2;
+
 import org.distorted.library.type.Static3D;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -37,11 +39,37 @@ abstract class Movement8 extends Movement
            new Static3D(     0,+SQ3/3,-SQ6/3), new Static3D(     0,-SQ3/3,+SQ6/3)
          };
 
+  private final float[][] mTouchBorders;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  Movement8(Static3D[] rotAxis)
+  Movement8(Static3D[] rotAxis,float[][] cuts, boolean[][] rotatable, int numLayers)
     {
     super(rotAxis, FACE_AXIS, DIST3D, DIST2D);
+    float scale = (3*SQ2/4)/numLayers; // 3*SQ2/4 is 1/cos(dihedral-90)
+    mTouchBorders = computeBorders(scale,cuts,rotatable);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// We have either one of the four faces (1,3,4,6) which, when the retAxis are cast onto it, they
+// point the right way (and so the triangle then spans from offset=-SQ3/6 to offset=+SQ3/3 with
+// midpoint at SQ3/12) or one of the other face when the cast rotAxis are the wrong way round (and
+// the triangle spans then from 0 to SQ3/2 with midpoint at SQ3/4).
+//
+// This is only true if the rotAxis connect the centers of opposing faces!
+
+  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
+    {
+    float off = ( face==1 || face==3 || face==4 || face==6 ) ? DIST2D/2 : -DIST2D/2;
+    float[] borders = mTouchBorders[axisIndex];
+    int len = borders.length;
+
+    for(int i=0; i<len; i++)
+      {
+      if( offset+off<borders[i] ) return i;
+      }
+
+    return len;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/MovementCornerTwisting.java b/src/main/java/org/distorted/objects/MovementCornerTwisting.java
index 91ae99d4..0ac70635 100644
--- a/src/main/java/org/distorted/objects/MovementCornerTwisting.java
+++ b/src/main/java/org/distorted/objects/MovementCornerTwisting.java
@@ -23,16 +23,21 @@ package org.distorted.objects;
 
 class MovementCornerTwisting extends Movement6
 {
-  MovementCornerTwisting()
-    {
-    super(TwistySkewb.ROT_AXIS);
-    }
+  private static final int[][][] ENABLED = new int[][][]
+      {
+          {{2,0,1},{2,3,1},{2,2,3},{2,0,2}},
+          {{2,2,3},{2,3,1},{2,0,1},{2,0,2}},
+          {{2,1,2},{2,0,1},{2,0,3},{2,2,3}},
+          {{2,1,2},{2,2,3},{2,0,3},{2,0,1}},
+          {{2,0,3},{2,0,2},{2,1,2},{2,1,3}},
+          {{2,1,2},{2,0,2},{2,0,3},{2,1,3}},
+      };
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
+  MovementCornerTwisting(float[][] cuts, boolean[][] rotatable, int numLayers)
     {
-    return offset<0 ? 0 : numLayers-1;
+    super(TwistySkewb.ROT_AXIS,cuts,rotatable,numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -44,73 +49,15 @@ class MovementCornerTwisting extends Movement6
 // |  /  2  \  |
 // -------------
 
-  private int getQuarter(float[] touchPoint)
+  void computeEnabledAxis(int face, float[] touchPoint, int[] enabled)
     {
     boolean p0 = touchPoint[1] >= touchPoint[0];
     boolean p1 = touchPoint[1] >=-touchPoint[0];
+    int quarter = p0 ? (p1 ? 0:3) : (p1 ? 1:2);
 
-    if( p0 )  return p1 ? 0:3;
-    else      return p1 ? 1:2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void computeEnabledAxis(int face, float[] touchPoint, int[] enabled)
-    {
-    enabled[0] = 2;
-
-    int quarter = getQuarter(touchPoint);
-
-    switch(face)
-      {
-      case 0: switch(quarter)
-                {
-                case 0: enabled[1]=0; enabled[2]=1; break;
-                case 1: enabled[1]=3; enabled[2]=1; break;
-                case 2: enabled[1]=2; enabled[2]=3; break;
-                case 3: enabled[1]=0; enabled[2]=2; break;
-                }
-              break;
-      case 1: switch(quarter)
-                {
-                case 0: enabled[1]=2; enabled[2]=3; break;
-                case 1: enabled[1]=3; enabled[2]=1; break;
-                case 2: enabled[1]=0; enabled[2]=1; break;
-                case 3: enabled[1]=0; enabled[2]=2; break;
-                }
-              break;
-      case 2: switch(quarter)
-                {
-                case 0: enabled[1]=1; enabled[2]=2; break;
-                case 1: enabled[1]=0; enabled[2]=1; break;
-                case 2: enabled[1]=0; enabled[2]=3; break;
-                case 3: enabled[1]=2; enabled[2]=3; break;
-                }
-              break;
-      case 3: switch(quarter)
-                {
-                case 0: enabled[1]=1; enabled[2]=2; break;
-                case 1: enabled[1]=2; enabled[2]=3; break;
-                case 2: enabled[1]=0; enabled[2]=3; break;
-                case 3: enabled[1]=0; enabled[2]=1; break;
-                }
-              break;
-      case 4: switch(quarter)
-                {
-                case 0: enabled[1]=0; enabled[2]=3; break;
-                case 1: enabled[1]=0; enabled[2]=2; break;
-                case 2: enabled[1]=1; enabled[2]=2; break;
-                case 3: enabled[1]=1; enabled[2]=3; break;
-                }
-              break;
-      case 5: switch(quarter)
-                {
-                case 0: enabled[1]=1; enabled[2]=2; break;
-                case 1: enabled[1]=0; enabled[2]=2; break;
-                case 2: enabled[1]=0; enabled[2]=3; break;
-                case 3: enabled[1]=1; enabled[2]=3; break;
-                }
-              break;
-      }
+    enabled[0] = ENABLED[face][quarter][0];
+    enabled[1] = ENABLED[face][quarter][1];
+    enabled[2] = ENABLED[face][quarter][2];
     }
 }
+
diff --git a/src/main/java/org/distorted/objects/MovementCube.java b/src/main/java/org/distorted/objects/MovementCube.java
index 89e0c689..44e2f615 100644
--- a/src/main/java/org/distorted/objects/MovementCube.java
+++ b/src/main/java/org/distorted/objects/MovementCube.java
@@ -23,29 +23,24 @@ package org.distorted.objects;
 
 class MovementCube extends Movement6
 {
-  MovementCube()
-    {
-    super(TwistyCube.ROT_AXIS);
-    }
+  private static final int[][][] ENABLED = new int[][][]
+      {
+          {{2,1,2}},{{2,1,2}},{{2,0,2}},{{2,0,2}},{{2,0,1}},{{2,0,1}},
+      };
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
+  MovementCube(float[][] cuts, boolean[][] rotatable, int numLayers)
     {
-    return (int)(numLayers*(offset+DIST2D));
+    super(TwistyCube.ROT_AXIS,cuts,rotatable,numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void computeEnabledAxis(int face, float[] touchPoint, int[] enabled)
     {
-    enabled[0] = 2;
-
-    switch(face/2)
-      {
-      case 0: enabled[1]=1; enabled[2]=2; break;
-      case 1: enabled[1]=0; enabled[2]=2; break;
-      case 2: enabled[1]=0; enabled[2]=1; break;
-      }
+    enabled[0] = ENABLED[face][0][0];
+    enabled[1] = ENABLED[face][0][1];
+    enabled[2] = ENABLED[face][0][2];
     }
 }
diff --git a/src/main/java/org/distorted/objects/MovementDiamond.java b/src/main/java/org/distorted/objects/MovementDiamond.java
index 65465cf2..329a2143 100644
--- a/src/main/java/org/distorted/objects/MovementDiamond.java
+++ b/src/main/java/org/distorted/objects/MovementDiamond.java
@@ -23,21 +23,9 @@ package org.distorted.objects;
 
 class MovementDiamond extends Movement8
 {
-  MovementDiamond()
+  MovementDiamond(float[][] cuts, boolean[][] rotatable, int numLayers)
     {
-    super(TwistyDiamond.ROT_AXIS);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// We have either one of the four faces (1,3,4,6) which, when the retAxis are cast onto it, they
-// point the right way (and so the triangle then spans from offset=-SQ3/6 to offset=+SQ3/3 with
-// midpoint at SQ3/12) or one of the other face when the cast rotAxis are the wrong way round (and
-// the triangle spans then from 0 to SQ3/2 with midpoint at SQ3/4).
-
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
-    {
-    float off = ( face==1 || face==3 || face==4 || face==6 ) ? (offset+2*DIST2D) : (offset+DIST2D);
-    return (int)(numLayers*off/(3*DIST2D));
+    super(TwistyDiamond.ROT_AXIS,cuts,rotatable, numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/MovementHelicopter.java b/src/main/java/org/distorted/objects/MovementHelicopter.java
index 7dee3524..9bf78ee0 100644
--- a/src/main/java/org/distorted/objects/MovementHelicopter.java
+++ b/src/main/java/org/distorted/objects/MovementHelicopter.java
@@ -23,16 +23,9 @@ package org.distorted.objects;
 
 class MovementHelicopter extends Movement6
 {
-  MovementHelicopter()
+  MovementHelicopter(float[][] cuts, boolean[][] rotatable, int numLayers)
     {
-    super(TwistyHelicopter.ROT_AXIS);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
-    {
-    return offset<0 ? 0:2;
+    super(TwistyHelicopter.ROT_AXIS,cuts,rotatable, numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -48,7 +41,6 @@ class MovementHelicopter extends Movement6
     {
     boolean p0 = touchPoint[0] > 0;
     boolean p1 = touchPoint[1] > 0;
-
     return p0 ? (p1 ? 0:1) : (p1 ? 3:2);
     }
 
diff --git a/src/main/java/org/distorted/objects/MovementIvy.java b/src/main/java/org/distorted/objects/MovementIvy.java
index 5468a7ec..0c770c07 100644
--- a/src/main/java/org/distorted/objects/MovementIvy.java
+++ b/src/main/java/org/distorted/objects/MovementIvy.java
@@ -23,16 +23,9 @@ package org.distorted.objects;
 
 class MovementIvy extends Movement6
 {
-  MovementIvy()
+  MovementIvy(float[][] cuts, boolean[][] rotatable, int numLayers)
     {
-    super(TwistyIvy.ROT_AXIS);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
-    {
-    return offset<0 ? 0:1;
+    super(TwistyIvy.ROT_AXIS,cuts,rotatable,numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/MovementJing.java b/src/main/java/org/distorted/objects/MovementJing.java
index 66c09480..67139a83 100644
--- a/src/main/java/org/distorted/objects/MovementJing.java
+++ b/src/main/java/org/distorted/objects/MovementJing.java
@@ -19,22 +19,13 @@
 
 package org.distorted.objects;
 
-import static org.distorted.objects.TwistyJing.F;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 class MovementJing extends Movement4
 {
-  MovementJing()
-    {
-    super(TwistyJing.ROT_AXIS);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
+  MovementJing(float[][] cuts, boolean[][] rotatable, int numLayers)
     {
-    return offset+DIST2D < (SQ3/4)*F ? 0:1;
+    super(TwistyJing.ROT_AXIS,cuts,rotatable,numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/MovementMinx.java b/src/main/java/org/distorted/objects/MovementMinx.java
index a8102264..c532f063 100644
--- a/src/main/java/org/distorted/objects/MovementMinx.java
+++ b/src/main/java/org/distorted/objects/MovementMinx.java
@@ -23,30 +23,9 @@ package org.distorted.objects;
 
 class MovementMinx extends Movement12
 {
-  MovementMinx()
+  MovementMinx(float[][] cuts, boolean[][] rotatable, int numLayers)
     {
-    super(TwistyMinx.ROT_AXIS);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
-    {
-    if( numLayers==3 )
-      {
-      return offset<0 ? 0:2;
-      }
-    if( numLayers==5 )
-      {
-      float quot = offset / DIST2D;
-
-      if( quot>-1.00f && quot<=-0.66f ) return 0;
-      if( quot>-0.66f && quot<= 0.00f ) return 1;
-      if( quot> 0.00f && quot<= 0.66f ) return 3;
-      if( quot> 0.66f && quot<= 1.00f ) return 4;
-      }
-
-    return 0;
+    super(TwistyMinx.ROT_AXIS,cuts,rotatable,numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/MovementPyraminx.java b/src/main/java/org/distorted/objects/MovementPyraminx.java
index 9b795c73..b68e6125 100644
--- a/src/main/java/org/distorted/objects/MovementPyraminx.java
+++ b/src/main/java/org/distorted/objects/MovementPyraminx.java
@@ -23,16 +23,9 @@ package org.distorted.objects;
 
 class MovementPyraminx extends Movement4
 {
-  MovementPyraminx()
+  MovementPyraminx(float[][] cuts, boolean[][] rotatable,int numLayers)
     {
-    super(TwistyPyraminx.ROT_AXIS);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
-    {
-    return (int)(numLayers*(offset+DIST2D)/(3*DIST2D));
+    super(TwistyPyraminx.ROT_AXIS,cuts,rotatable,numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/MovementSquare.java b/src/main/java/org/distorted/objects/MovementSquare.java
index 8bbd2a29..4b5fa82f 100644
--- a/src/main/java/org/distorted/objects/MovementSquare.java
+++ b/src/main/java/org/distorted/objects/MovementSquare.java
@@ -23,16 +23,9 @@ package org.distorted.objects;
 
 class MovementSquare extends Movement6
 {
-  MovementSquare()
+  MovementSquare(float[][] cuts, boolean[][] rotatable, int numLayers)
     {
-    super(TwistySquare.ROT_AXIS);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
-    {
-    return offset<0 ? 0 : 2-axisIndex;
+    super(TwistySquare.ROT_AXIS,cuts,rotatable,numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/MovementUltimate.java b/src/main/java/org/distorted/objects/MovementUltimate.java
index f383e113..e9c6d225 100644
--- a/src/main/java/org/distorted/objects/MovementUltimate.java
+++ b/src/main/java/org/distorted/objects/MovementUltimate.java
@@ -23,16 +23,9 @@ package org.distorted.objects;
 
 class MovementUltimate extends Movement12
 {
-  MovementUltimate()
+  MovementUltimate(float[][] cuts, boolean[][] rotatable, int numLayers)
     {
-    super(TwistyUltimate.ROT_AXIS);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
-    {
-    return offset<0 ? 0:1;
+    super(TwistyUltimate.ROT_AXIS,cuts,rotatable,numLayers);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/TwistyBandagedAbstract.java b/src/main/java/org/distorted/objects/TwistyBandagedAbstract.java
index dfe39d7f..58654b9d 100644
--- a/src/main/java/org/distorted/objects/TwistyBandagedAbstract.java
+++ b/src/main/java/org/distorted/objects/TwistyBandagedAbstract.java
@@ -59,6 +59,8 @@ abstract class TwistyBandagedAbstract extends Twisty6
   private Static4D[] mInitQuats;
   private int[][] mAxisMap;
   private int[][] mFaceMap;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private Movement mMovement;
   ScrambleState[] mStates;
   float[][] POSITIONS;
@@ -313,17 +315,36 @@ abstract class TwistyBandagedAbstract extends Twisty6
 
   float[][] getCuts(int numLayers)
     {
-    float[][] cuts = new float[3][numLayers-1];
+    if( numLayers<2 ) return null;
 
-    for(int i=0; i<numLayers-1; i++)
+    if( mCuts==null )
       {
-      float cut = (2-numLayers)*0.5f + i;
-      cuts[0][i] = cut;
-      cuts[1][i] = cut;
-      cuts[2][i] = cut;
+      mCuts = new float[3][numLayers-1];
+
+      for(int i=0; i<numLayers-1; i++)
+        {
+        float cut = (2-numLayers)*0.5f + i;
+        mCuts[0][i] = cut;
+        mCuts[1][i] = cut;
+        mCuts[2][i] = cut;
+        }
       }
 
-    return cuts;
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[numLayers];
+      for(int i=0; i<numLayers; i++) tmp[i] = true;
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -431,7 +452,14 @@ abstract class TwistyBandagedAbstract extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementCube();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementCube(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyCube.java b/src/main/java/org/distorted/objects/TwistyCube.java
index 5053bf9f..e181aae9 100644
--- a/src/main/java/org/distorted/objects/TwistyCube.java
+++ b/src/main/java/org/distorted/objects/TwistyCube.java
@@ -44,6 +44,8 @@ class TwistyCube extends Twisty6
 
   private ScrambleState[] mStates;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private int[] mBasicAngle;
   private ObjectSticker[] mStickers;
   private Movement mMovement;
@@ -275,17 +277,36 @@ class TwistyCube extends Twisty6
 
   float[][] getCuts(int numLayers)
     {
-    float[][] cuts = new float[3][numLayers-1];
+    if( numLayers<2 ) return null;
 
-    for(int i=0; i<numLayers-1; i++)
+    if( mCuts==null )
       {
-      float cut = (2-numLayers)*0.5f + i;
-      cuts[0][i] = cut;
-      cuts[1][i] = cut;
-      cuts[2][i] = cut;
+      mCuts = new float[3][numLayers-1];
+
+      for(int i=0; i<numLayers-1; i++)
+        {
+        float cut = (2-numLayers)*0.5f + i;
+        mCuts[0][i] = cut;
+        mCuts[1][i] = cut;
+        mCuts[2][i] = cut;
+        }
       }
 
-    return cuts;
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[numLayers];
+      for(int i=0; i<numLayers; i++) tmp[i] = true;
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -328,7 +349,14 @@ class TwistyCube extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementCube();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementCube(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyDiamond.java b/src/main/java/org/distorted/objects/TwistyDiamond.java
index 9db6ca72..2f9f360b 100644
--- a/src/main/java/org/distorted/objects/TwistyDiamond.java
+++ b/src/main/java/org/distorted/objects/TwistyDiamond.java
@@ -47,6 +47,8 @@ public class TwistyDiamond extends Twisty8
   private ScrambleState[] mStates;
   private int[] mBasicAngle;
   private int[] mFaceMap;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private Static4D[] mQuats;
   private int[] mTetraToFaceMap;
   private ObjectSticker[] mStickers;
@@ -143,26 +145,37 @@ public class TwistyDiamond extends Twisty8
 
   float[][] getCuts(int numLayers)
     {
-    if( numLayers<2 )
-      {
-      return null;
-      }
-    else
+    if( numLayers<2 ) return null;
+
+    if( mCuts==null )
       {
-      float[][] cuts = new float[4][numLayers-1];
-      float dist = SQ6/3;
-      float cut  = 0.5f*dist*(2-numLayers);
+      mCuts = new float[4][numLayers-1];
+      float cut = (SQ6/6)*(2-numLayers);
 
       for(int i=0; i<numLayers-1; i++)
         {
-        cuts[0][i] = cut;
-        cuts[1][i] = cut;
-        cuts[2][i] = cut;
-        cuts[3][i] = cut;
-        cut += dist;
+        mCuts[0][i] = cut;
+        mCuts[1][i] = cut;
+        mCuts[2][i] = cut;
+        mCuts[3][i] = cut;
+        cut += SQ6/3;
         }
+      }
+
+    return mCuts;
+    }
 
-      return cuts;
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[numLayers];
+      for(int i=0; i<numLayers; i++) tmp[i] = true;
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
       }
     }
 
@@ -479,7 +492,14 @@ public class TwistyDiamond extends Twisty8
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementDiamond();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementDiamond(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyDino.java b/src/main/java/org/distorted/objects/TwistyDino.java
index 0a09df01..8bfce17c 100644
--- a/src/main/java/org/distorted/objects/TwistyDino.java
+++ b/src/main/java/org/distorted/objects/TwistyDino.java
@@ -45,6 +45,8 @@ public abstract class TwistyDino extends Twisty6
 
   private int[] mBasicAngle;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private ObjectSticker[] mStickers;
   private float[][] mCenters;
   private Movement mMovement;
@@ -91,8 +93,26 @@ public abstract class TwistyDino extends Twisty6
 
   float[][] getCuts(int size)
     {
-    float[] cut = new float[] { -SQ3/3, +SQ3/3 };
-    return new float[][] { cut,cut,cut,cut };
+    if( mCuts==null )
+      {
+      float[] cut = new float[] { -SQ3/3, +SQ3/3 };
+      mCuts = new float[][] { cut,cut,cut,cut };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[] {true,false,true};
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -201,7 +221,14 @@ public abstract class TwistyDino extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementCornerTwisting();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementCornerTwisting(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyHelicopter.java b/src/main/java/org/distorted/objects/TwistyHelicopter.java
index 08042db6..3f641c67 100644
--- a/src/main/java/org/distorted/objects/TwistyHelicopter.java
+++ b/src/main/java/org/distorted/objects/TwistyHelicopter.java
@@ -49,6 +49,8 @@ public class TwistyHelicopter extends Twisty6
   private ScrambleState[] mStates;
   private int[] mBasicAngle;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private float[][] mCenters;
   private int[] mQuatIndices;
   private int[][] mFaceMap;
@@ -160,8 +162,26 @@ public class TwistyHelicopter extends Twisty6
 
   float[][] getCuts(int size)
     {
-    float[] cut = new float[] { -3*SQ2/4, +3*SQ2/4 };
-    return new float[][] { cut,cut,cut,cut,cut,cut };
+    if( mCuts==null )
+      {
+      float[] cut = new float[] { -3*SQ2/4, +3*SQ2/4 };
+      mCuts = new float[][] { cut,cut,cut,cut,cut,cut };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[] {true,false,true};
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -394,7 +414,14 @@ public class TwistyHelicopter extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementHelicopter();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementHelicopter(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyIvy.java b/src/main/java/org/distorted/objects/TwistyIvy.java
index 21a328bd..ce715616 100644
--- a/src/main/java/org/distorted/objects/TwistyIvy.java
+++ b/src/main/java/org/distorted/objects/TwistyIvy.java
@@ -50,6 +50,8 @@ public class TwistyIvy extends Twisty6
   private ScrambleState[] mStates;
   private int[] mBasicAngle;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private int[][] mFaceMap;
   private ObjectSticker[] mStickers;
   private Movement mMovement;
@@ -136,8 +138,27 @@ public class TwistyIvy extends Twisty6
 
   float[][] getCuts(int numLayers)
     {
-    float[] cut = new float[] {0.0f};
-    return new float[][] { cut,cut,cut,cut };
+    if( mCuts==null )
+      {
+      float[] cut = new float[] {0.0f};
+      mCuts = new float[][] { cut,cut,cut,cut };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[numLayers];
+      for(int i=0; i<numLayers; i++) tmp[i] = true;
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -420,7 +441,14 @@ public class TwistyIvy extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementIvy();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementIvy(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyJing.java b/src/main/java/org/distorted/objects/TwistyJing.java
index e2be52c1..0aef5fa2 100644
--- a/src/main/java/org/distorted/objects/TwistyJing.java
+++ b/src/main/java/org/distorted/objects/TwistyJing.java
@@ -51,6 +51,8 @@ public class TwistyJing extends Twisty4
   private int[] mBasicAngle;
   private int[] mRotQuat;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private float[][] mCenters;
   private int[][] mFaceMap;
   private ObjectSticker[] mStickers;
@@ -167,8 +169,27 @@ public class TwistyJing extends Twisty4
 
   float[][] getCuts(int size)
     {
-    float[] cut = { (F-0.5f)*(SQ6/3) };
-    return new float[][] { cut,cut,cut,cut };
+    if( mCuts==null )
+      {
+      float[] cut = { (F-0.5f)*(SQ6/3) };
+      mCuts = new float[][] { cut,cut,cut,cut };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[numLayers];
+      for(int i=0; i<numLayers; i++) tmp[i] = true;
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -384,7 +405,14 @@ public class TwistyJing extends Twisty4
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementJing();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementJing(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyKilominx.java b/src/main/java/org/distorted/objects/TwistyKilominx.java
index 37b00513..9115f915 100644
--- a/src/main/java/org/distorted/objects/TwistyKilominx.java
+++ b/src/main/java/org/distorted/objects/TwistyKilominx.java
@@ -90,35 +90,7 @@ public class TwistyKilominx extends TwistyMinx
 
   float[][] getCuts(int numLayers)
     {
-    float[][] cuts = new float[6][numLayers-1];
-    float D = numLayers*MovementMinx.DIST3D;
-    float E = 2*SIN54;
-    float X = 2*D*E/(1+2*E);  // height of the 'upper' part of a dodecahedron, i.e. put it on a table,
-                              // its height is then D*2*DIST3D, it has one 'lower' part of height X, one
-                              // 'middle' part of height Y and one upper part of height X again.
-                              // It's edge length = numLayers/3.0f.
-    int num = (numLayers-1)/2;
-    float G = X*0.5f/num;     // height of one Layer
-
-    for(int i=0; i<num; i++)
-      {
-      float cut = -D + (i+0.5f)*G;
-      int j = 2*num-1-i;
-      cuts[0][i] = +cut;
-      cuts[0][j] = -cut;
-      cuts[1][i] = +cut;
-      cuts[1][j] = -cut;
-      cuts[2][i] = +cut;
-      cuts[2][j] = -cut;
-      cuts[3][i] = +cut;
-      cuts[3][j] = -cut;
-      cuts[4][i] = +cut;
-      cuts[4][j] = -cut;
-      cuts[5][i] = +cut;
-      cuts[5][j] = -cut;
-      }
-
-    return cuts;
+    return genericGetCuts(numLayers,0.5f);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/TwistyMegaminx.java b/src/main/java/org/distorted/objects/TwistyMegaminx.java
index 75ecb37b..77e36d44 100644
--- a/src/main/java/org/distorted/objects/TwistyMegaminx.java
+++ b/src/main/java/org/distorted/objects/TwistyMegaminx.java
@@ -77,35 +77,7 @@ public class TwistyMegaminx extends TwistyMinx
 
   float[][] getCuts(int numLayers)
     {
-    float[][] cuts = new float[6][numLayers-1];
-    float D = numLayers*MovementMinx.DIST3D;
-    float E = 2*SIN54;
-    float X = 2*D*E/(1+2*E);  // height of the 'upper' part of a dodecahedron, i.e. put it on a table,
-                              // its height is then D*2*DIST3D, it has one 'lower' part of height X, one
-                              // 'middle' part of height Y and one upper part of height X again.
-                              // It's edge length = numLayers/3.0f.
-    int num = (numLayers-1)/2;
-    float G = X*(0.5f-MEGA_D)/num; // height of one Layer
-
-    for(int i=0; i<num; i++)
-      {
-      float cut = -D + (i+0.5f)*G;
-      int j = 2*num-1-i;
-      cuts[0][i] = +cut;
-      cuts[0][j] = -cut;
-      cuts[1][i] = +cut;
-      cuts[1][j] = -cut;
-      cuts[2][i] = +cut;
-      cuts[2][j] = -cut;
-      cuts[3][i] = +cut;
-      cuts[3][j] = -cut;
-      cuts[4][i] = +cut;
-      cuts[4][j] = -cut;
-      cuts[5][i] = +cut;
-      cuts[5][j] = -cut;
-      }
-
-    return cuts;
+    return genericGetCuts(numLayers,0.5f-MEGA_D);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/TwistyMinx.java b/src/main/java/org/distorted/objects/TwistyMinx.java
index 35e26833..071885c0 100644
--- a/src/main/java/org/distorted/objects/TwistyMinx.java
+++ b/src/main/java/org/distorted/objects/TwistyMinx.java
@@ -60,6 +60,8 @@ abstract class TwistyMinx extends Twisty12
   private ScrambleState[] mStates;
   private int[] mBasicAngle;
   private int[] mFaceMap;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private Movement mMovement;
   Static4D[] mQuats;
   float[][] mCenterCoords;
@@ -504,6 +506,58 @@ abstract class TwistyMinx extends Twisty12
     if( mQuats==null ) initializeQuats();
     return mQuats;
     }
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[][] genericGetCuts(int numLayers, float dist)
+    {
+    if( mCuts==null )
+      {
+      mCuts = new float[6][numLayers-1];
+      float D = numLayers*MovementMinx.DIST3D;
+      float E = 2*SIN54;
+      float X = 2*D*E/(1+2*E);  // height of the 'upper' part of a dodecahedron, i.e. put it on a table,
+                                // its height is then D*2*DIST3D, it has one 'lower' part of height X, one
+                                // 'middle' part of height Y and one upper part of height X again.
+                                // It's edge length = numLayers/3.0f.
+      int num = (numLayers-1)/2;
+      float G = X*dist/num;     // height of one Layer
+
+      for(int i=0; i<num; i++)
+        {
+        float cut = -D + (i+0.5f)*G;
+        int j = 2*num-1-i;
+        mCuts[0][i] = +cut;
+        mCuts[0][j] = -cut;
+        mCuts[1][i] = +cut;
+        mCuts[1][j] = -cut;
+        mCuts[2][i] = +cut;
+        mCuts[2][j] = -cut;
+        mCuts[3][i] = +cut;
+        mCuts[3][j] = -cut;
+        mCuts[4][i] = +cut;
+        mCuts[4][j] = -cut;
+        mCuts[5][i] = +cut;
+        mCuts[5][j] = -cut;
+        }
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[numLayers];
+      for(int i=0; i<numLayers; i++) tmp[i] = true;
+      tmp[numLayers/2] = false;
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -531,7 +585,14 @@ abstract class TwistyMinx extends Twisty12
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementMinx();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementMinx(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyMirror.java b/src/main/java/org/distorted/objects/TwistyMirror.java
index 853cc0f3..19765e08 100644
--- a/src/main/java/org/distorted/objects/TwistyMirror.java
+++ b/src/main/java/org/distorted/objects/TwistyMirror.java
@@ -49,6 +49,8 @@ class TwistyMirror extends Twisty6
 
   private ScrambleState[] mStates;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private int[] mBasicAngle;
   private ObjectSticker[] mStickers;
   private float[][] mPositions;
@@ -591,17 +593,34 @@ class TwistyMirror extends Twisty6
 
   float[][] getCuts(int numLayers)
     {
-    float[][] cuts = new float[3][numLayers-1];
-
-    for(int i=0; i<numLayers-1; i++)
+    if( mCuts==null )
       {
-      float cut = (2-numLayers)*0.5f + i;
-      cuts[0][i] = cut;
-      cuts[1][i] = cut;
-      cuts[2][i] = cut;
+      mCuts = new float[3][numLayers-1];
+
+      for(int i=0; i<numLayers-1; i++)
+        {
+        float cut = (2-numLayers)*0.5f + i;
+        mCuts[0][i] = cut;
+        mCuts[1][i] = cut;
+        mCuts[2][i] = cut;
+        }
       }
 
-    return cuts;
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[numLayers];
+      for(int i=0; i<numLayers; i++) tmp[i] = true;
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -630,7 +649,14 @@ class TwistyMirror extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementCube();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementCube(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyPyraminx.java b/src/main/java/org/distorted/objects/TwistyPyraminx.java
index 5ca6705e..82970ec6 100644
--- a/src/main/java/org/distorted/objects/TwistyPyraminx.java
+++ b/src/main/java/org/distorted/objects/TwistyPyraminx.java
@@ -46,6 +46,8 @@ public class TwistyPyraminx extends Twisty4
   private ScrambleState[] mStates;
   private int[] mBasicAngle;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private ObjectSticker[] mStickers;
   private Movement mMovement;
 
@@ -214,18 +216,35 @@ public class TwistyPyraminx extends Twisty4
 
   float[][] getCuts(int numLayers)
     {
-    float[][] cuts = new float[4][numLayers-1];
-
-    for(int i=0; i<numLayers-1; i++)
+    if( mCuts==null )
       {
-      float cut = (1.0f+i-numLayers/3.0f)*(SQ6/3);
-      cuts[0][i] = cut;
-      cuts[1][i] = cut;
-      cuts[2][i] = cut;
-      cuts[3][i] = cut;
+      mCuts = new float[4][numLayers-1];
+
+      for(int i=0; i<numLayers-1; i++)
+        {
+        float cut = (1.0f+i-numLayers/3.0f)*(SQ6/3);
+        mCuts[0][i] = cut;
+        mCuts[1][i] = cut;
+        mCuts[2][i] = cut;
+        mCuts[3][i] = cut;
+        }
       }
 
-    return cuts;
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[numLayers];
+      for(int i=0; i<numLayers; i++) tmp[i] = true;
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -357,7 +376,14 @@ public class TwistyPyraminx extends Twisty4
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementPyraminx();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementPyraminx(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyRedi.java b/src/main/java/org/distorted/objects/TwistyRedi.java
index 78d37568..f2bddced 100644
--- a/src/main/java/org/distorted/objects/TwistyRedi.java
+++ b/src/main/java/org/distorted/objects/TwistyRedi.java
@@ -46,6 +46,8 @@ public class TwistyRedi extends Twisty6
   private ScrambleState[] mStates;
   private int[] mBasicAngle;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private float[][] mCenters;
   private int[][] mFaceMap;
   private ObjectSticker[] mStickers;
@@ -139,9 +141,27 @@ public class TwistyRedi extends Twisty6
 
   float[][] getCuts(int size)
     {
-    float C = +SQ3/3 +0.05f;
-    float[] cut = new float[] {-C,+C};
-    return new float[][] { cut,cut,cut,cut };
+    if( mCuts==null )
+      {
+      float C = +SQ3/3 +0.05f;
+      float[] cut = new float[] {-C,+C};
+      mCuts = new float[][] { cut,cut,cut,cut };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[] {true,false,true};
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -391,7 +411,14 @@ public class TwistyRedi extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementCornerTwisting();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementCornerTwisting(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyRex.java b/src/main/java/org/distorted/objects/TwistyRex.java
index 2ef8d01e..6bceb02a 100644
--- a/src/main/java/org/distorted/objects/TwistyRex.java
+++ b/src/main/java/org/distorted/objects/TwistyRex.java
@@ -48,6 +48,8 @@ public class TwistyRex extends Twisty6
   private ScrambleState[] mStates;
   private int[] mBasicAngle;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private int[][] mFaceMap;
   private ObjectSticker[] mStickers;
   private Movement mMovement;
@@ -134,9 +136,27 @@ public class TwistyRex extends Twisty6
 
   float[][] getCuts(int numLayers)
     {
-    float C = SQ3*0.45f; // bit less than 1/2 of the length of the main diagonal
-    float[] cut = new float[] {-C,+C};
-    return new float[][] { cut,cut,cut,cut };
+    if( mCuts==null )
+      {
+      float C = SQ3*0.45f; // bit less than 1/2 of the length of the main diagonal
+      float[] cut = new float[] {-C,+C};
+      mCuts = new float[][] { cut,cut,cut,cut };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[] {true,false,true};
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -424,7 +444,14 @@ public class TwistyRex extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementCornerTwisting();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementCornerTwisting(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistySkewb.java b/src/main/java/org/distorted/objects/TwistySkewb.java
index 0e651d54..4767388c 100644
--- a/src/main/java/org/distorted/objects/TwistySkewb.java
+++ b/src/main/java/org/distorted/objects/TwistySkewb.java
@@ -46,6 +46,8 @@ public class TwistySkewb extends Twisty6
   private ScrambleState[] mStates;
   private int[] mBasicAngle;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private int[][] mCornerMap,mEdgeMap,mCenterMap;
   private ObjectSticker[] mStickers;
   private Movement mMovement;
@@ -154,14 +156,26 @@ public class TwistySkewb extends Twisty6
 
   float[][] getCuts(int numLayers)
     {
-    switch(numLayers)
+    if( mCuts==null )
       {
-      case 2: float[] c2 = new float[] {0.0f};
-              return new float[][] { c2,c2,c2,c2 };
-      case 3: float[] c3 = new float[] {-SQ3/6,+SQ3/6};
-              return new float[][] { c3,c3,c3,c3 };
+      float[] c = numLayers==2 ? (new float[] {0.0f}) : (new float[] {-SQ3/6,+SQ3/6});
+      mCuts = new float[][] {c,c,c,c};
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = numLayers==2 ? (new boolean[] {true,true}) : (new boolean[] {true,false,true});
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
       }
-    return null;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -533,7 +547,14 @@ public class TwistySkewb extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementCornerTwisting();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementCornerTwisting(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistySquare.java b/src/main/java/org/distorted/objects/TwistySquare.java
index 5d1059ed..037c0289 100644
--- a/src/main/java/org/distorted/objects/TwistySquare.java
+++ b/src/main/java/org/distorted/objects/TwistySquare.java
@@ -47,6 +47,8 @@ abstract class TwistySquare extends Twisty6
     };
 
   private int[] mBasicAngle;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private Movement mMovement;
   Static4D[] mQuats;
 
@@ -118,7 +120,22 @@ abstract class TwistySquare extends Twisty6
 
   float[][] getCuts(int numLayers)
     {
-    return new float[][] { {-0.5f,+0.5f}, {0.0f}, {-0.5f,+0.5f} };
+    if( mCuts==null )
+      {
+      mCuts = new float[][] { {-0.5f,+0.5f}, {0.0f}, {-0.5f,+0.5f} };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      mLayerRotatable = new boolean[][] { {true,false,true}, {true,true}, {true,false,true} };
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -133,7 +150,14 @@ abstract class TwistySquare extends Twisty6
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementSquare();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementSquare(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyUltimate.java b/src/main/java/org/distorted/objects/TwistyUltimate.java
index ddcf1ee1..d4cfc0ce 100644
--- a/src/main/java/org/distorted/objects/TwistyUltimate.java
+++ b/src/main/java/org/distorted/objects/TwistyUltimate.java
@@ -54,6 +54,8 @@ class TwistyUltimate extends Twisty12
   private ScrambleState[] mStates;
   private int[] mBasicAngle;
   private Static4D[] mQuats;
+  private float[][] mCuts;
+  private boolean[][] mLayerRotatable;
   private int[][] mFaceMap;
   private float[][] mCenters;
   private int[] mQuatIndex;
@@ -353,8 +355,27 @@ class TwistyUltimate extends Twisty12
 
   float[][] getCuts(int numLayers)
     {
-    float[] cut = new float[] {0.0f};
-    return new float[][] { cut,cut,cut,cut };
+    if( mCuts==null )
+      {
+      float[] cut = new float[] {0.0f};
+      mCuts = new float[][] { cut,cut,cut,cut };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getLayerRotatable(int numLayers)
+    {
+    if( mLayerRotatable==null )
+      {
+      int numAxis = ROT_AXIS.length;
+      boolean[] tmp = new boolean[numLayers];
+      for(int i=0; i<numLayers; i++) tmp[i] = true;
+      mLayerRotatable = new boolean[numAxis][];
+      for(int i=0; i<numAxis; i++) mLayerRotatable[i] = tmp;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -383,7 +404,14 @@ class TwistyUltimate extends Twisty12
 
   public Movement getMovement()
     {
-    if( mMovement==null ) mMovement = new MovementUltimate();
+    if( mMovement==null )
+      {
+      int numLayers = getNumLayers();
+      if( mCuts==null ) getCuts(numLayers);
+      getLayerRotatable(numLayers);
+
+      mMovement = new MovementUltimate(mCuts,mLayerRotatable,numLayers);
+      }
     return mMovement;
     }
 
