commit 60bed292222959bf5b0515fdaf4d377dc4f0daf6
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu May 19 00:25:57 2022 +0200

    Mixup Plus Cube: beginnings

diff --git a/src/main/java/org/distorted/objectlib/main/ObjectSignatures.java b/src/main/java/org/distorted/objectlib/main/ObjectSignatures.java
index f0d40805..cde53aca 100644
--- a/src/main/java/org/distorted/objectlib/main/ObjectSignatures.java
+++ b/src/main/java/org/distorted/objectlib/main/ObjectSignatures.java
@@ -73,4 +73,5 @@ public class ObjectSignatures
   public static final int STAR_3 = ObjectType.STAR_3.ordinal();
   public static final int PENT_2 = ObjectType.PENT_2.ordinal();
   public static final int MIXU_3 = ObjectType.MIXU_3.ordinal();
+  public static final int MIXP_3 = ObjectType.MIXP_3.ordinal();
   }
diff --git a/src/main/java/org/distorted/objectlib/main/ObjectType.java b/src/main/java/org/distorted/objectlib/main/ObjectType.java
index a483daf2..7bda4060 100644
--- a/src/main/java/org/distorted/objectlib/main/ObjectType.java
+++ b/src/main/java/org/distorted/objectlib/main/ObjectType.java
@@ -79,11 +79,14 @@ public enum ObjectType
   SKEW_3 ( TwistySkewb.class         , new int[] {3,3,3,3}    , 21, R.drawable.skew_3, true),
   CONT_2 ( TwistyContainer.class     , new int[] {2,2,2,2}    , 12, R.drawable.cont_2, true),
 
+  MIXU_3 ( TwistyMixup3x3.class      , new int[] {3,3,3}      , 24, R.drawable.mixu_3, true),
+  MIXP_3 ( TwistyMixup3x3Plus.class  , new int[] {3,3,3}      , 28, R.drawable.mixu_3, true),
+
   VOID_3 ( TwistyVoid.class          , new int[] {3,3,3}      , 17, R.drawable.void_3, false),  // wasDownloadableButNowIsBuiltIn()
   CRYS_3 ( TwistyCrystal.class       , new int[] {3,3,3,3,3,3}, 27, R.drawable.crys_3, false),  // function!
   STAR_3 ( TwistyStarminx.class      , new int[] {3,3,3,3,3,3}, 27, R.drawable.star_3, false),
   PENT_2 ( TwistyPentultimate.class  , new int[] {2,2,2,2,2,2}, 21, R.drawable.pent_2, false),
-  MIXU_3 ( TwistyMixup.class         , new int[] {3,3,3}      , 24, R.drawable.mixu_3, false),
+
   ;
 
   public static int NUM_OBJECTS;
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyMixup.java b/src/main/java/org/distorted/objectlib/objects/TwistyMixup.java
index 80903127..e2fee2ab 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyMixup.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyMixup.java
@@ -21,10 +21,6 @@ package org.distorted.objectlib.objects;
 
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
-import org.distorted.objectlib.helpers.ObjectFaceShape;
-import org.distorted.objectlib.helpers.ObjectShape;
-import org.distorted.objectlib.helpers.ObjectSignature;
-import org.distorted.objectlib.main.ObjectType;
 import org.distorted.objectlib.main.ShapeHexahedron;
 import org.distorted.objectlib.scrambling.ScrambleState;
 import org.distorted.objectlib.touchcontrol.TouchControlHexahedron;
@@ -36,7 +32,7 @@ import static org.distorted.objectlib.touchcontrol.TouchControl.TYPE_NOT_SPLIT;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class TwistyMixup extends ShapeHexahedron
+abstract class TwistyMixup extends ShapeHexahedron
 {
   static final Static3D[] ROT_AXIS = new Static3D[]
          {
@@ -45,11 +41,11 @@ public class TwistyMixup extends ShapeHexahedron
            new Static3D(0,0,1)
          };
 
-  private ScrambleState[] mStates;
-  private int[][] mBasicAngle;
-  private float[][] mCuts;
-  private float[][] mPosition;
-  private int[] mQuatIndex;
+  ScrambleState[] mStates;
+  int[][] mBasicAngle;
+  float[][] mCuts;
+  float[][] mPosition;
+  int[] mQuatIndex;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -368,20 +364,6 @@ public class TwistyMixup extends ShapeHexahedron
     return mStates;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float[][] getCuts(int[] numLayers)
-    {
-    if( mCuts==null )
-      {
-      float C = 1.5f*(SQ2-1);
-      float[] cut = new float[] {-C,+C};
-      mCuts = new float[][] { cut,cut,cut };
-      }
-
-    return mCuts;
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public boolean[][] getLayerRotatable(int[] numLayers)
@@ -425,148 +407,6 @@ public class TwistyMixup extends ShapeHexahedron
     return TouchControlHexahedron.FACE_AXIS;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public float[][] getCubitPositions(int[] numLayers)
-    {
-    if( mPosition==null )
-      {
-      float SHORT_EDGE = 1.5f-0.75f*SQ2;
-      float LONG_EDGE  = 1.5f*(SQ2-1);
-
-      final float DIST_CORNER = LONG_EDGE+SHORT_EDGE;
-      final float DIST_EDGE   = LONG_EDGE+SHORT_EDGE;
-      final float DIST_CENTER = LONG_EDGE+SHORT_EDGE;
-
-      mPosition = new float[][]
-         {
-             { DIST_CORNER, DIST_CORNER, DIST_CORNER },
-             { DIST_CORNER, DIST_CORNER,-DIST_CORNER },
-             { DIST_CORNER,-DIST_CORNER, DIST_CORNER },
-             { DIST_CORNER,-DIST_CORNER,-DIST_CORNER },
-             {-DIST_CORNER, DIST_CORNER, DIST_CORNER },
-             {-DIST_CORNER, DIST_CORNER,-DIST_CORNER },
-             {-DIST_CORNER,-DIST_CORNER, DIST_CORNER },
-             {-DIST_CORNER,-DIST_CORNER,-DIST_CORNER },
-
-             {      0.0f, DIST_EDGE, DIST_EDGE },
-             { DIST_EDGE,      0.0f, DIST_EDGE },
-             {      0.0f,-DIST_EDGE, DIST_EDGE },
-             {-DIST_EDGE,      0.0f, DIST_EDGE },
-             { DIST_EDGE, DIST_EDGE,      0.0f },
-             { DIST_EDGE,-DIST_EDGE,      0.0f },
-             {-DIST_EDGE,-DIST_EDGE,      0.0f },
-             {-DIST_EDGE, DIST_EDGE,      0.0f },
-             {      0.0f, DIST_EDGE,-DIST_EDGE },
-             { DIST_EDGE,      0.0f,-DIST_EDGE },
-             {      0.0f,-DIST_EDGE,-DIST_EDGE },
-             {-DIST_EDGE,      0.0f,-DIST_EDGE },
-
-             {           0,           0, DIST_CENTER },
-             {           0,           0,-DIST_CENTER },
-             {           0, DIST_CENTER,           0 },
-             {           0,-DIST_CENTER,           0 },
-             { DIST_CENTER,           0,           0 },
-             {-DIST_CENTER,           0,           0 },
-         };
-      }
-
-    return mPosition;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public Static4D getCubitQuats(int cubit, int[] numLayers)
-    {
-    if( mQuatIndex==null )
-      {
-      mQuatIndex = new int[] { 0,10,11,3,6,2,1,8,
-                               0,4,11,5,7,12,13,6,10,20,3,18,
-                               0,3,10,11,7,6 };
-      }
-
-    return mObjectQuats[mQuatIndex[cubit]];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ObjectShape getObjectShape(int variant)
-    {
-    float SHORT_EDGE = 1.5f-0.75f*SQ2;
-    float LONG_EDGE  = 1.5f*(SQ2-1);
-    float X = variant==0 ? SHORT_EDGE : LONG_EDGE;
-    float Y = variant<=1 ? SHORT_EDGE : LONG_EDGE;
-    float Z = SHORT_EDGE;
-
-    float[][] vertices =
-          {
-              { -X,-Y,-Z },
-              { -X,-Y,+Z },
-              { -X,+Y,-Z },
-              { -X,+Y,+Z },
-              { +X,-Y,-Z },
-              { +X,-Y,+Z },
-              { +X,+Y,-Z },
-              { +X,+Y,+Z },
-          };
-
-    int[][] indices =
-          {
-              {1,5,7,3},
-              {3,7,6,2},
-              {5,4,6,7},
-              {0,1,3,2},
-              {4,0,2,6},
-              {0,4,5,1}
-          };
-
-    return new ObjectShape(vertices, indices);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ObjectFaceShape getObjectFaceShape(int variant)
-    {
-    float h1 = isInIconMode() ? 0.001f : 0.06f;
-    float h2 = 0.001f;
-    float[][] bands   = { {h1,45,0.3f,0.5f,5,1,0}, {h2,45,0.3f,0.5f,5,1,0} };
-    float[][] centers = { { 0.0f, 0.0f, 0.0f} };
-    float[][] corners = { {0.04f,0.10f} };
-
-    if( variant==0 )
-      {
-      int[] bandIndices = { 0,0,0,1,1,1 };
-      int[] indices     = { -1,0,0,0,0,0,0,0 };
-      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
-      }
-    if( variant==1 )
-      {
-      int[] bandIndices = { 0,0,1,1,1,1 };
-      int[] indices     = { -1,0,0,0,-1,0,0,0 };
-      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
-      }
-    else
-      {
-      int[] bandIndices = { 0,1,1,1,1,1 };
-      int[] indices     = { -1,0,-1,0,-1,0,-1,0 };
-      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getNumCubitVariants(int[] numLayers)
-    {
-    return 3;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getCubitVariant(int cubit, int[] numLayers)
-    {
-    return cubit<8 ? 0 : (cubit<20?1:2);
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public float getStickerRadius()
@@ -608,65 +448,4 @@ public class TwistyMixup extends ShapeHexahedron
 
     return mBasicAngle;
     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String getShortName()
-    {
-    return ObjectType.MIXU_3.name();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ObjectSignature getSignature()
-    {
-    return new ObjectSignature(ObjectType.MIXU_3);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String getObjectName()
-    {
-    return "Mixup Cube";
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String getInventor()
-    {
-    return "Sergey Makarov";
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getYearOfInvention()
-    {
-    return 1985;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getComplexity()
-    {
-    return 2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String[][] getTutorials()
-    {
-    return new String[][]{
-                          {"gb","w0DmJYwNI3Q","How to Solve the Mixup Cube","Z3"},
-                          {"es","wHyf1imdAi4","Resolver Mixup 3x3","Cuby"},
-                          {"ru","PN2ntFP6sfs","Как собрать Mixup","Цель+Действие=Результат"},
-                          {"fr","QWsFaw0zUJU","Résolution du Mixup Cube","skieur cubb"},
-                          {"de","vvDPyByyLyQ","Lösung für den 3x3x3 Mixup Cube","rofrisch"},
-                          {"pl","TpG6MzWmwLQ","Mixup 3x3x3 Cube Tutorial","MrUK"},
-                          {"br","_63j3i4Xa78","3x3 Mixup cube Tutorial 1/3","Cubo vicio"},
-                          {"br","qpTnQavPLEI","3x3 Mixup cube Tutorial 2/3","Cubo vicio"},
-                          {"br","nrFEm1ygcV4","3x3 Mixup cube Tutorial 3/3","Cubo vicio"},
-                          {"kr","Ag4XkmC2v6c","3x3x3 믹스업 (Mix-up) 큐브 해법","듀나메스 큐브 해법연구소"},
-                          {"vn","mWW6HYbvvh8","Mixup 3x3 Tutorial","VĂN CÔNG TÙNG"},
-                         };
-    }
 }
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyMixup3x3.java b/src/main/java/org/distorted/objectlib/objects/TwistyMixup3x3.java
new file mode 100644
index 00000000..5034439e
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyMixup3x3.java
@@ -0,0 +1,259 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.objectlib.objects;
+
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.objectlib.helpers.ObjectFaceShape;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSignature;
+import org.distorted.objectlib.main.ObjectType;
+
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class TwistyMixup3x3 extends TwistyMixup
+{
+  public TwistyMixup3x3(int[] numL, int meshState, int iconMode, Static4D quat, Static3D move, float scale, InputStream stream)
+    {
+    super(numL, meshState, iconMode, quat, move, scale, stream);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[][] getCuts(int[] numLayers)
+    {
+    if( mCuts==null )
+      {
+      float C = 1.5f*(SQ2-1);
+      float[] cut = new float[] {-C,+C};
+      mCuts = new float[][] { cut,cut,cut };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[][] getCubitPositions(int[] numLayers)
+    {
+    if( mPosition==null )
+      {
+      float SHORT_EDGE = 1.5f-0.75f*SQ2;
+      float LONG_EDGE  = 1.5f*(SQ2-1);
+
+      final float DIST_CORNER = LONG_EDGE+SHORT_EDGE;
+      final float DIST_EDGE   = LONG_EDGE+SHORT_EDGE;
+      final float DIST_CENTER = LONG_EDGE+SHORT_EDGE;
+
+      mPosition = new float[][]
+         {
+             { DIST_CORNER, DIST_CORNER, DIST_CORNER },
+             { DIST_CORNER, DIST_CORNER,-DIST_CORNER },
+             { DIST_CORNER,-DIST_CORNER, DIST_CORNER },
+             { DIST_CORNER,-DIST_CORNER,-DIST_CORNER },
+             {-DIST_CORNER, DIST_CORNER, DIST_CORNER },
+             {-DIST_CORNER, DIST_CORNER,-DIST_CORNER },
+             {-DIST_CORNER,-DIST_CORNER, DIST_CORNER },
+             {-DIST_CORNER,-DIST_CORNER,-DIST_CORNER },
+
+             {      0.0f, DIST_EDGE, DIST_EDGE },
+             { DIST_EDGE,      0.0f, DIST_EDGE },
+             {      0.0f,-DIST_EDGE, DIST_EDGE },
+             {-DIST_EDGE,      0.0f, DIST_EDGE },
+             { DIST_EDGE, DIST_EDGE,      0.0f },
+             { DIST_EDGE,-DIST_EDGE,      0.0f },
+             {-DIST_EDGE,-DIST_EDGE,      0.0f },
+             {-DIST_EDGE, DIST_EDGE,      0.0f },
+             {      0.0f, DIST_EDGE,-DIST_EDGE },
+             { DIST_EDGE,      0.0f,-DIST_EDGE },
+             {      0.0f,-DIST_EDGE,-DIST_EDGE },
+             {-DIST_EDGE,      0.0f,-DIST_EDGE },
+
+             {           0,           0, DIST_CENTER },
+             {           0,           0,-DIST_CENTER },
+             {           0, DIST_CENTER,           0 },
+             {           0,-DIST_CENTER,           0 },
+             { DIST_CENTER,           0,           0 },
+             {-DIST_CENTER,           0,           0 },
+         };
+      }
+
+    return mPosition;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public Static4D getCubitQuats(int cubit, int[] numLayers)
+    {
+    if( mQuatIndex==null )
+      {
+      mQuatIndex = new int[] { 0,10,11,3,6,2,1,8,
+                               0,4,11,5,7,12,13,6,10,20,3,18,
+                               0,3,10,11,7,6 };
+      }
+
+    return mObjectQuats[mQuatIndex[cubit]];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectShape getObjectShape(int variant)
+    {
+    float SHORT_EDGE = 1.5f-0.75f*SQ2;
+    float LONG_EDGE  = 1.5f*(SQ2-1);
+    float X = variant==0 ? SHORT_EDGE : LONG_EDGE;
+    float Y = variant<=1 ? SHORT_EDGE : LONG_EDGE;
+    float Z = SHORT_EDGE;
+
+    float[][] vertices =
+          {
+              { -X,-Y,-Z },
+              { -X,-Y,+Z },
+              { -X,+Y,-Z },
+              { -X,+Y,+Z },
+              { +X,-Y,-Z },
+              { +X,-Y,+Z },
+              { +X,+Y,-Z },
+              { +X,+Y,+Z },
+          };
+
+    int[][] indices =
+          {
+              {1,5,7,3},
+              {3,7,6,2},
+              {5,4,6,7},
+              {0,1,3,2},
+              {4,0,2,6},
+              {0,4,5,1}
+          };
+
+    return new ObjectShape(vertices, indices);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectFaceShape getObjectFaceShape(int variant)
+    {
+    float h1 = isInIconMode() ? 0.001f : 0.06f;
+    float h2 = 0.001f;
+    float[][] bands   = { {h1,45,0.3f,0.5f,5,1,0}, {h2,45,0.3f,0.5f,5,1,0} };
+    float[][] centers = { { 0.0f, 0.0f, 0.0f} };
+    float[][] corners = { {0.04f,0.10f} };
+
+    if( variant==0 )
+      {
+      int[] bandIndices = { 0,0,0,1,1,1 };
+      int[] indices     = { -1,0,0,0,0,0,0,0 };
+      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
+      }
+    if( variant==1 )
+      {
+      int[] bandIndices = { 0,0,1,1,1,1 };
+      int[] indices     = { -1,0,0,0,-1,0,0,0 };
+      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
+      }
+    else
+      {
+      int[] bandIndices = { 0,1,1,1,1,1 };
+      int[] indices     = { -1,0,-1,0,-1,0,-1,0 };
+      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumCubitVariants(int[] numLayers)
+    {
+    return 3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getCubitVariant(int cubit, int[] numLayers)
+    {
+    return cubit<8 ? 0 : (cubit<20?1:2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getShortName()
+    {
+    return ObjectType.MIXU_3.name();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectSignature getSignature()
+    {
+    return new ObjectSignature(ObjectType.MIXU_3);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getObjectName()
+    {
+    return "Mixup Cube";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getInventor()
+    {
+    return "Sergey Makarov";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getYearOfInvention()
+    {
+    return 1985;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getComplexity()
+    {
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String[][] getTutorials()
+    {
+    return new String[][]{
+                          {"gb","w0DmJYwNI3Q","How to Solve the Mixup Cube","Z3"},
+                          {"es","wHyf1imdAi4","Resolver Mixup 3x3","Cuby"},
+                          {"ru","PN2ntFP6sfs","Как собрать Mixup","Цель+Действие=Результат"},
+                          {"fr","QWsFaw0zUJU","Résolution du Mixup Cube","skieur cubb"},
+                          {"de","vvDPyByyLyQ","Lösung für den 3x3x3 Mixup Cube","rofrisch"},
+                          {"pl","TpG6MzWmwLQ","Mixup 3x3x3 Cube Tutorial","MrUK"},
+                          {"br","_63j3i4Xa78","3x3 Mixup cube Tutorial 1/3","Cubo vicio"},
+                          {"br","qpTnQavPLEI","3x3 Mixup cube Tutorial 2/3","Cubo vicio"},
+                          {"br","nrFEm1ygcV4","3x3 Mixup cube Tutorial 3/3","Cubo vicio"},
+                          {"kr","Ag4XkmC2v6c","3x3x3 믹스업 (Mix-up) 큐브 해법","듀나메스 큐브 해법연구소"},
+                          {"vn","mWW6HYbvvh8","Mixup 3x3 Tutorial","VĂN CÔNG TÙNG"},
+                         };
+    }
+}
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyMixup3x3Plus.java b/src/main/java/org/distorted/objectlib/objects/TwistyMixup3x3Plus.java
new file mode 100644
index 00000000..6efdc76a
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyMixup3x3Plus.java
@@ -0,0 +1,354 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.objectlib.objects;
+
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.objectlib.helpers.ObjectFaceShape;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSignature;
+import org.distorted.objectlib.main.ObjectType;
+
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class TwistyMixup3x3Plus extends TwistyMixup
+{
+  public TwistyMixup3x3Plus(int[] numL, int meshState, int iconMode, Static4D quat, Static3D move, float scale, InputStream stream)
+    {
+    super(numL, meshState, iconMode, quat, move, scale, stream);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[][] getCuts(int[] numLayers)
+    {
+    if( mCuts==null )
+      {
+      float C = (6*SQ2-3)/14;
+      float[] cut = new float[] {-C,+C};
+      mCuts = new float[][] { cut,cut,cut };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[][] getCubitPositions(int[] numLayers)
+    {
+    if( mPosition==null )
+      {
+      float SHORT_EDGE = 1.5f-0.75f*SQ2;
+      float LONG_EDGE  = 1.5f*(SQ2-1);
+
+      final float DIST_CORNER = LONG_EDGE+SHORT_EDGE;
+      final float DIST_EDGE   = 1.5f*LONG_EDGE+SHORT_EDGE;
+      final float DIST_CENTER = LONG_EDGE+SHORT_EDGE;
+      final float DIST_SMALL  = 2.0f*LONG_EDGE+SHORT_EDGE;
+      final float DIST        = 0.5f*LONG_EDGE+SHORT_EDGE;
+
+      mPosition = new float[][]
+         {
+             { DIST_CORNER, DIST_CORNER, DIST_CORNER },
+             { DIST_CORNER, DIST_CORNER,-DIST_CORNER },
+             { DIST_CORNER,-DIST_CORNER, DIST_CORNER },
+             { DIST_CORNER,-DIST_CORNER,-DIST_CORNER },
+             {-DIST_CORNER, DIST_CORNER, DIST_CORNER },
+             {-DIST_CORNER, DIST_CORNER,-DIST_CORNER },
+             {-DIST_CORNER,-DIST_CORNER, DIST_CORNER },
+             {-DIST_CORNER,-DIST_CORNER,-DIST_CORNER },
+
+             {      0.0f, DIST_EDGE, DIST_EDGE },
+             { DIST_EDGE,      0.0f, DIST_EDGE },
+             {      0.0f,-DIST_EDGE, DIST_EDGE },
+             {-DIST_EDGE,      0.0f, DIST_EDGE },
+             { DIST_EDGE, DIST_EDGE,      0.0f },
+             { DIST_EDGE,-DIST_EDGE,      0.0f },
+             {-DIST_EDGE,-DIST_EDGE,      0.0f },
+             {-DIST_EDGE, DIST_EDGE,      0.0f },
+             {      0.0f, DIST_EDGE,-DIST_EDGE },
+             { DIST_EDGE,      0.0f,-DIST_EDGE },
+             {      0.0f,-DIST_EDGE,-DIST_EDGE },
+             {-DIST_EDGE,      0.0f,-DIST_EDGE },
+
+             {           0,        DIST,  DIST_SMALL },
+             {        DIST,           0,  DIST_SMALL },
+             {           0,       -DIST,  DIST_SMALL },
+             {       -DIST,           0,  DIST_SMALL },
+             {           0,        DIST, -DIST_SMALL },
+             {        DIST,           0, -DIST_SMALL },
+             {           0,       -DIST, -DIST_SMALL },
+             {       -DIST,           0, -DIST_SMALL },
+             {           0,  DIST_SMALL,       -DIST },
+             {        DIST,  DIST_SMALL,           0 },
+             {           0,  DIST_SMALL,        DIST },
+             {       -DIST,  DIST_SMALL,           0 },
+             {           0, -DIST_SMALL,       -DIST },
+             {        DIST, -DIST_SMALL,           0 },
+             {           0, -DIST_SMALL,        DIST },
+             {       -DIST, -DIST_SMALL,           0 },
+             {  DIST_SMALL,        DIST,           0 },
+             {  DIST_SMALL,           0,       -DIST },
+             {  DIST_SMALL,       -DIST,           0 },
+             {  DIST_SMALL,           0,        DIST },
+             { -DIST_SMALL,        DIST,           0 },
+             { -DIST_SMALL,           0,       -DIST },
+             { -DIST_SMALL,       -DIST,           0 },
+             { -DIST_SMALL,           0,        DIST },
+
+             {           0,           0, DIST_CENTER },
+             {           0,           0,-DIST_CENTER },
+             {           0, DIST_CENTER,           0 },
+             {           0,-DIST_CENTER,           0 },
+             { DIST_CENTER,           0,           0 },
+             {-DIST_CENTER,           0,           0 },
+         };
+      }
+
+    return mPosition;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public Static4D getCubitQuats(int cubit, int[] numLayers)
+    {
+    if( mQuatIndex==null )
+      {
+      mQuatIndex = new int[] { 0,10,11,3,6,2,1,8,
+                               0,4,11,5,7,12,13,6,10,20,3,18,
+                               0,4,0,4, 3,15,3,15, 10,16,10,16, 11,19,11,19, 7,17,7,17, 6,18,6,18,
+                               0,3,10,11,7,6 };
+      }
+
+    return mObjectQuats[mQuatIndex[cubit]];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectShape getObjectShape(int variant)
+    {
+    float SHORT_EDGE = 1.5f-0.75f*SQ2;
+    float LONG_EDGE  = 1.5f*(SQ2-1);
+
+    if( variant==0 || variant==3 )
+      {
+      float X = variant==0 ? LONG_EDGE : SHORT_EDGE;
+      float Y = variant==0 ? LONG_EDGE : SHORT_EDGE;
+      float Z = LONG_EDGE;
+
+      float[][] vertices =
+          {
+              { -X,-Y,-Z },
+              { -X,-Y,+Z },
+              { -X,+Y,-Z },
+              { -X,+Y,+Z },
+              { +X,-Y,-Z },
+              { +X,-Y,+Z },
+              { +X,+Y,-Z },
+              { +X,+Y,+Z },
+          };
+
+      int[][] indices =
+          {
+              {1,5,7,3},
+              {3,7,6,2},
+              {5,4,6,7},
+              {0,1,3,2},
+              {4,0,2,6},
+              {0,4,5,1}
+          };
+
+      return new ObjectShape(vertices, indices);
+      }
+    else if( variant==1 )
+      {
+      float X = SHORT_EDGE;
+      float Y = LONG_EDGE/2;
+      float Z = LONG_EDGE/2;
+
+      float[][] vertices =
+          {
+              { -X,+Y,+Z },
+              { -X,+Y,-Z },
+              { +X,+Y,-Z },
+              { +X,+Y,+Z },
+              { -X,-Y,+Z },
+              { +X,-Y,+Z },
+              { -X,-Y,-3*Z },
+              { +X,-Y,-3*Z },
+              { +X,-3*Y,-Z },
+              { -X,-3*Y,-Z },
+          };
+
+      int[][] indices =
+          {
+              {0,3,2,1},
+              {4,5,3,0},
+              {8,7,2,3,5},
+              {6,9,4,0,1},
+              {9,8,5,4},
+              {7,6,1,2},
+              {6,7,8,9}
+          };
+
+      return new ObjectShape(vertices, indices);
+      }
+    else
+      {
+      float X = SHORT_EDGE;
+      float Y = LONG_EDGE/2;
+
+      float[][] vertices =
+          {
+              { -X,-Y, 0 },
+              { -X,+Y, 0 },
+              { +X,-Y, 0 },
+              { +X,+Y, 0 },
+              { -X, 0,-Y },
+              { +X, 0,-Y }
+          };
+
+      int[][] indices =
+          {
+              {0,2,3,1},
+              {2,5,3},
+              {1,4,0},
+              {0,4,5,2},
+              {3,5,4,1}
+          };
+
+      return new ObjectShape(vertices, indices);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectFaceShape getObjectFaceShape(int variant)
+    {
+    float h1 = isInIconMode() ? 0.001f : 0.06f;
+    float h2 = 0.001f;
+    float[][] bands   = { {h1,45,0.3f,0.5f,5,1,0}, {h2,45,0.3f,0.5f,5,1,0} };
+    float[][] centers = { { 0.0f, 0.0f, 0.0f} };
+    float[][] corners = { {0.04f,0.10f} };
+
+    if( variant==0 )
+      {
+      int[] bandIndices = { 0,0,0,1,1,1 };
+      int[] indices     = { -1,0,0,0,0,0,0,0 };
+      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
+      }
+    if( variant==1 )
+      {
+      int[] bandIndices = { 0,0,1,1,1,1,1 };
+      int[] indices     = { 0,0,0,0,0,0,-1,-1,-1,-1 };
+      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
+      }
+    if( variant==2 )
+      {
+      int[] bandIndices = { 0,1,1,1,1 };
+      int[] indices     = { 0,0,0,0,-1,-1 };
+      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
+      }
+    else
+      {
+      int[] bandIndices = { 0,1,1,1,1,1 };
+      int[] indices     = { -1,0,-1,0,-1,0,-1,0 };
+      return new ObjectFaceShape(bands,bandIndices,corners,indices,centers,indices,null);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumCubitVariants(int[] numLayers)
+    {
+    return 4;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getCubitVariant(int cubit, int[] numLayers)
+    {
+    return cubit<8 ? 0 : (cubit<20 ? 1 : (cubit<44?2:3));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getShortName()
+    {
+    return ObjectType.MIXP_3.name();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectSignature getSignature()
+    {
+    return new ObjectSignature(ObjectType.MIXP_3);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getObjectName()
+    {
+    return "Mixup Plus Cube";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getInventor()
+    {
+    return "Guan Yang";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getYearOfInvention()
+    {
+    return 2012;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getComplexity()
+    {
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String[][] getTutorials()
+    {
+    return new String[][]{
+                          {"gb","0ABxlmD6AYQ","3x3 Mixup Plus Tutorial","Superantoniovivaldi"},
+                          {"es","s19NAGbkbw4","Tutorial Mixup Plus 3x3x3","Kubekings"},
+                          {"fr","QWsFaw0zUJU","Résolution du Mixup Plus","skieur cubb"},
+                          {"ru","cdtK86ZeF2w","Как собрать Mixup plus 1/3","Иван Циков"},
+                          {"ru","OHg0MfM2iEU","Как собрать Mixup plus 2/3","Иван Циков"},
+                          {"ru","rtQoRAoMRS8","Как собрать Mixup plus 3/3","Иван Циков"},
+                          {"pl","8Bm0LNsGfIs","Mixup 3x3x3 Plus TUTORIAL PL","MrUK"},
+                          {"kr","80QKOZXGtBs","믹스업 플러스 (Mix-up Plus) 해법","듀나메스 큐브 해법연구소"},
+                          {"vn","vuIfGk1Asmc","Tutorial N.206 - Mixup Plus","Duy Thích Rubik"},
+                         };
+    }
+}
