commit 168b6b5651d52b17b9b66bda7e970d11ac6372b9
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Wed Jun 17 16:45:53 2020 +0100

    Improvements for the way we rotate layers of Objects. (take the speed into account - so even if we rotated a layer of a Cube to less than 45 degrees, but we did it fast, do a 90 degree rotation!)

diff --git a/src/main/java/org/distorted/main/RubikPreRender.java b/src/main/java/org/distorted/main/RubikPreRender.java
index 10bf4df9..9341702f 100644
--- a/src/main/java/org/distorted/main/RubikPreRender.java
+++ b/src/main/java/org/distorted/main/RubikPreRender.java
@@ -65,6 +65,7 @@ public class RubikPreRender implements EffectListener
   private ActionFinishedListener mAddActionListener;
   private long mAddRotationID, mRemoveRotationID;
   private int mCubit, mFace, mNewColor;
+  private int mNearestAngle;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -224,7 +225,7 @@ public class RubikPreRender implements EffectListener
     mFinishRotation = false;
     mCanRotate      = false;
     mCanPlay        = false;
-    mRotationFinishedID = mNewObject.finishRotationNow(this);
+    mRotationFinishedID = mNewObject.finishRotationNow(this, mNearestAngle);
 
     if( mRotationFinishedID==0 ) // failed to add effect - should never happen
       {
@@ -366,8 +367,9 @@ public class RubikPreRender implements EffectListener
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void finishRotation()
+  void finishRotation(int nearestAngle)
     {
+    mNearestAngle   = nearestAngle;
     mFinishRotation = true;
     }
 
diff --git a/src/main/java/org/distorted/main/RubikSurfaceView.java b/src/main/java/org/distorted/main/RubikSurfaceView.java
index 984ebcc6..84b5a446 100644
--- a/src/main/java/org/distorted/main/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/main/RubikSurfaceView.java
@@ -40,6 +40,8 @@ import org.distorted.states.RubikStateSolving;
 
 public class RubikSurfaceView extends GLSurfaceView
 {
+    private static final int NUM_SPEED_PROBES = 10;
+
     public static final int MODE_ROTATE  = 0;
     public static final int MODE_DRAG    = 1;
     public static final int MODE_REPLACE = 2;
@@ -75,7 +77,10 @@ public class RubikSurfaceView extends GLSurfaceView
     private float mRotationFactor;
     private int mLastCubitColor, mLastCubitFace, mLastCubit;
     private int mCurrentAxis, mCurrentRow;
-    private float mCurrentAngle;
+    private float mCurrentAngle, mCurrRotSpeed;
+    private float[] mLastAngles;
+    private long[] mLastTimestamps;
+    private int mFirstIndex, mLastIndex;
 
     private static Static4D mQuatCurrent    = new Static4D(0,0,0,1);
     private static Static4D mQuatAccumulated= new Static4D(-0.25189602f,0.3546389f,0.009657208f,0.90038127f);
@@ -315,6 +320,49 @@ public class RubikSurfaceView extends GLSurfaceView
       return quatMultiply(tmp,quat);
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void addSpeedProbe(float angle)
+      {
+      long currTime = System.currentTimeMillis();
+      boolean theSame = mLastIndex==mFirstIndex;
+
+      mLastIndex++;
+      if( mLastIndex>=NUM_SPEED_PROBES ) mLastIndex=0;
+
+      mLastTimestamps[mLastIndex] = currTime;
+      mLastAngles[mLastIndex] = angle;
+
+      if( mLastIndex==mFirstIndex)
+        {
+        mFirstIndex++;
+        if( mFirstIndex>=NUM_SPEED_PROBES ) mFirstIndex=0;
+        }
+
+      if( theSame )
+        {
+        mLastTimestamps[mFirstIndex] = currTime;
+        mLastAngles[mFirstIndex] = angle;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void computeCurrentSpeed()
+      {
+      long firstTime = mLastTimestamps[mFirstIndex];
+      long lastTime  = mLastTimestamps[mLastIndex];
+      float firstAngle = mLastAngles[mFirstIndex];
+      float lastAngle  = mLastAngles[mLastIndex];
+
+      long timeDiff = lastTime-firstTime;
+
+      mLastIndex = 0;
+      mFirstIndex= 0;
+
+      mCurrRotSpeed = timeDiff>0 ? (lastAngle-firstAngle)/timeDiff : 0;
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -326,6 +374,12 @@ public class RubikSurfaceView extends GLSurfaceView
       if(!isInEditMode())
         {
         mLastCubitColor = -1;
+        mCurrRotSpeed   = 0.0f;
+
+        mLastAngles = new float[NUM_SPEED_PROBES];
+        mLastTimestamps = new long[NUM_SPEED_PROBES];
+        mFirstIndex =0;
+        mLastIndex  =0;
 
         mRenderer  = new RubikRenderer(this);
         mPreRender = new RubikPreRender(this);
@@ -390,6 +444,8 @@ public class RubikSurfaceView extends GLSurfaceView
                                                });
                                              }
 
+                                           addSpeedProbe(0.0f);
+
                                            mBeginningRotation = false;
                                            mContinuingRotation= true;
                                            }
@@ -399,6 +455,8 @@ public class RubikSurfaceView extends GLSurfaceView
                                          float angle = continueRotation(x-mStartRotX,y-mStartRotY);
                                          mCurrentAngle = SWIPING_SENSITIVITY*angle;
                                          mPreRender.getObject().continueRotation(mCurrentAngle);
+
+                                         addSpeedProbe(mCurrentAngle);
                                          }
                                        else if( mDragging )
                                          {
@@ -430,14 +488,14 @@ public class RubikSurfaceView extends GLSurfaceView
 
                                        if( mContinuingRotation )
                                          {
-                                         mPreRender.finishRotation();
+                                         computeCurrentSpeed();
+                                         int angle = mPreRender.getObject().computeNearestAngle(mCurrentAngle, mCurrRotSpeed);
+                                         mPreRender.finishRotation(angle);
 
                                          if( RubikState.getCurrentState()==RubikState.SOLV )
                                            {
                                            RubikStateSolving solving = (RubikStateSolving)RubikState.SOLV.getStateClass();
 
-                                           int angle = mPreRender.getObject().computeNearestAngle(mCurrentAngle);
-
                                            if( angle!=0 )
                                              solving.addMove(mCurrentAxis, mCurrentRow, angle);
                                            }
diff --git a/src/main/java/org/distorted/objects/RubikObject.java b/src/main/java/org/distorted/objects/RubikObject.java
index 9b46d935..54c7aeee 100644
--- a/src/main/java/org/distorted/objects/RubikObject.java
+++ b/src/main/java/org/distorted/objects/RubikObject.java
@@ -520,10 +520,9 @@ public abstract class RubikObject extends DistortedNode
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public long finishRotationNow(EffectListener listener)
+  public long finishRotationNow(EffectListener listener, int nearestAngleInDegrees)
     {
     float angle = getAngle();
-    int nearestAngleInDegrees = computeNearestAngle(angle);
     mRotationAngleStatic.set0(angle);
     mRotationAngleFinal.set0(nearestAngleInDegrees);
     mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-angle)*0.2f );
@@ -561,27 +560,26 @@ public abstract class RubikObject extends DistortedNode
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void removeRotationNow()
-     {
-     float angle = getAngle();
-     int nearestAngleInDegrees = computeNearestAngle(angle);
-     double nearestAngleInRadians = nearestAngleInDegrees*Math.PI/180;
-     float sinA =-(float)Math.sin(nearestAngleInRadians*0.5);
-     float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
-     float axisX = ROTATION_AXIS[mRotAxis].get0();
-     float axisY = ROTATION_AXIS[mRotAxis].get1();
-     float axisZ = ROTATION_AXIS[mRotAxis].get2();
-     Static4D quat = new Static4D( axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
-
-     mRotationAngle.removeAll();
-     mRotationAngleStatic.set0(0);
-
-     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);
-         }
-     }
+    {
+    float angle = getAngle();
+    double nearestAngleInRadians = angle*Math.PI/180;
+    float sinA =-(float)Math.sin(nearestAngleInRadians*0.5);
+    float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
+    float axisX = ROTATION_AXIS[mRotAxis].get0();
+    float axisY = ROTATION_AXIS[mRotAxis].get1();
+    float axisZ = ROTATION_AXIS[mRotAxis].get2();
+    Static4D quat = new Static4D( axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
+
+    mRotationAngle.removeAll();
+    mRotationAngleStatic.set0(0);
+
+    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);
+        }
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -618,14 +616,19 @@ public abstract class RubikObject extends DistortedNode
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public int computeNearestAngle(float angle)
+  public int computeNearestAngle(float angle, float speed)
     {
     final int NEAREST = 360/getBasicAngle();
 
-    int tmp = (int)((angle+NEAREST/2)/NEAREST);
-    if( angle< -(NEAREST*0.5) ) tmp-=1;
+    float angleAndSpeed = angle + 100*speed;
+
+    int tmp1 = (int)((angle+NEAREST/2)/NEAREST);
+    if( angle< -(NEAREST*0.5) ) tmp1-=1;
+
+    int tmp2 = (int)((angleAndSpeed+NEAREST/2)/NEAREST);
+    if( angleAndSpeed< -(NEAREST*0.5) ) tmp2-=1;
 
-    return NEAREST*tmp;
+    return NEAREST*(tmp1==0 ? tmp2 : tmp1);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
