commit bc649d9a072f38e9ac555dad96f44c1af281aafd
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon May 24 23:50:51 2021 +0200

    Beginnings of a new object: Skewb Ultimate.

diff --git a/src/main/java/org/distorted/objects/MovementUltimate.java b/src/main/java/org/distorted/objects/MovementUltimate.java
new file mode 100644
index 00000000..a5375cad
--- /dev/null
+++ b/src/main/java/org/distorted/objects/MovementUltimate.java
@@ -0,0 +1,317 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.objects;
+
+import org.distorted.library.type.Static3D;
+
+import static org.distorted.objects.TwistyMinx.C2;
+import static org.distorted.objects.TwistyMinx.COS54;
+import static org.distorted.objects.TwistyMinx.LEN;
+import static org.distorted.objects.TwistyMinx.SIN54;
+import static org.distorted.objects.TwistyObject.SQ5;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class MovementUltimate extends Movement
+{
+  static final float DIST3D = (float)Math.sqrt(0.625f+0.275f*SQ5)/3;
+  static final float DIST2D = (0.5f*SIN54/COS54)/3;
+
+  static final Static3D[] FACE_AXIS = new Static3D[]
+         {
+           new Static3D(    C2/LEN, SIN54/LEN,    0      ),
+           new Static3D(    C2/LEN,-SIN54/LEN,    0      ),
+           new Static3D(   -C2/LEN, SIN54/LEN,    0      ),
+           new Static3D(   -C2/LEN,-SIN54/LEN,    0      ),
+           new Static3D( 0        ,    C2/LEN, SIN54/LEN ),
+           new Static3D( 0        ,    C2/LEN,-SIN54/LEN ),
+           new Static3D( 0        ,   -C2/LEN, SIN54/LEN ),
+           new Static3D( 0        ,   -C2/LEN,-SIN54/LEN ),
+           new Static3D( SIN54/LEN,    0     ,    C2/LEN ),
+           new Static3D( SIN54/LEN,    0     ,   -C2/LEN ),
+           new Static3D(-SIN54/LEN,    0     ,    C2/LEN ),
+           new Static3D(-SIN54/LEN,    0     ,   -C2/LEN )
+         };
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MovementUltimate()
+    {
+    super(TwistyUltimate.ROT_AXIS, FACE_AXIS, DIST3D, DIST2D);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int computeRowFromOffset(int face, int size, float offset)
+    {
+    if( size==3 )
+      {
+      return offset<DIST2D ? 0:2;
+      }
+    if( size==5 )
+      {
+      float quot = offset / DIST2D;
+
+      if( quot>0.00f && quot<=0.34f ) return 0;
+      if( quot>0.34f && quot<=1.00f ) return 1;
+      if( quot>1.00f && quot<=1.66f ) return 3;
+      if( quot>1.66f && quot<=2.00f ) return 4;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float returnRotationFactor(int size, int row)
+    {
+    return 1.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return angle (in radians) that the line connecting the center C of the pentagonal face and the
+// first vertex of the pentagon makes with a vertical line coming upwards from the center C.
+
+  private float returnAngle(int face)
+    {
+    switch(face)
+      {
+      case  0:
+      case  2:
+      case  6:
+      case  7: return 0.0f;
+      case  1:
+      case  3:
+      case  4:
+      case  5: return (float)(36*Math.PI/180);
+      case  9:
+      case 10: return (float)(54*Math.PI/180);
+      case  8:
+      case 11: return (float)(18*Math.PI/180);
+      }
+
+    return 0.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// The pair (distance,angle) defines a point P in R^2 in polar coordinate system. Let V be the vector
+// from the center of the coordinate system to P.
+// Let P' be the point defined by polar (distance,angle+PI/2). Let Lh be the half-line starting at
+// P' and going in the direction of V.
+// Return true iff point 'point' lies on the left of Lh, i.e. when we rotate (using the center of
+// the coordinate system as the center of rotation) 'point' and Lh in such a way that Lh points
+// directly upwards, is 'point' on the left or the right of it?
+
+  private boolean isOnTheLeft(float[] point, float distance, float angle)
+    {
+    float sin = (float)Math.sin(angle);
+    float cos = (float)Math.cos(angle);
+
+    float vx = point[0] + sin*distance;
+    float vy = point[1] - cos*distance;
+
+    return vx*sin < vy*cos;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Return 1,2,3,4,5 - the vertex of the pentagon to which point 'point' is the closest, if the
+// 'point' is inside the pentagon - or 0 otherwise.
+// The 'first' vertex is the one we meet the first when we rotate clockwise starting from 12:00.
+// This vertex makes angle 'returnAngle()' with the line coming out upwards from the center of the
+// pentagon.
+// Distance from the center to a vertex of the pentagon = 1/(6*COS54)
+
+  private int returnPartOfThePentagon(float[] point, int face)
+    {
+    float angle = returnAngle(face);
+    float A = (float)(Math.PI/5);
+
+    for(int i=0; i<5; i++)
+      {
+      if( isOnTheLeft(point, DIST2D, (9-2*i)*A-angle) ) return 0;
+      }
+
+    if( isOnTheLeft(point, 0, 2.5f*A-angle) )
+      {
+      if( isOnTheLeft(point, 0, 3.5f*A-angle) )
+        {
+        return isOnTheLeft(point, 0, 5.5f*A-angle) ? 4 : 5;
+        }
+      else return 1;
+      }
+    else
+      {
+      if( isOnTheLeft(point, 0, 4.5f*A-angle) )
+        {
+        return 3;
+        }
+      else
+        {
+        return isOnTheLeft(point, 0, 6.5f*A-angle) ? 2 : 1;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean isInsideFace(int face, float[] p)
+    {
+    return returnPartOfThePentagon(p,face) > 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void computeEnabledAxis(int face, float[] touchPoint, int[] enabled)
+    {
+    int part = returnPartOfThePentagon(touchPoint,face);
+
+    if( part>0 )
+      {
+      enabled[0] = 2;
+
+      switch(face)
+        {
+        case  0:  switch(part)
+                    {
+                    case 1: enabled[1]=2; enabled[2]=3; break;
+                    case 2: enabled[1]=3; enabled[2]=5; break;
+                    case 3: enabled[1]=5; enabled[2]=1; break;
+                    case 4: enabled[1]=1; enabled[2]=4; break;
+                    case 5: enabled[1]=4; enabled[2]=2; break;
+                    }
+                  break;
+
+        case  1:  switch(part)
+                    {
+                    case 1: enabled[1]=0; enabled[2]=5; break;
+                    case 2: enabled[1]=5; enabled[2]=2; break;
+                    case 3: enabled[1]=2; enabled[2]=3; break;
+                    case 4: enabled[1]=3; enabled[2]=4; break;
+                    case 5: enabled[1]=4; enabled[2]=0; break;
+                    }
+                  break;
+
+        case  2:  switch(part)
+                    {
+                    case 1: enabled[1]=3; enabled[2]=2; break;
+                    case 2: enabled[1]=2; enabled[2]=5; break;
+                    case 3: enabled[1]=5; enabled[2]=0; break;
+                    case 4: enabled[1]=0; enabled[2]=4; break;
+                    case 5: enabled[1]=4; enabled[2]=3; break;
+                    }
+                  break;
+
+        case  3:  switch(part)
+                    {
+                    case 1: enabled[1]=1; enabled[2]=5; break;
+                    case 2: enabled[1]=5; enabled[2]=3; break;
+                    case 3: enabled[1]=3; enabled[2]=2; break;
+                    case 4: enabled[1]=2; enabled[2]=4; break;
+                    case 5: enabled[1]=4; enabled[2]=1; break;
+                    }
+                  break;
+
+        case  4:  switch(part)
+                    {
+                    case 1: enabled[1]=3; enabled[2]=0; break;
+                    case 2: enabled[1]=0; enabled[2]=4; break;
+                    case 3: enabled[1]=4; enabled[2]=5; break;
+                    case 4: enabled[1]=5; enabled[2]=1; break;
+                    case 5: enabled[1]=1; enabled[2]=3; break;
+                    }
+                  break;
+
+        case  5:  switch(part)
+                    {
+                    case 1: enabled[1]=2; enabled[2]=1; break;
+                    case 2: enabled[1]=1; enabled[2]=4; break;
+                    case 3: enabled[1]=4; enabled[2]=5; break;
+                    case 4: enabled[1]=5; enabled[2]=0; break;
+                    case 5: enabled[1]=0; enabled[2]=2; break;
+                    }
+                  break;
+
+        case  6:  switch(part)
+                    {
+                    case 1: enabled[1]=5; enabled[2]=4; break;
+                    case 2: enabled[1]=4; enabled[2]=1; break;
+                    case 3: enabled[1]=1; enabled[2]=2; break;
+                    case 4: enabled[1]=2; enabled[2]=0; break;
+                    case 5: enabled[1]=0; enabled[2]=5; break;
+                    }
+                  break;
+
+        case  7:  switch(part)
+                    {
+                    case 1: enabled[1]=5; enabled[2]=4; break;
+                    case 2: enabled[1]=4; enabled[2]=0; break;
+                    case 3: enabled[1]=0; enabled[2]=3; break;
+                    case 4: enabled[1]=3; enabled[2]=1; break;
+                    case 5: enabled[1]=1; enabled[2]=5; break;
+                    }
+                  break;
+
+        case  8: switch(part)
+                    {
+                    case 1: enabled[1]=2; enabled[2]=0; break;
+                    case 2: enabled[1]=0; enabled[2]=1; break;
+                    case 3: enabled[1]=1; enabled[2]=3; break;
+                    case 4: enabled[1]=3; enabled[2]=5; break;
+                    case 5: enabled[1]=5; enabled[2]=2; break;
+                    }
+                  break;
+
+        case  9:  switch(part)
+                    {
+                    case 1: enabled[1]=3; enabled[2]=4; break;
+                    case 2: enabled[1]=4; enabled[2]=2; break;
+                    case 3: enabled[1]=2; enabled[2]=1; break;
+                    case 4: enabled[1]=1; enabled[2]=0; break;
+                    case 5: enabled[1]=0; enabled[2]=3; break;
+                    }
+                  break;
+
+        case 10:  switch(part)
+                    {
+                    case 1: enabled[1]=2; enabled[2]=4; break;
+                    case 2: enabled[1]=4; enabled[2]=3; break;
+                    case 3: enabled[1]=3; enabled[2]=0; break;
+                    case 4: enabled[1]=0; enabled[2]=1; break;
+                    case 5: enabled[1]=1; enabled[2]=2; break;
+                    }
+                  break;
+
+        case 11:  switch(part)
+                    {
+                    case 1: enabled[1]=3; enabled[2]=1; break;
+                    case 2: enabled[1]=1; enabled[2]=0; break;
+                    case 3: enabled[1]=0; enabled[2]=2; break;
+                    case 4: enabled[1]=2; enabled[2]=5; break;
+                    case 5: enabled[1]=5; enabled[2]=3; break;
+                    }
+                  break;
+        }
+      }
+    else
+      {
+      enabled[0] = 0;
+      }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/objects/ObjectList.java b/src/main/java/org/distorted/objects/ObjectList.java
index 4a4e0d43..e532364f 100644
--- a/src/main/java/org/distorted/objects/ObjectList.java
+++ b/src/main/java/org/distorted/objects/ObjectList.java
@@ -59,14 +59,14 @@ public enum ObjectList
          30
        ),
 
-  DIAM (
+  ULTI (
          new int[][] {
-                       {2 , 10, R.raw.diam2, R.drawable.ui_small_diam, R.drawable.ui_medium_diam, R.drawable.ui_big_diam, R.drawable.ui_huge_diam} ,
+                       {2 , 18, R.raw.diam2, R.drawable.ui_small_diam, R.drawable.ui_medium_diam, R.drawable.ui_big_diam, R.drawable.ui_huge_diam} ,
                      },
-         TwistyDiamond.class,
-         new MovementDiamond(),
+         TwistyUltimate.class,
+         new MovementUltimate(),
          1,
-         60
+         30
        ),
 
   DINO (
@@ -201,6 +201,17 @@ public enum ObjectList
          5,
          60
        ),
+
+  DIAM (
+         new int[][] {
+                       {2 , 10, R.raw.diam2, R.drawable.ui_small_diam, R.drawable.ui_medium_diam, R.drawable.ui_big_diam, R.drawable.ui_huge_diam} ,
+                       {3 , 18, R.raw.diam2, R.drawable.ui_small_diam, R.drawable.ui_medium_diam, R.drawable.ui_big_diam, R.drawable.ui_huge_diam} ,
+                     },
+         TwistyDiamond.class,
+         new MovementDiamond(),
+         6,
+         60
+       ),
   ;
 
   public static final int NUM_OBJECTS = values().length;
@@ -584,7 +595,7 @@ public enum ObjectList
       {
       case  0: return new TwistyCube          (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case  1: return new TwistyPyraminx      (size, quat, texture, mesh, effects, moves, res, scrWidth);
-      case  2: return new TwistyDiamond       (size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case  2: return new TwistyUltimate      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case  3: return new TwistyDino6         (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case  4: return new TwistyDino4         (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case  5: return new TwistyRedi          (size, quat, texture, mesh, effects, moves, res, scrWidth);
@@ -595,9 +606,10 @@ public enum ObjectList
       case 10: return new TwistyKilominx      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 11: return new TwistyMegaminx      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 12: return new TwistyBandagedFused (size, quat, texture, mesh, effects, moves, res, scrWidth);
-      case 13: return new TwistyBandaged2Bar(size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case 13: return new TwistyBandaged2Bar  (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 14: return new TwistyBandaged3Plate(size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 15: return new TwistyBandagedEvil  (size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case 16: return new TwistyDiamond       (size, quat, texture, mesh, effects, moves, res, scrWidth);
       }
 
     return null;
diff --git a/src/main/java/org/distorted/objects/TwistyMinx.java b/src/main/java/org/distorted/objects/TwistyMinx.java
index 30a9a151..99d7143e 100644
--- a/src/main/java/org/distorted/objects/TwistyMinx.java
+++ b/src/main/java/org/distorted/objects/TwistyMinx.java
@@ -61,18 +61,18 @@ abstract class TwistyMinx extends TwistyObject
            new Static3D( SIN54/LEN,    0     ,   -C2/LEN )
          };
 
-  private static final int MINX_LGREEN = 0xff53aa00;
-  private static final int MINX_PINK   = 0xfffd7ab7;
-  private static final int MINX_SANDY  = 0xffefd48b;
-  private static final int MINX_LBLUE  = 0xff00a2d7;
-  private static final int MINX_ORANGE = 0xffff6200;
-  private static final int MINX_VIOLET = 0xff7d59a4;
-  private static final int MINX_DGREEN = 0xff007a47;
-  private static final int MINX_DRED   = 0xffbd0000;
-  private static final int MINX_DBLUE  = 0xff1a29b2;
-  private static final int MINX_DYELLOW= 0xffffc400;
-  private static final int MINX_WHITE  = 0xffffffff;
-  private static final int MINX_GREY   = 0xff727c7b;
+  static final int MINX_LGREEN = 0xff53aa00;
+  static final int MINX_PINK   = 0xfffd7ab7;
+  static final int MINX_SANDY  = 0xffefd48b;
+  static final int MINX_LBLUE  = 0xff00a2d7;
+  static final int MINX_ORANGE = 0xffff6200;
+  static final int MINX_VIOLET = 0xff7d59a4;
+  static final int MINX_DGREEN = 0xff007a47;
+  static final int MINX_DRED   = 0xffbd0000;
+  static final int MINX_DBLUE  = 0xff1a29b2;
+  static final int MINX_DYELLOW= 0xffffc400;
+  static final int MINX_WHITE  = 0xffffffff;
+  static final int MINX_GREY   = 0xff727c7b;
 
   static final int[] FACE_COLORS = new int[]
          {
diff --git a/src/main/java/org/distorted/objects/TwistyUltimate.java b/src/main/java/org/distorted/objects/TwistyUltimate.java
new file mode 100644
index 00000000..de5b0223
--- /dev/null
+++ b/src/main/java/org/distorted/objects/TwistyUltimate.java
@@ -0,0 +1,448 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.objects;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import org.distorted.helpers.FactoryCubit;
+import org.distorted.helpers.FactorySticker;
+import org.distorted.library.effect.MatrixEffectQuaternion;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.mesh.MeshSquare;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.main.R;
+
+import java.util.Random;
+
+import static org.distorted.objects.TwistyMinx.MINX_DBLUE;
+import static org.distorted.objects.TwistyMinx.MINX_DGREEN;
+import static org.distorted.objects.TwistyMinx.MINX_DRED;
+import static org.distorted.objects.TwistyMinx.MINX_DYELLOW;
+import static org.distorted.objects.TwistyMinx.MINX_GREY;
+import static org.distorted.objects.TwistyMinx.MINX_LBLUE;
+import static org.distorted.objects.TwistyMinx.MINX_LGREEN;
+import static org.distorted.objects.TwistyMinx.MINX_ORANGE;
+import static org.distorted.objects.TwistyMinx.MINX_PINK;
+import static org.distorted.objects.TwistyMinx.MINX_SANDY;
+import static org.distorted.objects.TwistyMinx.MINX_VIOLET;
+import static org.distorted.objects.TwistyMinx.MINX_WHITE;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class TwistyUltimate extends TwistyObject
+{
+  private static final float A = (float)Math.sqrt(21*SQ5+47);
+  private static final float B = SQ6*(5*SQ5+11)/(6*A);
+  private static final float C = SQ6*(  SQ5+ 2)/(3*A);
+  private static final float D = SQ3/3;
+  private static final float E = (SQ5+1)/4;
+  private static final float F = (SQ5-1)/4;
+
+  static final Static3D[] ROT_AXIS = new Static3D[]
+         {
+           new Static3D( B,0,C ),
+           new Static3D( C,B,0 ),
+           new Static3D(-D,D,D ),
+           new Static3D( 0,C,-B)
+         };
+
+  private static final int[] FACE_COLORS = new int[]
+         {
+           MINX_LGREEN, MINX_PINK   , MINX_SANDY , MINX_LBLUE,
+           MINX_ORANGE, MINX_VIOLET , MINX_DGREEN, MINX_DRED ,
+           MINX_DBLUE , MINX_DYELLOW, MINX_WHITE , MINX_GREY
+         };
+
+  private static final Static4D[] QUATS = new Static4D[]
+         {
+           new Static4D( 0.0f, 0.0f, 0.0f, 1.0f ),
+           new Static4D(-0.5f, 0.5f, 0.5f, 0.5f ),
+           new Static4D( 0.5f,-0.5f,-0.5f, 0.5f ),
+           new Static4D( 0.0f,    F,   -E, 0.5f ),
+           new Static4D( 0.0f,   -F,    E, 0.5f ),
+           new Static4D(    E, 0.0f,    F, 0.5f ),
+           new Static4D(   -E, 0.0f,   -F, 0.5f ),
+           new Static4D(    F,    E, 0.0f, 0.5f ),
+           new Static4D(   -F,   -E, 0.0f, 0.5f ),
+           new Static4D(    E,    F,-0.5f, 0.0f ),
+           new Static4D( 0.5f,   -E,    F, 0.0f ),
+           new Static4D(    F, 0.5f,    E, 0.0f )
+         };
+
+  private static final double[][] VERTICES_SMALL = new double[][]
+         {
+           { 0.0       ,  0.0      , 0.0       },
+           { -0.5*E    , 0.5*E+0.25, -0.25     },
+           {-0.25      , -E/2      , (-2*E-1)/4},
+           { 0.5*E+0.25, 0.25      , -E/2      },
+           { 0.0       , 0.5       , -E-0.5    },
+           { 0.0       , 0.5       , 0.0       },
+           { -0.5*E    ,-0.5*E+0.25, -0.25     },
+           {  0.5*E    ,-0.5*E+0.25, -0.25     }
+         };
+
+  private static final int[][] VERT_INDEXES_SMALL = new int[][]
+         {
+           {6,0,5,1},   // counterclockwise!
+           {0,7,3,5},
+           {0,6,2,7},
+           {4,3,5,1},
+           {4,2,7,3},
+           {4,1,6,2},
+         };
+
+  private static final double[][] VERTICES_BIG = new double[][]
+         {
+           {-E/2     ,-E/2+0.25,     0.25},
+           { E/2     , E/2-0.25,    -0.25},
+           {-E       ,     0.00,     0.00},
+           {     0.25, E/2     ,-E/2-0.25},
+           {-E/2     ,-E/2-0.25,     0.25},
+           { E/2+0.25,    -0.25,-E/2     },
+           {-E       ,    -0.50,     0.00},
+           {     0.50,     0.00,-E       },
+           {-E  +0.25, E/2     ,-E/2-0.25},
+           {     0.25,-E/2-0.50,-E/2+0.25},
+           {-E/2     ,-E/2-0.25,-E  -0.25}
+         };
+
+  private static final int[][] VERT_INDEXES_BIG = new int[][]
+         {
+           {0,1,3,8,2},   // counterclockwise!
+           {0,4,9,5,1},
+           { 0,2,6,4},
+           { 1,5,7,3},
+           {10,9,4,6},
+           {10,9,5,7},
+           {10,8,3,7},
+           {10,8,2,6}
+         };
+
+  // TODO
+  private static final float[][] STICKERS = new float[][]
+         {
+              { -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f }
+         };
+
+  private static MeshBase[] mMeshes;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  TwistyUltimate(int size, Static4D quat, DistortedTexture texture,
+                 MeshSquare mesh, DistortedEffects effects, int[][] moves, Resources res, int scrWidth)
+    {
+    super(size, size, quat, texture, mesh, effects, moves, ObjectList.ULTI, res, scrWidth);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MeshBase createCubitMesh(int cubit, int numLayers)
+    {
+    if( mMeshes==null )
+      {
+      FactoryCubit factory = FactoryCubit.getInstance();
+      factory.clear();
+      mMeshes = new MeshBase[2];
+      }
+
+    MeshBase mesh;
+
+    if( cubit<8 )
+      {
+      if( mMeshes[0]==null )
+        {
+        float[][] bands= new float[][]
+          {
+             {0.04f,17,0.5f,0.2f,5,  2,2},
+             {0.01f, 1,0.5f,0.2f,5,  2,2}
+          };
+        int[] bandIndexes   = new int[] { 0,0,0,1,1,1 };
+        float[][] corners   = new float[][] {  { 0.013f, 0.08f } };
+        int[] cornerIndexes = new int[] { 0, 0, 0, 0,-1, 0, 0, 0 };
+        float[][] centers   = new float[][] { { 0.0f,-0.5f, -(SQ5+3)/4 } };
+        int[] centerIndexes = new int[] { 0,0,0,0,0,0,0,0 };
+
+        FactoryCubit factory = FactoryCubit.getInstance();
+        factory.createNewFaceTransform(VERTICES_SMALL,VERT_INDEXES_SMALL);
+        mMeshes[0] = factory.createRoundedSolid(VERTICES_SMALL, VERT_INDEXES_SMALL,
+                                                bands, bandIndexes,
+                                                corners, cornerIndexes,
+                                                centers, centerIndexes,
+                                                getNumCubitFaces() );
+        }
+      mesh = mMeshes[0].copy(true);
+      }
+    else
+      {
+      if( mMeshes[1]==null )
+        {
+        float[][] bands= new float[][]
+          {
+            {0.04f,17,0.5f,0.2f,5,  2,2},
+            {0.04f,17,0.5f,0.2f,5,  2,2},
+            {0.01f, 1,0.5f,0.2f,5,  2,2}
+          };
+        int[] bandIndexes   = new int[] { 0,0,1,1,2,2,2,2 };
+        float[][] corners   = new float[][] { { 0.013f, 0.08f } };
+        int[] cornerIndexes = new int[] { 0,0,0,0,0,0,0,0,0,0,-1 };
+        float[][] centers   = new float[][] { { -(SQ5+1)/8, 0.25f, -(SQ5+5)/8 } };
+        int[] centerIndexes = new int[] { 0,0,0,0,0,0,0,0,0,0,0 };
+
+        FactoryCubit factory = FactoryCubit.getInstance();
+        factory.createNewFaceTransform(VERTICES_BIG,VERT_INDEXES_BIG);
+        mMeshes[1] = factory.createRoundedSolid(VERTICES_BIG, VERT_INDEXES_BIG,
+                                                bands, bandIndexes,
+                                                corners, cornerIndexes,
+                                                centers, centerIndexes,
+                                                getNumCubitFaces() );
+        }
+      mesh = mMeshes[1].copy(true);
+      }
+
+    MatrixEffectQuaternion quat = new MatrixEffectQuaternion( getQuat(cubit), new Static3D(0,0,0) );
+    mesh.apply(quat,0xffffffff,0);
+
+    return mesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  private Static4D getQuat(int cubit)
+    {
+    switch(cubit)
+      {
+      case  0:
+      case  1:
+      case  2:
+      case  3:
+      case  4:
+      case  5:
+      case  6:
+      case  7:
+
+      case  8:
+      case  9:
+      case 10:
+      case 11:
+      case 12:
+      case 13: return QUATS[0];
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[][] getCubitPositions(int numLayers)
+    {
+    float[][] tmp = new float[14][];
+
+    // TODO
+
+    return tmp;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  void createFaceTexture(Canvas canvas, Paint paint, int face, int left, int top)
+    {
+    float R = 0.10f;
+    float S = 0.08f;
+
+    FactorySticker factory = FactorySticker.getInstance();
+    factory.drawRoundedPolygon(canvas, paint, left, top, STICKERS[0], S, FACE_COLORS[face], R);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  int getFaceColor(int cubit, int cubitface, int size)
+    {
+    return CUBITS[cubit].mRotationRow[cubitface/2] == (cubitface%2==0 ? (1<<(size-1)):1) ? cubitface : NUM_FACES;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  float returnMultiplier()
+    {
+    return getNumLayers();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Static4D[] getQuats()
+    {
+    return QUATS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumFaces()
+    {
+    return FACE_COLORS.length;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[] getCuts(int numLayers)
+    {
+    float[] cuts = new float[1];
+    cuts[0] = 0.0f;
+
+    return cuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumStickerTypes(int numLayers)
+    {
+    return STICKERS.length;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumCubitFaces()
+    {
+    return 8;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float getScreenRatio()
+    {
+    return 0.5f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[] getRowChances(int numLayers)
+    {
+    float[] chances = new float[2];
+    chances[0] = 0.5f;
+    chances[1] = 1.0f;
+
+    return chances;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public Static3D[] getRotationAxis()
+    {
+    return ROT_AXIS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getBasicAngle()
+    {
+    return 3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void randomizeNewScramble(int[][] scramble, Random rnd, int num)
+    {
+    if( num==0 )
+      {
+      scramble[num][0] = rnd.nextInt(ROTATION_AXIS.length);
+      }
+    else
+      {
+      int newVector = rnd.nextInt(ROTATION_AXIS.length-1);
+      scramble[num][0] = (newVector>=scramble[num-1][0] ? newVector+1 : newVector);
+      }
+
+    float rowFloat = rnd.nextFloat();
+
+    for(int row=0; row<mRowChances.length; row++)
+      {
+      if( rowFloat<=mRowChances[row] )
+        {
+        scramble[num][1] = row;
+        break;
+        }
+      }
+
+    switch( rnd.nextInt(2) )
+      {
+      case 0: scramble[num][2] =  1; break;
+      case 1: scramble[num][2] = -1; break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean isSolved()
+    {
+    int index = CUBITS[0].mQuatIndex;
+
+    for(int i=1; i<NUM_CUBITS; i++)
+      {
+      if( CUBITS[i].mQuatIndex != index ) return false;
+      }
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// only needed for solvers - there are no Skewb Ultimate solvers ATM)
+
+  public String retObjectString()
+    {
+    return "";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getObjectName(int numLayers)
+    {
+    return R.string.ulti2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getInventor(int numLayers)
+    {
+    return R.string.ulti2_inventor;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getComplexity(int numLayers)
+    {
+    return 6;
+    }
+}
diff --git a/src/main/res/raw/compute_quats.c b/src/main/res/raw/compute_quats.c
index 4e281c38..56d91bd1 100644
--- a/src/main/res/raw/compute_quats.c
+++ b/src/main/res/raw/compute_quats.c
@@ -2,12 +2,13 @@
 #include <math.h>
 #include <stdlib.h>
 
-#define PYRA
+#define ULTI
 
 #define SQ2 1.41421356237f
 #define SQ3 1.73205080757f
+#define SQ5 2.23606797750f
 #define PI  3.14159265358f
-#define NUM_QUATS  100
+#define NUM_QUATS  200
 
 #ifdef PYRA 
 #define NUM_AXIS    4
@@ -61,6 +62,18 @@ float axis[NUM_AXIS][3] = { {     0, +SQ2/2, -SQ2/2} ,
 
 #endif
 
+#ifdef ULTI
+#define NUM_AXIS 4
+#define BASIC_ANGLE 3
+
+float axis[NUM_AXIS][3] = {   { 5*SQ5/16 + 11.0f/16,              0.0f   ,    SQ5/8  + 1.0f/4 } ,
+                              {   SQ5/8  +  1.0f/4 ,  5*SQ5/16 + 11.0f/16,             0.0f   } ,
+                              {-3*SQ5/16 -  7.0f/16,  3*SQ5/16 +  7.0f/16,  3*SQ5/16 + 7.0f/16} ,
+                              {             0.0f   ,    SQ5/4  +  1.0f/2 , -5*SQ5/8  -11.0f/8 } };
+
+#endif
+
+
 int inserted=0;
 
 ///////////////////////////////////////////////////////////////////
@@ -127,12 +140,32 @@ void insert(float* quat, float* to)
     if( is_the_same(quat,to+4*i)==1 ) return;
     }
 
-  to[4*inserted+0] = quat[0];
-  to[4*inserted+1] = quat[1];
-  to[4*inserted+2] = quat[2];
-  to[4*inserted+3] = quat[3];
+  if( inserted<NUM_QUATS )
+    {
+    to[4*inserted+0] = quat[0];
+    to[4*inserted+1] = quat[1];
+    to[4*inserted+2] = quat[2];
+    to[4*inserted+3] = quat[3];
+
+    inserted++;
+    }
+  else
+    {
+    printf("Error: inserted=%d\n", inserted);
+    }
+  }
+
+///////////////////////////////////////////////////////////////////
+
+void normalize(int index)
+  {
+  float* a = axis[index];
+
+  float len = (float)(sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2]));
 
-  inserted++;
+  a[0] /= len;  
+  a[1] /= len;  
+  a[2] /= len;  
   }
 
 ///////////////////////////////////////////////////////////////////
@@ -146,6 +179,11 @@ int main(int argc, char** argv)
   tmp[0] = 0.0f; tmp[1] = 0.0f; tmp[2] = 0.0f; tmp[3] = 1.0f;
   insert(tmp,table);
 
+  for(int ax=0; ax<NUM_AXIS; ax++)
+    {
+    normalize(ax);
+    }
+
   for(int angle=1; angle<BASIC_ANGLE; angle++)
     for( int ax=0; ax<NUM_AXIS; ax++)
       {
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 1ef1d320..be028100 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -94,6 +94,7 @@
     <string name="minx3" translatable="false">Megaminx</string>
     <string name="minx4" translatable="false">Master Kilominx</string>
     <string name="minx5" translatable="false">Gigaminx</string>
+    <string name="ulti2" translatable="false">Skewb Ultimate</string>
 
     <string name="bandaged_fused"  translatable="false">Fused Cube</string>
     <string name="bandaged_2bar"   translatable="false">2Bar Cube</string>
@@ -121,6 +122,7 @@
     <string name="minx3_inventor" translatable="false">Ferenc Szlivka, 1982</string>
     <string name="minx4_inventor" translatable="false">David Gugl, 2010</string>
     <string name="minx5_inventor" translatable="false">Tyler Fox, 2006</string>
+    <string name="ulti2_inventor" translatable="false">Tony Fisher, 2000</string>
 
     <string name="bandaged_fused_inventor"  translatable="false">who knows</string>
     <string name="bandaged_2bar_inventor"   translatable="false">who knows</string>
