commit eaee1ddc7809c65e0c871c9501096bc20dae8ac8
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Sep 24 21:35:42 2020 +0100

    Add the 4-color Dino.
    
    Still one thing needs to be done about it: randomization of Moves (now sometimes in Level 1 the randomized move leads to an already solved position)

diff --git a/src/main/java/org/distorted/objects/RubikCube.java b/src/main/java/org/distorted/objects/RubikCube.java
index c48f5581..d3367794 100644
--- a/src/main/java/org/distorted/objects/RubikCube.java
+++ b/src/main/java/org/distorted/objects/RubikCube.java
@@ -158,6 +158,13 @@ class RubikCube extends RubikObject
     return QUATS;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return false;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   int getNumFaces()
diff --git a/src/main/java/org/distorted/objects/RubikDino.java b/src/main/java/org/distorted/objects/RubikDino.java
index 3c6fc078..14b5ef87 100644
--- a/src/main/java/org/distorted/objects/RubikDino.java
+++ b/src/main/java/org/distorted/objects/RubikDino.java
@@ -37,7 +37,6 @@ import org.distorted.library.mesh.MeshSquare;
 import org.distorted.library.type.Static1D;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
-import org.distorted.main.RubikSurfaceView;
 
 import java.util.Random;
 
@@ -45,7 +44,7 @@ import static org.distorted.effects.scramble.ScrambleEffect.START_AXIS;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikDino extends RubikObject
+public abstract class RubikDino extends RubikObject
 {
   private static final float SQ2 = (float)Math.sqrt(2);
   private static final float SQ3 = (float)Math.sqrt(3);
@@ -75,7 +74,7 @@ public class RubikDino extends RubikObject
          };
 
   // All legal rotation quats of a RubikDino
-  private static final Static4D[] QUATS = new Static4D[]
+  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 ),
@@ -108,18 +107,14 @@ public class RubikDino extends RubikObject
            new Static3D(-1.5f, 0.0f,-1.5f )
          };
 
-  private static final int[] mFaceMap = {4,2, 0,4, 4,3, 1,4,
-                                         2,0, 3,0, 3,1, 2,1,
-                                         5,2, 0,5, 5,3, 1,5 };
-
   private static MeshBase mMesh;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  RubikDino(int size, Static4D quat, DistortedTexture texture,
-            MeshSquare mesh, DistortedEffects effects, int[][] moves, Resources res, int scrWidth)
+  RubikDino(int size, Static4D quat, DistortedTexture texture, MeshSquare mesh,
+            DistortedEffects effects, int[][] moves, RubikObjectList obj, Resources res, int scrWidth)
     {
-    super(size, 60, quat, texture, mesh, effects, moves, RubikObjectList.DINO, res, scrWidth);
+    super(size, 60, quat, texture, mesh, effects, moves, obj, res, scrWidth);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -312,18 +307,6 @@ public class RubikDino extends RubikObject
     return mesh;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getFaceColor(int cubit, int cubitface, int size)
-    {
-    switch(cubitface)
-      {
-      case 0 : return mFaceMap[2*cubit];
-      case 1 : return mFaceMap[2*cubit+1];
-      default: return NUM_FACES;
-      }
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void createFaceTexture(Canvas canvas, Paint paint, int face, int left, int top, int side)
@@ -468,81 +451,6 @@ public class RubikDino extends RubikObject
       }
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// remember about the double cover or unit quaternions!
-
-  private int mulQuat(int q1, int q2)
-    {
-    Static4D result = RubikSurfaceView.quatMultiply(QUATS[q1],QUATS[q2]);
-
-    float rX = result.get0();
-    float rY = result.get1();
-    float rZ = result.get2();
-    float rW = result.get3();
-
-    final float MAX_ERROR = 0.1f;
-    float dX,dY,dZ,dW;
-
-    for(int i=0; i<QUATS.length; i++)
-      {
-      dX = QUATS[i].get0() - rX;
-      dY = QUATS[i].get1() - rY;
-      dZ = QUATS[i].get2() - rZ;
-      dW = QUATS[i].get3() - rW;
-
-      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
-          dY<MAX_ERROR && dY>-MAX_ERROR &&
-          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
-          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
-
-      dX = QUATS[i].get0() + rX;
-      dY = QUATS[i].get1() + rY;
-      dZ = QUATS[i].get2() + rZ;
-      dW = QUATS[i].get3() + rW;
-
-      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
-          dY<MAX_ERROR && dY>-MAX_ERROR &&
-          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
-          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Dino is solved if and only if:
-//
-// All four 'X' cubits (i.e. those whose longest edge goes along the X axis) are rotated
-// by the same quaternion qX, similarly all four 'Y' cubits by the same qY and all four 'Z'
-// by the same qZ, and then either:
-//
-// a) qX = qY = qZ
-// b) qY = qX*Q2 and qZ = qX*Q8  (i.e. swap of WHITE and YELLOW faces)
-// c) qX = qY*Q2 and qZ = qY*Q10 (i.e. swap of BLUE and GREEN faces)
-// d) qX = qZ*Q8 and qY = qZ*Q10 (i.e. swap of RED and BROWN faces)
-//
-// BUT: cases b), c) and d) are really the same - it's all just a mirror image of the original.
-//
-// X cubits: 0, 2, 8, 10
-// Y cubits: 1, 3, 9, 11
-// Z cubits: 4, 5, 6, 7
-
-  public boolean isSolved()
-    {
-    int qX = CUBITS[0].mQuatIndex;
-    int qY = CUBITS[1].mQuatIndex;
-    int qZ = CUBITS[4].mQuatIndex;
-
-    if( CUBITS[2].mQuatIndex != qX || CUBITS[8].mQuatIndex != qX || CUBITS[10].mQuatIndex != qX ||
-        CUBITS[3].mQuatIndex != qY || CUBITS[9].mQuatIndex != qY || CUBITS[11].mQuatIndex != qY ||
-        CUBITS[5].mQuatIndex != qZ || CUBITS[6].mQuatIndex != qZ || CUBITS[ 7].mQuatIndex != qZ  )
-      {
-      return false;
-      }
-
-    return ( qX==qY && qX==qZ ) || ( qY==mulQuat(qX,2) && qZ==mulQuat(qX,8) );
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // only needed for solvers - there are no Dino solvers ATM)
 
diff --git a/src/main/java/org/distorted/objects/RubikDino4.java b/src/main/java/org/distorted/objects/RubikDino4.java
new file mode 100644
index 00000000..67a0e974
--- /dev/null
+++ b/src/main/java/org/distorted/objects/RubikDino4.java
@@ -0,0 +1,85 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 android.content.res.Resources;
+
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshSquare;
+import org.distorted.library.type.Static4D;
+import org.distorted.main.RubikSurfaceView;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikDino4 extends RubikDino
+{
+  private static final int[] mFaceMap = {4,4, 2,2, 2,2, 4,4,
+                                         0,0, 2,2, 1,1, 4,4,
+                                         0,0, 0,0, 1,1, 1,1 };
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikDino4(int size, Static4D quat, DistortedTexture texture,
+             MeshSquare mesh, DistortedEffects effects, int[][] moves, Resources res, int scrWidth)
+    {
+    super(size, quat, texture, mesh, effects, moves, RubikObjectList.DIN4, res, scrWidth);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getFaceColor(int cubit, int cubitface, int size)
+    {
+    switch(cubitface)
+      {
+      case 0 : return mFaceMap[2*cubit];
+      case 1 : return mFaceMap[2*cubit+1];
+      default: return NUM_FACES;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Dino4 is solved if and only if the four groups of three same-colored cubits each are rotate with
+// the same quaternion (actually we need to check only 3 first groups - if those are correct, the
+// fourth one also needs to be correct).
+//
+// White group : 6,10,11
+// Red group   : 0,3,7
+// Blue group  : 1,2,5
+// Yellow group: 4,8,9
+
+  public boolean isSolved()
+    {
+    int qR = CUBITS[0].mQuatIndex;
+    int qB = CUBITS[1].mQuatIndex;
+    int qY = CUBITS[4].mQuatIndex;
+
+    return (CUBITS[3].mQuatIndex == qR && CUBITS[7].mQuatIndex == qR &&
+            CUBITS[2].mQuatIndex == qB && CUBITS[5].mQuatIndex == qB &&
+            CUBITS[8].mQuatIndex == qY && CUBITS[9].mQuatIndex == qY  );
+    }
+}
diff --git a/src/main/java/org/distorted/objects/RubikDino6.java b/src/main/java/org/distorted/objects/RubikDino6.java
new file mode 100644
index 00000000..9a6ad7a3
--- /dev/null
+++ b/src/main/java/org/distorted/objects/RubikDino6.java
@@ -0,0 +1,138 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 android.content.res.Resources;
+
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshSquare;
+import org.distorted.library.type.Static4D;
+import org.distorted.main.RubikSurfaceView;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikDino6 extends RubikDino
+{
+  private static final int[] mFaceMap = {4,2, 0,4, 4,3, 1,4,
+                                         2,0, 3,0, 3,1, 2,1,
+                                         5,2, 0,5, 5,3, 1,5 };
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikDino6(int size, Static4D quat, DistortedTexture texture,
+             MeshSquare mesh, DistortedEffects effects, int[][] moves, Resources res, int scrWidth)
+    {
+    super(size, quat, texture, mesh, effects, moves, RubikObjectList.DINO, res, scrWidth);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int mulQuat(int q1, int q2)
+    {
+    Static4D result = RubikSurfaceView.quatMultiply(QUATS[q1],QUATS[q2]);
+
+    float rX = result.get0();
+    float rY = result.get1();
+    float rZ = result.get2();
+    float rW = result.get3();
+
+    final float MAX_ERROR = 0.1f;
+    float dX,dY,dZ,dW;
+
+    for(int i=0; i<QUATS.length; i++)
+      {
+      dX = QUATS[i].get0() - rX;
+      dY = QUATS[i].get1() - rY;
+      dZ = QUATS[i].get2() - rZ;
+      dW = QUATS[i].get3() - rW;
+
+      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
+          dY<MAX_ERROR && dY>-MAX_ERROR &&
+          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
+          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
+
+      dX = QUATS[i].get0() + rX;
+      dY = QUATS[i].get1() + rY;
+      dZ = QUATS[i].get2() + rZ;
+      dW = QUATS[i].get3() + rW;
+
+      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
+          dY<MAX_ERROR && dY>-MAX_ERROR &&
+          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
+          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getFaceColor(int cubit, int cubitface, int size)
+    {
+    switch(cubitface)
+      {
+      case 0 : return mFaceMap[2*cubit];
+      case 1 : return mFaceMap[2*cubit+1];
+      default: return NUM_FACES;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Dino6 is solved if and only if:
+//
+// All four 'X' cubits (i.e. those whose longest edge goes along the X axis) are rotated
+// by the same quaternion qX, similarly all four 'Y' cubits by the same qY and all four 'Z'
+// by the same qZ, and then either:
+//
+// a) qX = qY = qZ
+// b) qY = qX*Q2 and qZ = qX*Q8  (i.e. swap of WHITE and YELLOW faces)
+// c) qX = qY*Q2 and qZ = qY*Q10 (i.e. swap of BLUE and GREEN faces)
+// d) qX = qZ*Q8 and qY = qZ*Q10 (i.e. swap of RED and BROWN faces)
+//
+// BUT: cases b), c) and d) are really the same - it's all just a mirror image of the original.
+//
+// X cubits: 0, 2, 8, 10
+// Y cubits: 1, 3, 9, 11
+// Z cubits: 4, 5, 6, 7
+
+  public boolean isSolved()
+    {
+    int qX = CUBITS[0].mQuatIndex;
+    int qY = CUBITS[1].mQuatIndex;
+    int qZ = CUBITS[4].mQuatIndex;
+
+    if( CUBITS[2].mQuatIndex != qX || CUBITS[8].mQuatIndex != qX || CUBITS[10].mQuatIndex != qX ||
+        CUBITS[3].mQuatIndex != qY || CUBITS[9].mQuatIndex != qY || CUBITS[11].mQuatIndex != qY ||
+        CUBITS[5].mQuatIndex != qZ || CUBITS[6].mQuatIndex != qZ || CUBITS[ 7].mQuatIndex != qZ  )
+      {
+      return false;
+      }
+
+    return ( qX==qY && qX==qZ ) || ( qY==mulQuat(qX,2) && qZ==mulQuat(qX,8) );
+    }
+}
diff --git a/src/main/java/org/distorted/objects/RubikHelicopter.java b/src/main/java/org/distorted/objects/RubikHelicopter.java
index 56a8784a..db902f56 100644
--- a/src/main/java/org/distorted/objects/RubikHelicopter.java
+++ b/src/main/java/org/distorted/objects/RubikHelicopter.java
@@ -484,6 +484,13 @@ public class RubikHelicopter extends RubikObject
     return QUATS;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return false;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   int getNumFaces()
diff --git a/src/main/java/org/distorted/objects/RubikObject.java b/src/main/java/org/distorted/objects/RubikObject.java
index 12eb5a6e..88b203e8 100644
--- a/src/main/java/org/distorted/objects/RubikObject.java
+++ b/src/main/java/org/distorted/objects/RubikObject.java
@@ -209,6 +209,8 @@ public abstract class RubikObject extends DistortedNode
         CUBITS[i] = new Cubit(this,mOrigPos[i]);
         mMesh.setEffectAssociation(i, CUBITS[i].computeAssociation(), 0);
         }
+
+      if( shouldResetTextureMaps() ) resetAllTextureMaps();
       }
     else
       {
@@ -694,6 +696,7 @@ public abstract class RubikObject extends DistortedNode
   abstract float returnMultiplier();
   abstract float[] getRowChances();
   abstract float getBasicStep();
+  abstract boolean shouldResetTextureMaps();
 
   public abstract boolean isSolved();
   public abstract Static3D[] getRotationAxis();
diff --git a/src/main/java/org/distorted/objects/RubikObjectList.java b/src/main/java/org/distorted/objects/RubikObjectList.java
index c806d64e..a7084695 100644
--- a/src/main/java/org/distorted/objects/RubikObjectList.java
+++ b/src/main/java/org/distorted/objects/RubikObjectList.java
@@ -61,7 +61,16 @@ public enum RubikObjectList
          new int[][] {
                        {3 , 10, R.raw.dino, R.drawable.ui_small_dino, R.drawable.ui_medium_dino, R.drawable.ui_big_dino, R.drawable.ui_huge_dino} ,
                      },
-         RubikDino.class,
+         RubikDino6.class,
+         new RubikMovementDino(),
+         2
+       ),
+
+  DIN4 (
+         new int[][] {
+                       {3 ,  7, R.raw.dino, R.drawable.ui_small_din4, R.drawable.ui_medium_din4, R.drawable.ui_big_din4, R.drawable.ui_huge_din4} ,
+                     },
+         RubikDino4.class,
          new RubikMovementDino(),
          2
        ),
@@ -457,9 +466,10 @@ public enum RubikObjectList
       {
       case 0: return new RubikCube      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 1: return new RubikPyraminx  (size, quat, texture, mesh, effects, moves, res, scrWidth);
-      case 2: return new RubikDino      (size, quat, texture, mesh, effects, moves, res, scrWidth);
-      case 3: return new RubikSkewb     (size, quat, texture, mesh, effects, moves, res, scrWidth);
-      case 4: return new RubikHelicopter(size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case 2: return new RubikDino6     (size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case 3: return new RubikDino4     (size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case 4: return new RubikSkewb     (size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case 5: return new RubikHelicopter(size, quat, texture, mesh, effects, moves, res, scrWidth);
       }
 
     return null;
diff --git a/src/main/java/org/distorted/objects/RubikPyraminx.java b/src/main/java/org/distorted/objects/RubikPyraminx.java
index f175972d..67f6a4dc 100644
--- a/src/main/java/org/distorted/objects/RubikPyraminx.java
+++ b/src/main/java/org/distorted/objects/RubikPyraminx.java
@@ -249,6 +249,13 @@ public class RubikPyraminx extends RubikObject
     return 0.82f;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return false;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   int getFaceColor(int cubit, int cubitface, int size)
diff --git a/src/main/java/org/distorted/objects/RubikSkewb.java b/src/main/java/org/distorted/objects/RubikSkewb.java
index ddf4958e..538b6054 100644
--- a/src/main/java/org/distorted/objects/RubikSkewb.java
+++ b/src/main/java/org/distorted/objects/RubikSkewb.java
@@ -397,6 +397,13 @@ public class RubikSkewb extends RubikObject
     return FACE_COLORS.length;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return false;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Each face has two types of a texture: the central square and the triangle in the corner.
 
diff --git a/src/main/java/org/distorted/states/RubikStatePlay.java b/src/main/java/org/distorted/states/RubikStatePlay.java
index 7a03cf93..e5ddd7b6 100644
--- a/src/main/java/org/distorted/states/RubikStatePlay.java
+++ b/src/main/java/org/distorted/states/RubikStatePlay.java
@@ -219,8 +219,12 @@ public class RubikStatePlay extends RubikStateAbstract implements RubikPreRender
         View popupView = mPlayPopup.getContentView();
         popupView.setSystemUiVisibility(RubikActivity.FLAGS);
 
+        final int sizeIndex = RubikObjectList.getSizeIndex(mObject,mSize);
+        final int maxLevel = RubikObjectList.getMaxLevel(mObject, sizeIndex);
+        final int levelsShown = Math.min(maxLevel,LEVELS_SHOWN);
+
         mPlayPopup.showAsDropDown(view, margin, margin, Gravity.RIGHT);
-        mPlayPopup.update(view, mPlayLayoutWidth, (int)(LEVELS_SHOWN*(mMenuItemSize+margin)+margin));
+        mPlayPopup.update(view, mPlayLayoutWidth, (int)(levelsShown*(mMenuItemSize+margin)+2*margin));
 
         mPlayPopup.setFocusable(true);
         mPlayPopup.update();
