commit df9739f8d0882c0255fea887af5610f01c98e5c7
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sat Jul 24 01:37:16 2021 +0200

    New 'JingPyraminx' object.

diff --git a/src/main/java/org/distorted/objects/MovementJing.java b/src/main/java/org/distorted/objects/MovementJing.java
new file mode 100644
index 00000000..f2995abb
--- /dev/null
+++ b/src/main/java/org/distorted/objects/MovementJing.java
@@ -0,0 +1,84 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.objects;
+
+import org.distorted.library.type.Static3D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class MovementJing extends Movement
+{
+  static final float DIST3D = SQ6/12;
+  static final float DIST2D = SQ3/6;
+
+  static final Static3D[] FACE_AXIS = new Static3D[]
+         {
+           new Static3D(     0,+SQ3/3,+SQ6/3),
+           new Static3D(     0,+SQ3/3,-SQ6/3),
+           new Static3D(-SQ6/3,-SQ3/3,     0),
+           new Static3D(+SQ6/3,-SQ3/3,     0),
+         };
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MovementJing()
+    {
+    super(TwistyJing.ROT_AXIS, FACE_AXIS, DIST3D, DIST2D);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int computeRowFromOffset(int face, int axisIndex, int numLayers, float offset)
+    {
+    return (int)(numLayers*offset/(SQ6/3));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float returnRotationFactor(int numLayers, int row)
+    {
+    return ((float)numLayers)/(numLayers-row);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean isInsideFace(int face, float[] p)
+    {
+    float y = (face > 1 ? p[1] : -p[1]);
+    float x = p[0];
+
+    return (y >= -DIST2D) && (y <= DIST2D*(2-6*x)) && (y <= DIST2D*(2+6*x));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void computeEnabledAxis(int face, float[] touchPoint, int[] enabled)
+    {
+    enabled[0] = 3;
+
+    switch(face)
+      {
+      case 0: enabled[1]=1; enabled[2]=2; enabled[3]=3; break;
+      case 1: enabled[1]=0; enabled[2]=2; enabled[3]=3; break;
+      case 2: enabled[1]=0; enabled[2]=1; enabled[3]=3; break;
+      case 3: enabled[1]=0; enabled[2]=1; enabled[3]=2; break;
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/objects/ObjectList.java b/src/main/java/org/distorted/objects/ObjectList.java
index a0d0f962..960620cd 100644
--- a/src/main/java/org/distorted/objects/ObjectList.java
+++ b/src/main/java/org/distorted/objects/ObjectList.java
@@ -49,24 +49,24 @@ public enum ObjectList
          60
        ),
 
-  PYRA (
+  JING (
          new int[][] {
-                       {3 , 10, 10, R.raw.pyra3, R.drawable.ui_small_pyra3, R.drawable.ui_medium_pyra3, R.drawable.ui_big_pyra3, R.drawable.ui_huge_pyra3} ,
-                       {4 , 15, 17, R.raw.pyra4, R.drawable.ui_small_pyra4, R.drawable.ui_medium_pyra4, R.drawable.ui_big_pyra4, R.drawable.ui_huge_pyra4} ,
-                       {5 , 20, 23, R.raw.pyra5, R.drawable.ui_small_pyra5, R.drawable.ui_medium_pyra5, R.drawable.ui_big_pyra5, R.drawable.ui_huge_pyra5}
+                       {2 , 10, 10, 0, R.drawable.ui_small_ulti, R.drawable.ui_medium_ulti, R.drawable.ui_big_ulti, R.drawable.ui_huge_ulti} ,
                      },
-         TwistyPyraminx.class,
-         new MovementPyraminx(),
+         TwistyJing.class,
+         new MovementJing(),
          1,
          30
        ),
 
-  ULTI (
+  PYRA (
          new int[][] {
-                       {2 , 18, 18, R.raw.ulti, R.drawable.ui_small_ulti, R.drawable.ui_medium_ulti, R.drawable.ui_big_ulti, R.drawable.ui_huge_ulti} ,
+                       {3 , 10, 10, R.raw.pyra3, R.drawable.ui_small_pyra3, R.drawable.ui_medium_pyra3, R.drawable.ui_big_pyra3, R.drawable.ui_huge_pyra3} ,
+                       {4 , 15, 17, R.raw.pyra4, R.drawable.ui_small_pyra4, R.drawable.ui_medium_pyra4, R.drawable.ui_big_pyra4, R.drawable.ui_huge_pyra4} ,
+                       {5 , 20, 23, R.raw.pyra5, R.drawable.ui_small_pyra5, R.drawable.ui_medium_pyra5, R.drawable.ui_big_pyra5, R.drawable.ui_huge_pyra5}
                      },
-         TwistyUltimate.class,
-         new MovementUltimate(),
+         TwistyPyraminx.class,
+         new MovementPyraminx(),
          1,
          30
        ),
@@ -234,6 +234,16 @@ public enum ObjectList
          6,
          60
        ),
+
+  ULTI (
+         new int[][] {
+                       {2 , 18, 18, R.raw.ulti, R.drawable.ui_small_ulti, R.drawable.ui_medium_ulti, R.drawable.ui_big_ulti, R.drawable.ui_huge_ulti} ,
+                     },
+         TwistyUltimate.class,
+         new MovementUltimate(),
+         7,
+         30
+       ),
   ;
 
   public static final int NUM_OBJECTS = values().length;
@@ -642,8 +652,8 @@ public enum ObjectList
     switch(ordinal())
       {
       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 TwistyUltimate      (size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case  1: return new TwistyJing          (size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case  2: return new TwistyPyraminx      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case  3: return new TwistyKilominx      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case  4: return new TwistyMegaminx      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case  5: return new TwistyDino6         (size, quat, texture, mesh, effects, moves, res, scrWidth);
@@ -660,6 +670,7 @@ public enum ObjectList
       case 16: return new TwistyDiamond       (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 17: return new TwistySquare1       (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 18: return new TwistySquare2       (size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case 19: return new TwistyUltimate      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       }
 
     return null;
diff --git a/src/main/java/org/distorted/objects/TwistyJing.java b/src/main/java/org/distorted/objects/TwistyJing.java
new file mode 100644
index 00000000..9d453b18
--- /dev/null
+++ b/src/main/java/org/distorted/objects/TwistyJing.java
@@ -0,0 +1,519 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class TwistyJing extends TwistyObject
+{
+  static final float F = 0.24f;
+
+  static final Static3D[] ROT_AXIS = new Static3D[]
+         {
+           new Static3D(     0,-SQ3/3,-SQ6/3),
+           new Static3D(     0,-SQ3/3,+SQ6/3),
+           new Static3D(+SQ6/3,+SQ3/3,     0),
+           new Static3D(-SQ6/3,+SQ3/3,     0),
+         };
+
+  private static final int[] BASIC_ANGLE = new int[] { 3,3,3,3 };
+
+  private static final int[] FACE_COLORS = new int[]
+         {
+           COLOR_GREEN , COLOR_YELLOW,
+           COLOR_BLUE  , COLOR_RED
+         };
+
+  // computed with res/raw/compute_quats.c
+  private static final Static4D[] QUATS = new Static4D[]
+         {
+           new Static4D(  0.0f,   0.0f,   0.0f,  1.0f),
+           new Static4D(  0.0f,   1.0f,   0.0f,  0.0f),
+           new Static4D( SQ2/2,   0.5f,   0.0f,  0.5f),
+           new Static4D(-SQ2/2,   0.5f,   0.0f,  0.5f),
+           new Static4D(  0.0f,  -0.5f, -SQ2/2,  0.5f),
+           new Static4D(  0.0f,  -0.5f,  SQ2/2,  0.5f),
+           new Static4D( SQ2/2,   0.5f,   0.0f, -0.5f),
+           new Static4D(-SQ2/2,   0.5f,   0.0f, -0.5f),
+           new Static4D(  0.0f,  -0.5f, -SQ2/2, -0.5f),
+           new Static4D(  0.0f,  -0.5f,  SQ2/2, -0.5f),
+           new Static4D( SQ2/2,   0.0f,  SQ2/2,  0.0f),
+           new Static4D(-SQ2/2,   0.0f,  SQ2/2,  0.0f)
+         };
+
+
+static float H = 0.120f;
+
+  static final float[][] CENTERS = new float[][]
+         {
+           { 0.000f, -SQ2/4,  0.500f },
+           { 0.000f, -SQ2/4, -0.500f },
+           {-0.500f,  SQ2/4,  0.000f },
+           { 0.500f,  SQ2/4,  0.000f },
+
+           { 0.000f, -SQ2/4, 0.000f },
+           {-0.250f, 0.000f, 0.250f },
+           { 0.250f, 0.000f, 0.250f },
+           {-0.250f, 0.000f,-0.250f },
+           { 0.250f, 0.000f,-0.250f },
+           { 0.000f,  SQ2/4, 0.000f },
+
+           { 0.000f, H, 1.0f/6 },
+           { 0.000f, H,-1.0f/6 },
+           {-1.0f/6,-H, 0.000f },
+           { 1.0f/6,-H, 0.000f },
+         };
+
+  // Colors of the faces of cubits.
+  // GREEN 0 YELLOW 1 BLUE  2 RED 3
+  // GREEN 4 YELLOW 5 BLUE  6 RED 7
+  // GREEN 8 YELLOW 9 BLUE 10 RED 11
+  private static final int[][] mFaceMap = new int[][]
+         {
+           {  2, 3, 1, 12, 12, 12 },
+           {  0, 1, 3, 12, 12, 12 },
+           {  0, 2, 1, 12, 12, 12 },
+           {  0, 3, 2, 12, 12, 12 },
+
+           {  7, 5, 12, 12, 12, 12 },
+           {  6, 5, 12, 12, 12, 12 },
+           {  7, 6, 12, 12, 12, 12 },
+           {  4, 5, 12, 12, 12, 12 },
+           {  7, 4, 12, 12, 12, 12 },
+           {  6, 4, 12, 12, 12, 12 },
+
+           { 10, 12, 12, 12, 12, 12 },
+           {  8, 12, 12, 12, 12, 12 },
+           {  9, 12, 12, 12, 12, 12 },
+           { 11, 12, 12, 12, 12, 12 },
+         };
+
+  private static final float X = F/2;
+  private static final float Y = F*SQ2/2;
+  private static final float Z =-F/2;
+  private static final float L = (1-3*F);
+  private static final float X2= L/2;
+  private static final float Y2= L*SQ2/2;
+  private static final float Z2=-L/2;
+  private static final float D = F/L;
+
+  private static final double[][] VERTICES_CORNER = new double[][]
+          {
+             { 0.0, 0.0, 0.0 },
+             {   X,   Y,   Z },
+             { 0.0, 2*Y, 2*Z },
+             {  -X,   Y,   Z },
+             { 0.0, 0.0,    -F },
+             {   X,   Y,   Z-F },
+             { 0.0, 2*Y, 2*Z-F },
+             {  -X,   Y,   Z-F },
+          };
+
+  private static final int[][] VERT_INDEXES_CORNER = new int[][]
+          {
+             {0,1,2,3},
+             {1,0,4,5},
+             {7,4,0,3},
+             {1,5,6,2},
+             {7,3,2,6},
+             {4,7,6,5}
+          };
+
+  private static final double[][] VERTICES_EDGE = new double[][]
+          {
+             { 0.0, 0.0,     0.5-F },
+             {   X,   Y,   Z+0.5-F },
+             { 0.0, 2*Y, 2*Z+0.5-F },
+             {  -X,   Y,   Z+0.5-F },
+             { 0.0, 0.0,    -0.5+F },
+             {   X,   Y,  -Z-0.5+F },
+             { 0.0, 2*Y,-2*Z-0.5+F },
+             {  -X,   Y,  -Z-0.5+F },
+          };
+
+  private static final int[][] VERT_INDEXES_EDGE = new int[][]
+          {
+             {0,4,5,1},
+             {3,7,4,0},
+             {0,1,2,3},
+             {4,7,6,5},
+             {1,5,6,2},
+             {2,6,7,3}
+          };
+
+  private static final double[][] VERTICES_FACE = new double[][]
+          {
+             {    0.0,     -2*Y2/3,   -2*Z2/3 },
+             {      X2,       Y2/3,      Z2/3 },
+             {     -X2,       Y2/3,      Z2/3 },
+             {    0.0,     -2*Y2/3,-2*Z2/3+2*D*Z2 },
+             {  X2-D*X2, Y2/3-D*Y2, Z2/3+D*Z2 },
+             { -X2+D*X2, Y2/3-D*Y2, Z2/3+D*Z2 },
+          };
+
+  private static final int[][] VERT_INDEXES_FACE = new int[][]
+          {
+             {0,1,2},
+             {3,5,4},
+             {0,3,4,1},
+             {5,3,0,2},
+             {4,5,2,1}
+          };
+
+  private static final float[][] STICKERS = new float[][]
+          {
+             { 0.0f, -0.5f, 0.28867516f, 0.0f, 0.0f, 0.5f, -0.28867516f, 0.0f },
+             { -0.5f, 0.11983269f, 0.27964598f, -0.43146032f, 0.3200817f, 0.007388768f, -0.09972769f, 0.30423886f },
+             { 0.0f, -0.5f, 0.43301272f, 0.25f, -0.43301272f, 0.25f },
+          };
+
+  private static MeshBase[] mMeshes;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  TwistyJing(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.JING, res, scrWidth);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[][] getCubitPositions(int size)
+    {
+    return CENTERS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Static4D[] getQuats()
+    {
+    return QUATS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumFaces()
+    {
+    return FACE_COLORS.length;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumStickerTypes(int numLayers)
+    {
+    return STICKERS.length;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[][] getCuts(int size)
+    {
+    float[] cut = {F-0.5f};
+    return new float[][] { cut,cut,cut,cut };
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumCubitFaces()
+    {
+    return 6;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float getScreenRatio()
+    {
+    return 1.64f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getFaceColor(int cubit, int cubitface, int size)
+    {
+    return mFaceMap[cubit][cubitface];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getQuat(int cubit)
+    {
+    switch(cubit)
+      {
+      case  0: return 0;
+      case  1: return 1;
+      case  2: return 2;
+      case  3: return 7;
+
+      case  4: return 0;
+      case  5: return 2;
+      case  6: return 7;
+      case  7: return 6;
+      case  8: return 3;
+      case  9: return 10;
+
+      case 10: return 0;
+      case 11: return 1;
+      case 12: return 3;
+      case 13: return 5;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MeshBase createCubitMesh(int cubit, int numLayers)
+    {
+    if( mMeshes==null )
+      {
+      FactoryCubit factory = FactoryCubit.getInstance();
+      factory.clear();
+      mMeshes = new MeshBase[3];
+      }
+
+    MeshBase mesh;
+
+    if( cubit<4 )
+      {
+      if( mMeshes[0]==null )
+        {
+        float[][] bands     = new float[][] { {0.015f,35,0.5f*F,F,5,1,1},{0.001f,35,0.5f*F,F,5,1,1} };
+        int[] bandIndexes   = new int[] { 0,0,0,1,1,1 };
+        float[][] corners   = new float[][] { {0.10f,0.20f*F},{0.07f,0.20f*F} };
+        int[] cornerIndexes = new int[] { 0,1,1,-1,1,-1,-1,-1 };
+        float[][] centers   = new float[][] { { 0.0f, F*SQ2/2, -F} };
+        int[] centerIndexes = new int[] { 0,0,0,-1,0,-1,-1,-1 };
+
+        FactoryCubit factory = FactoryCubit.getInstance();
+
+        factory.createNewFaceTransform(VERTICES_CORNER,VERT_INDEXES_CORNER);
+        mMeshes[0] = factory.createRoundedSolid(VERTICES_CORNER, VERT_INDEXES_CORNER,
+                                                bands, bandIndexes,
+                                                corners, cornerIndexes,
+                                                centers, centerIndexes,
+                                                getNumCubitFaces() );
+        }
+      mesh = mMeshes[0].copy(true);
+      }
+    else if( cubit<10 )
+      {
+      if( mMeshes[1]==null )
+        {
+        float[][] bands     = new float[][] { {0.015f,35,0.5f*F,F,5,1,1},{0.001f,35,0.5f*F,F,5,1,1} };
+        int[] bandIndexes   = new int[] { 0,0,1,1,1,1 };
+        float[][] corners   = new float[][] { {0.07f,0.20f*F} };
+        int[] cornerIndexes = new int[] { 0,0,-1,0,0,0,-1,0 };
+        float[][] centers   = new float[][] { { 0, F*SQ2/2, 0 } };
+        int[] centerIndexes = new int[] { 0,0,-1,0,0,0,-1,0 };
+
+        FactoryCubit factory = FactoryCubit.getInstance();
+
+        factory.createNewFaceTransform(VERTICES_EDGE,VERT_INDEXES_EDGE);
+        mMeshes[1] = factory.createRoundedSolid(VERTICES_EDGE, VERT_INDEXES_EDGE,
+                                                bands, bandIndexes,
+                                                corners, cornerIndexes,
+                                                centers, centerIndexes,
+                                                getNumCubitFaces() );
+
+        factory.printStickerCoords();
+        }
+      mesh = mMeshes[1].copy(true);
+      }
+    else
+      {
+      if( mMeshes[2]==null )
+        {
+        float[][] bands     = new float[][] { {0.020f,35,0.20f*(1-3*F),0.6f*(1-3*F),5,1,1},
+                                              {0.001f,35,0.05f*(1-3*F),0.1f*(1-3*F),5,1,1} };
+        int[] bandIndexes   = new int[] { 0,1,1,1,1,1 };
+        float[][] corners   = new float[][] { {0.04f,0.15f} };
+        int[] cornerIndexes = new int[] { 0,0,0,-1,-1,-1 };
+        float[][] centers   = new float[][] { { 0, -2*Y/3, 4*Z/3 } };
+        int[] centerIndexes = new int[] { 0,0,0,-1,-1,-1 };
+
+        FactoryCubit factory = FactoryCubit.getInstance();
+
+        factory.createNewFaceTransform(VERTICES_FACE,VERT_INDEXES_FACE);
+        mMeshes[2] = factory.createRoundedSolid(VERTICES_FACE, VERT_INDEXES_FACE,
+                                                bands, bandIndexes,
+                                                corners, cornerIndexes,
+                                                centers, centerIndexes,
+                                                getNumCubitFaces() );
+
+        factory.printStickerCoords();
+        }
+      mesh = mMeshes[2].copy(true);
+      }
+
+    MatrixEffectQuaternion quat = new MatrixEffectQuaternion( QUATS[getQuat(cubit)], new Static3D(0,0,0) );
+    mesh.apply(quat,0xffffffff,0);
+
+    return mesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void createFaceTexture(Canvas canvas, Paint paint, int face, int left, int top)
+    {
+    int COLORS = FACE_COLORS.length;
+    int stickerType = face/COLORS;
+    float R,S;
+
+    switch(stickerType)
+      {
+      case 0:  R = 0.05f; S = 0.05f; break;
+      case 1:  R = 0.03f; S = 0.03f; break;
+      default: R = 0.05f; S = 0.06f; break;
+      }
+
+    FactorySticker factory = FactorySticker.getInstance();
+    factory.drawRoundedPolygon(canvas, paint, left, top, STICKERS[stickerType], S, FACE_COLORS[face%COLORS], R);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SQ6/3 = height of the tetrahedron
+
+  float returnMultiplier()
+    {
+    return getNumLayers()/(SQ6/3);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public Static3D[] getRotationAxis()
+    {
+    return ROT_AXIS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[] getBasicAngle()
+    {
+    return BASIC_ANGLE;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void randomizeNewScramble(int[][] scramble, Random rnd, int curr, int total)
+    {
+    if( curr==0 )
+      {
+      scramble[curr][0] = rnd.nextInt(NUM_AXIS);
+      }
+    else
+      {
+      int newVector = rnd.nextInt(NUM_AXIS-1);
+      scramble[curr][0] = (newVector>=scramble[curr-1][0] ? newVector+1 : newVector);
+      }
+
+    scramble[curr][1] = rnd.nextInt(2);
+
+    switch( rnd.nextInt(2) )
+      {
+      case 0: scramble[curr][2] = -1; break;
+      case 1: scramble[curr][2] =  1; break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// JingPyraminx is solved iff
+// a) all of its corner and edge cubits are rotated with the same quat
+// b) its 4 face cubits might also be rotated along the axis perpendicular to the face.
+//
+// So:
+// [10] might be extra QUAT[] or QUAT[]
+// [11] might be extra QUAT[] or QUAT[]
+// [12] might be extra QUAT[] or QUAT[]
+// [13] might be extra QUAT[] or QUAT[]
+
+  public boolean isSolved()
+    {
+    int index = CUBITS[0].mQuatIndex;
+
+    if( CUBITS[1].mQuatIndex != index ) return false;
+    if( CUBITS[2].mQuatIndex != index ) return false;
+    if( CUBITS[3].mQuatIndex != index ) return false;
+    if( CUBITS[4].mQuatIndex != index ) return false;
+    if( CUBITS[5].mQuatIndex != index ) return false;
+    if( CUBITS[6].mQuatIndex != index ) return false;
+    if( CUBITS[7].mQuatIndex != index ) return false;
+    if( CUBITS[8].mQuatIndex != index ) return false;
+    if( CUBITS[9].mQuatIndex != index ) return false;
+
+    // TODO: face cubits
+
+    return true;
+    }
+
+////////////////////////////////////////////////////////////////////////
+// only needed for solvers - there are no JingPyraminx solvers ATM)
+
+  public String retObjectString()
+    {
+    return "";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getObjectName(int numLayers)
+    {
+    return R.string.jing;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getInventor(int numLayers)
+    {
+    return R.string.jing_inventor;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getComplexity(int numLayers)
+    {
+    return 3;
+    }
+}
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index b8125f84..868def83 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -98,6 +98,7 @@
     <string name="ulti2" translatable="false">Skewb Ultimate</string>
     <string name="squa1" translatable="false">Square-1</string>
     <string name="squa2" translatable="false">Square-2</string>
+    <string name="jing"  translatable="false">Jing Pyraminx</string>
 
     <string name="bandaged_fused"  translatable="false">Fused Cube</string>
     <string name="bandaged_2bar"   translatable="false">2Bar Cube</string>
@@ -128,6 +129,7 @@
     <string name="ulti2_inventor" translatable="false">Tony Fisher, 2000</string>
     <string name="squa1_inventor" translatable="false">Vojtech Kopsky, Harel Hrsel, 1999</string>
     <string name="squa2_inventor" translatable="false">David Litwin, 1995</string>
+    <string name="jing_inventor"  translatable="false">Tony Fisher, 1991</string>
 
     <string name="bandaged_fused_inventor"  translatable="false">who knows</string>
     <string name="bandaged_2bar_inventor"   translatable="false">who knows</string>
