commit 542ec777365cef23d8f4dda3bbd5d50fa6817e73
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Tue Jun 8 09:10:53 2021 +0200

    Square-1: looks to be finished.

diff --git a/src/main/java/org/distorted/effects/scramble/ScrambleEffect.java b/src/main/java/org/distorted/effects/scramble/ScrambleEffect.java
index 08b21cb5..bbae9d96 100644
--- a/src/main/java/org/distorted/effects/scramble/ScrambleEffect.java
+++ b/src/main/java/org/distorted/effects/scramble/ScrambleEffect.java
@@ -67,8 +67,8 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 
   private EffectController mController;
   private int mEffectReturned;
-  private int mNumDoubleScramblesLeft, mNumScramblesLeft;
-  private long mDurationSingleTurn;
+  private int mNumScramblesLeft;
+  private long mDurationPerDegree;
   private final Random mRnd;
   private int[] mBasicAngle;
   private boolean mRotReady, mPluginReady;
@@ -96,28 +96,23 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
   abstract void effectFinishedPlugin(final long effectID);
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// first compute how many out of 'numScrambles' are double turns (this will matter when we compute
-// the time a single quarter-turn takes!
-//
-// Only works for
-// basicAngle==5, i.e. something whose rotations are by  72 degrees (Megaminx) or
-// basicAngle==4, i.e. something whose rotations are by  90 degrees (RubikCube) or
-// basicAngle==3, i.e. something whose rotations are by 120 degrees (Pyramix) or
-// basicAngle==2, i.e. something whose rotations are by 180 degrees (e.g. a 3x2x1 'Bunny')
 
   private void createBaseEffects(int duration, int numScrambles)
     {
     mNumScramblesLeft = numScrambles;
-    mNumDoubleScramblesLeft=0;
+    int absAngle, angle, axis, basicDegrees, totalDegrees = 0;
 
     for(int scramble=0; scramble<mNumScramblesLeft; scramble++)
       {
       mObject.randomizeNewScramble(mScrambles, mRnd, scramble);
-      int angle = mScrambles[scramble][2];
-      if( angle==2 || angle==-2 ) mNumDoubleScramblesLeft++;
+      axis  = mScrambles[scramble][0];
+      angle = mScrambles[scramble][2];
+      absAngle = (angle<0 ? -angle : angle);
+      basicDegrees = 360/mBasicAngle[axis];
+      totalDegrees += absAngle*basicDegrees;
       }
 
-    mDurationSingleTurn = duration/(mNumScramblesLeft+mNumDoubleScramblesLeft);
+    mDurationPerDegree = duration/totalDegrees;
     mNumScrambles = 0;
 
     mRotReady    = false;
@@ -127,7 +122,6 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// only works if basicAngle<=5
 
   private void addNewScramble()
     {
@@ -137,29 +131,19 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
       int row  = mScrambles[mNumScrambles][1];
       int angle= mScrambles[mNumScrambles][2];
 
-      int rowBitmap  = (1<<row);
+      int rowBitmap= (1<<row);
       int absAngle = (angle<0 ? -angle : angle);
-      long durationMillis = absAngle*mDurationSingleTurn;
+      int basicDegrees  = 360/mBasicAngle[axis];
+      long durationMillis = absAngle*basicDegrees*mDurationPerDegree;
 
       mNumScramblesLeft--;
-      if( absAngle==2 ) mNumDoubleScramblesLeft--;
-
-      if( mNumScramblesLeft==0 && mNumDoubleScramblesLeft!=0 )
-        {
-        android.util.Log.e("effect", "ERROR: "+mNumDoubleScramblesLeft);
-        }
-
-      mController.addRotation(this, axis, rowBitmap, angle*(360/mBasicAngle[axis]), durationMillis);
-
+      mController.addRotation(this, axis, rowBitmap, angle*basicDegrees, durationMillis);
       mNumScrambles++;
       }
     else
       {
-      if( mEffectReturned == mCubeEffectNumber+mNodeEffectNumber )
-        {
-        mRotReady = true;
-        if( mPluginReady ) mController.effectFinished(FAKE_EFFECT_ID);
-        }
+      mRotReady = true;
+      if( mPluginReady ) mController.effectFinished(FAKE_EFFECT_ID);
       }
     }
 
diff --git a/src/main/java/org/distorted/objects/TwistyObject.java b/src/main/java/org/distorted/objects/TwistyObject.java
index a3e406bb..5d43c69a 100644
--- a/src/main/java/org/distorted/objects/TwistyObject.java
+++ b/src/main/java/org/distorted/objects/TwistyObject.java
@@ -888,6 +888,28 @@ public abstract class TwistyObject extends DistortedNode
         int index = CUBITS[i].removeRotationNow(quat);
         mMesh.setEffectAssociation(i, CUBITS[i].computeAssociation(),index);
         }
+/*
+    if( NUM_CUBITS==18 )
+      {
+      boolean error=false;
+      String str="";
+
+      for(int i=10; i<18; i++)
+        {
+        int q = CUBITS[i].mQuatIndex;
+
+        str += (" "+q);
+
+        if( (((i%2)==0) && (q==2 || q== 8 || q==17 || q==23)) ||
+            (((i%2)==1) && (q==5 || q==11 || q==14 || q==20))  )
+           error=true;
+        }
+
+      if( error ) str+= " ERROR";
+      android.util.Log.e("D", "cubit: "+str);
+      }
+
+ */
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/TwistySquare1.java b/src/main/java/org/distorted/objects/TwistySquare1.java
index 45fbedad..e74e36fd 100644
--- a/src/main/java/org/distorted/objects/TwistySquare1.java
+++ b/src/main/java/org/distorted/objects/TwistySquare1.java
@@ -40,6 +40,11 @@ import java.util.Random;
 
 class TwistySquare1 extends TwistyObject
 {
+  private static final int LAST_SL = 0; // automatic rotations: last rot was a 'slash' i.e. along ROT_AXIS[1]
+  private static final int LAST_UP = 1; // last rot was along ROT_AXIS[0], upper layer and forelast was a slash
+  private static final int LAST_LO = 2; // last rot was along ROT_AXIS[0], lower layer and forelast was a slash
+  private static final int LAST_UL = 3; // two last rots were along ROT_AXIS[0] (so the next must be a slash)
+
   private static final float COS15 = (SQ6+SQ2)/4;
   private static final float SIN15 = (SQ6-SQ2)/4;
   private static final float     X = 3*(2-SQ3)/2;
@@ -232,15 +237,9 @@ class TwistySquare1 extends TwistyObject
     {
       { 2, 8,17,23},
       { 5,11,14,20},
-      { 8, 2,23,17},
-      {11, 5,20,14},
-      {17,23, 2, 8},
-      {14,20, 5,11},
-      {23,17, 8, 2},
-      {20,14,11, 5}
     };
 
-  // QUAT[i]*QUAT[j] = QUAT_MULT[i][j]
+  // QUATS[i]*QUATS[j] = QUATS[QUAT_MULT[i][j]]
   private static final int[][] QUAT_MULT = new int[][]
     {
       {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,},
@@ -271,12 +270,20 @@ class TwistySquare1 extends TwistyObject
 
   private static MeshBase[] mMeshes;
 
+  private final int[][] mPermittedAngles;
+  private final int[] mCornerQuat;
+  private int mLastRot, mPermittedUp, mPermittedDo;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   TwistySquare1(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.SQU1, res, scrWidth);
+
+    mLastRot = LAST_SL;
+    mPermittedAngles = new int[2][BASIC_ANGLE[0]];
+    mCornerQuat = new int[8];
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -503,6 +510,202 @@ class TwistySquare1 extends TwistyObject
     return rowBitmap;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cornerIsUp(int index)
+    {
+    return ((index<4) ^ (mCornerQuat[index]>=12));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cornerIsLeft(int index)
+    {
+    int q = mCornerQuat[index];
+
+    switch(index)
+      {
+      case 0:
+      case 4: return ((q>=3 && q<= 7) || (q>=18 && q<=22));
+      case 1:
+      case 5: return ((q>=6 && q<=10) || (q>=15 && q<=19));
+      case 2:
+      case 6: return ((q==0 || q==1 || (q>=9 && q<=11)) || (q>=12 && q<=16));
+      case 3:
+      case 7: return ((q>=0 && q<=4) || (q==12 || q==13 || (q>=21 && q<=23)));
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean quatIsBad(int quatIndex, int corner)
+    {
+    int index = (corner%2);
+
+    return ( quatIndex==BAD_CORNER_QUATS[index][0] ||
+             quatIndex==BAD_CORNER_QUATS[index][1] ||
+             quatIndex==BAD_CORNER_QUATS[index][2] ||
+             quatIndex==BAD_CORNER_QUATS[index][3]  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean isPermittedDo(int angle)
+    {
+    for(int corner=0; corner<8; corner++)
+      {
+      if( !cornerIsUp(corner) )
+        {
+        int currQuat = mCornerQuat[corner];
+        int finalQuat= QUAT_MULT[angle][currQuat];
+        if( quatIsBad(finalQuat,corner) ) return false;
+        }
+      }
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean isPermittedUp(int angle)
+    {
+    for(int corner=0; corner<8; corner++)
+      {
+//android.util.Log.e("D", "isPermittedUp: up corner: "+corner+" angle: "+angle);
+
+
+      if( cornerIsUp(corner) )
+        {
+        int currQuat = mCornerQuat[corner];
+        int finalQuat= QUAT_MULT[angle][currQuat];
+
+//boolean bad = quatIsBad(finalQuat,corner);
+//android.util.Log.e("D", "isPermittedUp: up corner: "+corner+" angle: "+angle+" is bad: "+bad);
+
+        if( quatIsBad(finalQuat,corner) ) return false;
+        }
+      }
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computePermittedAngles()
+    {
+    mPermittedDo = 0;
+
+    for(int angle=0; angle<BASIC_ANGLE[0]; angle++)
+      {
+      if( isPermittedDo(angle ) ) mPermittedAngles[0][mPermittedDo++] = angle;
+      }
+
+    mPermittedUp = 0;
+
+    for(int angle=0; angle<BASIC_ANGLE[0]; angle++)
+      {
+      if( isPermittedUp(angle ) ) mPermittedAngles[1][mPermittedUp++] = angle;
+      }
+/*
+    String strDo="";
+
+    for(int i=0; i<mPermittedDo; i++)
+      {
+      strDo += (" "+mPermittedAngles[0][i]);
+      }
+
+    String strUp="";
+
+    for(int i=0; i<mPermittedUp; i++)
+      {
+      strUp += (" "+mPermittedAngles[1][i]);
+      }
+
+    android.util.Log.e("D", "up  : "+strUp);
+    android.util.Log.e("D", "down: "+strDo);
+
+ */
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getNextAngle(Random rnd, int layer)
+    {
+    int basic = BASIC_ANGLE[0];
+    int num = layer==0 ? mPermittedDo:mPermittedUp;
+    int index = rnd.nextInt(num);
+    int angle = mPermittedAngles[layer][index];
+    return angle<basic/2 ? -angle : basic-angle;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getNextAngleNotZero(Random rnd, int layer)
+    {
+    int basic = BASIC_ANGLE[0];
+    int num = layer==0 ? mPermittedDo:mPermittedUp;
+    int index = rnd.nextInt(num-1);
+    int angle = mPermittedAngles[layer][index+1];
+    return angle<basic/2 ? -angle : basic-angle;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int makeQuat(int axis,int index)
+    {
+    if( axis==1 ) return 13;
+    if( index<0 ) index+=12;
+    return index;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cornerBelongs(int index, int axis, int layer)
+    {
+    if( axis==0 )
+      {
+      boolean up = cornerIsUp(index);
+      return ((up && layer==2) || (!up && layer==0));
+      }
+    else
+      {
+      boolean le = cornerIsLeft(index);
+      return ((le && layer==0) || (!le && layer==1));
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void updateCornerQuats(int[] rotInfo)
+    {
+    int axis = rotInfo[0];
+    int layer= rotInfo[1];
+    int index=-rotInfo[2];
+
+    int quat = makeQuat(axis,index);
+
+    for(int corner=0; corner<8; corner++)
+      {
+      if( cornerBelongs(corner,axis,layer) )
+        {
+        int curr = mCornerQuat[corner];
+        mCornerQuat[corner] = QUAT_MULT[quat][curr];
+        }
+      }
+/*
+    String q="";
+
+    for(int c=0; c<8; c++)
+      {
+      q += (" "+mCornerQuat[c]);
+      }
+
+    android.util.Log.d("D", "quat="+quat+" corner quats now= "+q);
+*/
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 
@@ -519,28 +722,72 @@ class TwistySquare1 extends TwistyObject
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
 
   public void randomizeNewScramble(int[][] scramble, Random rnd, int num)
     {
+    int layer, nextAngle;
+
     if( num==0 )
       {
-      scramble[num][0] = rnd.nextInt(NUM_AXIS);
+      for(int corner=0; corner<8; corner++) mCornerQuat[corner] = 0;
+      mLastRot = rnd.nextInt(4);
+      computePermittedAngles();
       }
-    else
-      {
-      int newVector = rnd.nextInt(NUM_AXIS-1);
-      scramble[num][0] = (newVector>=scramble[num-1][0] ? newVector+1 : newVector);
-      }
-
-    scramble[num][1] = rnd.nextFloat()<=0.5f ? 0 : 1;
 
-    switch( rnd.nextInt(4) )
+    switch(mLastRot)
       {
-      case 0: scramble[num][2] = -2; break;
-      case 1: scramble[num][2] = -1; break;
-      case 2: scramble[num][2] =  1; break;
-      case 3: scramble[num][2] =  2; break;
+      case LAST_SL: layer = rnd.nextInt(2);
+                    nextAngle = getNextAngle(rnd,layer);
+
+                    if( nextAngle==0 )
+                      {
+                      layer = 1-layer;
+                      nextAngle = getNextAngleNotZero(rnd,layer);
+                      }
+
+                    scramble[num][0] = 0;
+                    scramble[num][1] = 2*layer;
+                    scramble[num][2] = nextAngle;
+                    mLastRot = layer==0 ? LAST_LO : LAST_UP;
+                    updateCornerQuats(scramble[num]);
+
+//android.util.Log.e("D", "SL axis="+scramble[num][0]+" layer="+scramble[num][1]+" angle="+scramble[num][2]);
+
+                    break;
+      case LAST_LO:
+      case LAST_UP: layer = mLastRot==LAST_LO ? 1:0;
+                    nextAngle = getNextAngle(rnd,layer);
+
+                    if( nextAngle!=0 )
+                      {
+                      scramble[num][0] = 0;
+                      scramble[num][1] = 2*layer;
+                      scramble[num][2] = nextAngle;
+                      updateCornerQuats(scramble[num]);
+                      mLastRot = LAST_UL;
+//android.util.Log.e("D", "LO/UP 1 axis="+scramble[num][0]+" layer="+scramble[num][1]+" angle="+scramble[num][2]);
+                      }
+                    else
+                      {
+                      scramble[num][0] = 1;
+                      scramble[num][1] = rnd.nextInt(2);
+                      scramble[num][2] = 1;
+                      mLastRot = LAST_SL;
+                      updateCornerQuats(scramble[num]);
+                      computePermittedAngles();
+//android.util.Log.e("D", "LO/UP 2 axis="+scramble[num][0]+" layer="+scramble[num][1]+" angle="+scramble[num][2]);
+                      }
+
+                    break;
+      case LAST_UL: scramble[num][0] = 1;
+                    scramble[num][1] = rnd.nextInt(2);
+                    scramble[num][2] = 1;
+                    mLastRot = LAST_SL;
+                    updateCornerQuats(scramble[num]);
+                    computePermittedAngles();
+//android.util.Log.e("D", "UL axis="+scramble[num][0]+" layer="+scramble[num][1]+" angle="+scramble[num][2]);
+
+                    break;
       }
     }
 
