commit 6b6504fef125a5d4302ac4c1af64007ec82744e9
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Wed Aug 26 13:55:54 2020 +0100

    Finally fix the Dino's 'isSolved()' - Dino can be mirrored and then it is also solved!

diff --git a/src/main/java/org/distorted/objects/RubikCube.java b/src/main/java/org/distorted/objects/RubikCube.java
index 7f1bc387..8d2e0976 100644
--- a/src/main/java/org/distorted/objects/RubikCube.java
+++ b/src/main/java/org/distorted/objects/RubikCube.java
@@ -394,6 +394,20 @@ class RubikCube extends RubikObject
     return 0;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean isSolved()
+    {
+    int index = CUBITS[0].mQuatIndex;
+
+    for(int i=1; i<NUM_CUBITS; i++)
+      {
+      if( !thereIsNoVisibleDifference(CUBITS[i], index) ) return false;
+      }
+
+    return true;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // return if the Cubit, when rotated with its own mQuatScramble, would have looked any different
 // then if it were rotated by quaternion 'quat'.
@@ -404,7 +418,7 @@ class RubikCube extends RubikObject
 // a) is a corner or edge and the quaternions are the same
 // b) is inside one of the faces and after rotations by both quats it ends up on the same face.
 
-  boolean thereIsNoVisibleDifference(Cubit cubit, int quatIndex)
+  private boolean thereIsNoVisibleDifference(Cubit cubit, int quatIndex)
     {
     if ( cubit.mQuatIndex == quatIndex ) return true;
 
diff --git a/src/main/java/org/distorted/objects/RubikDino.java b/src/main/java/org/distorted/objects/RubikDino.java
index e126e794..bffe2741 100644
--- a/src/main/java/org/distorted/objects/RubikDino.java
+++ b/src/main/java/org/distorted/objects/RubikDino.java
@@ -41,6 +41,7 @@ import org.distorted.library.mesh.MeshTriangles;
 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;
 
@@ -442,12 +443,78 @@ public class RubikDino extends RubikObject
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// here it's simple - all cubits have to be rotated with the same quaretnion for the whole thing
-// to be solved.
+// remember about the double cover or unit quaternions!
 
-  boolean thereIsNoVisibleDifference(Cubit cubit, int quatIndex)
+  private int mulQuat(int q1, int q2)
     {
-    return cubit.mQuatIndex == quatIndex;
+    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) );
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/RubikObject.java b/src/main/java/org/distorted/objects/RubikObject.java
index f12c6e25..c9c52089 100644
--- a/src/main/java/org/distorted/objects/RubikObject.java
+++ b/src/main/java/org/distorted/objects/RubikObject.java
@@ -68,20 +68,20 @@ public abstract class RubikObject extends DistortedNode
 
   final Static3D[] ROTATION_AXIS;
   final Static4D[] QUATS;
+  final Cubit[] CUBITS;
   final int NUM_FACES;
   final int NUM_CUBIT_FACES;
   final int NUM_AXIS;
+  final int NUM_CUBITS;
 
   private static float mInitScreenRatio,mObjectScreenRatio;
 
-  private final int NUM_CUBITS;
   private final int mNodeSize;
   private int mRotRowBitmap;
   private int mRotAxis;
   private Static3D[] mOrigPos;
   private Static3D mNodeScale;
   private Static4D mQuat;
-  private Cubit[] mCubits;
   private int mSize;
   private RubikObjectList mList;
   private MeshBase mMesh;
@@ -146,6 +146,7 @@ public abstract class RubikObject extends DistortedNode
     MatrixEffectScale nodeScaleEffect = new MatrixEffectScale(mNodeScale);
     nodeEffects.apply(nodeScaleEffect);
 
+    CUBITS = new Cubit[NUM_CUBITS];
     createMeshAndCubits(list,res);
 
     mTexture = new DistortedTexture();
@@ -174,8 +175,6 @@ public abstract class RubikObject extends DistortedNode
 
   private void createMeshAndCubits(RubikObjectList list, Resources res)
     {
-    mCubits = new Cubit[NUM_CUBITS];
-
     if( mCreateFromDMesh )
       {
       int sizeIndex = RubikObjectList.getSizeIndex(list.ordinal(),mSize);
@@ -196,8 +195,8 @@ public abstract class RubikObject extends DistortedNode
 
       for(int i=0; i<NUM_CUBITS; i++)
         {
-        mCubits[i] = new Cubit(this,mOrigPos[i]);
-        mMesh.setEffectAssociation(i, mCubits[i].computeAssociation(), 0);
+        CUBITS[i] = new Cubit(this,mOrigPos[i]);
+        mMesh.setEffectAssociation(i, CUBITS[i].computeAssociation(), 0);
         }
       }
     else
@@ -206,10 +205,10 @@ public abstract class RubikObject extends DistortedNode
 
       for(int i=0; i<NUM_CUBITS; i++)
         {
-        mCubits[i] = new Cubit(this,mOrigPos[i]);
+        CUBITS[i] = new Cubit(this,mOrigPos[i]);
         cubitMesh[i] = createCubitMesh(i);
         cubitMesh[i].apply(new MatrixEffectMove(mOrigPos[i]),1,0);
-        cubitMesh[i].setEffectAssociation(0, mCubits[i].computeAssociation(), 0);
+        cubitMesh[i].setEffectAssociation(0, CUBITS[i].computeAssociation(), 0);
         }
 
       mMesh = new MeshJoined(cubitMesh);
@@ -276,7 +275,7 @@ public abstract class RubikObject extends DistortedNode
 
   private boolean belongsToRotation( int cubit, int axis, int rowBitmap)
     {
-    int cubitRow = (int)(mCubits[cubit].mRotationRow[axis]+0.5f);
+    int cubitRow = (int)(CUBITS[cubit].mRotationRow[axis]+0.5f);
     return ((1<<cubitRow)&rowBitmap)!=0;
     }
 
@@ -317,8 +316,8 @@ public abstract class RubikObject extends DistortedNode
         for(int j=0; j<NUM_CUBITS; j++)
           if( belongsToRotation(j,axis,rowBitmap) )
             {
-            index = mCubits[j].removeRotationNow(quat);
-            mMesh.setEffectAssociation(j,mCubits[j].computeAssociation(),index);
+            index = CUBITS[j].removeRotationNow(quat);
+            mMesh.setEffectAssociation(j, CUBITS[j].computeAssociation(),index);
             }
         }
       }
@@ -332,7 +331,7 @@ public abstract class RubikObject extends DistortedNode
   boolean isOnFace( int cubit, int axis, int row)
     {
     final float MAX_ERROR = 0.0001f;
-    float diff = mCubits[cubit].mRotationRow[axis] - row;
+    float diff = CUBITS[cubit].mRotationRow[axis] - row;
     return diff*diff < MAX_ERROR;
     }
 
@@ -433,7 +432,7 @@ public abstract class RubikObject extends DistortedNode
 
   public void savePreferences(SharedPreferences.Editor editor)
     {
-    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].savePreferences(editor);
+    for(int i=0; i<NUM_CUBITS; i++) CUBITS[i].savePreferences(editor);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -442,8 +441,8 @@ public abstract class RubikObject extends DistortedNode
     {
     for(int i=0; i<NUM_CUBITS; i++)
       {
-      int index = mCubits[i].restorePreferences(preferences);
-      mMesh.setEffectAssociation(i,mCubits[i].computeAssociation(),index);
+      int index = CUBITS[i].restorePreferences(preferences);
+      mMesh.setEffectAssociation(i, CUBITS[i].computeAssociation(),index);
       }
     }
 
@@ -474,23 +473,9 @@ public abstract class RubikObject extends DistortedNode
     {
     for(int i=0; i<NUM_CUBITS; i++)
       {
-      mCubits[i].solve();
-      mMesh.setEffectAssociation(i, mCubits[i].computeAssociation(), 0);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean isSolved()
-    {
-    int index = mCubits[0].mQuatIndex;
-
-    for(int i=1; i<NUM_CUBITS; i++)
-      {
-      if( !thereIsNoVisibleDifference(mCubits[i], index) ) return false;
+      CUBITS[i].solve();
+      mMesh.setEffectAssociation(i, CUBITS[i].computeAssociation(), 0);
       }
-
-    return true;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -625,8 +610,8 @@ public abstract class RubikObject extends DistortedNode
     for(int i=0; i<NUM_CUBITS; i++)
       if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
         {
-        int index = mCubits[i].removeRotationNow(quat);
-        mMesh.setEffectAssociation(i,mCubits[i].computeAssociation(),index);
+        int index = CUBITS[i].removeRotationNow(quat);
+        mMesh.setEffectAssociation(i, CUBITS[i].computeAssociation(),index);
         }
     }
 
@@ -652,7 +637,7 @@ public abstract class RubikObject extends DistortedNode
 
     for(int i=0; i<NUM_CUBITS; i++)
       {
-      dist = mCubits[i].getDistSquared(point3D);
+      dist = CUBITS[i].getDistSquared(point3D);
       if( dist<minDist )
         {
         minDist = dist;
@@ -703,8 +688,8 @@ public abstract class RubikObject extends DistortedNode
   abstract int getFaceColor(int cubit, int cubitface, int size);
   abstract float returnMultiplier();
   abstract float[] getRowChances();
-  abstract boolean thereIsNoVisibleDifference(Cubit cubit, int quatIndex);
 
+  public abstract boolean isSolved();
   public abstract Static3D[] getRotationAxis();
   public abstract int getBasicAngle();
   public abstract int computeRowFromOffset(float offset);
diff --git a/src/main/java/org/distorted/objects/RubikPyraminx.java b/src/main/java/org/distorted/objects/RubikPyraminx.java
index de25f5e6..d28c9e20 100644
--- a/src/main/java/org/distorted/objects/RubikPyraminx.java
+++ b/src/main/java/org/distorted/objects/RubikPyraminx.java
@@ -486,6 +486,20 @@ public class RubikPyraminx extends RubikObject
     return 0;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean isSolved()
+    {
+    int index = CUBITS[0].mQuatIndex;
+
+    for(int i=1; i<NUM_CUBITS; i++)
+      {
+      if( !thereIsNoVisibleDifference(CUBITS[i], index) ) return false;
+      }
+
+    return true;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // return if the Cubit, when rotated with its own mQuatScramble, would have looked any different
 // then if it were rotated by quaternion 'quat'.
@@ -496,7 +510,7 @@ public class RubikPyraminx extends RubikObject
 // a) is a corner or edge and the quaternions are the same
 // b) is inside one of the faces and after rotations by both quats it ends up on the same face.
 
-  boolean thereIsNoVisibleDifference(Cubit cubit, int quatIndex)
+  private boolean thereIsNoVisibleDifference(Cubit cubit, int quatIndex)
     {
     if ( cubit.mQuatIndex == quatIndex ) return true;
 
