commit ebb64a1d5c6b0cac07deb0bb337c27293a7500e6
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Jan 23 15:27:05 2020 +0000

    RubikCube: add skeleton WinEffects (for now only one effect - 'Spin' copied from Solve)

diff --git a/src/main/java/org/distorted/effect/BaseEffect.java b/src/main/java/org/distorted/effect/BaseEffect.java
index c6bf7fc2..793768cd 100644
--- a/src/main/java/org/distorted/effect/BaseEffect.java
+++ b/src/main/java/org/distorted/effect/BaseEffect.java
@@ -25,6 +25,7 @@ import android.content.SharedPreferences;
 import org.distorted.effect.scramble.ScrambleEffect;
 import org.distorted.effect.sizechange.SizeChangeEffect;
 import org.distorted.effect.solve.SolveEffect;
+import org.distorted.effect.win.WinEffect;
 import org.distorted.magic.R;
 import org.distorted.magic.RubikRenderer;
 
@@ -37,14 +38,15 @@ public class BaseEffect
     SIZECHANGE  ( 20, 1, R.string.sizechange_effect , SizeChangeEffect.class),
     SOLVE       ( 20, 1, R.string.solve_effect      , SolveEffect.class     ),
     SCRAMBLE    ( 20, 1, R.string.scramble_effect   , ScrambleEffect.class  ),
+    WIN         ( 20, 1, R.string.win_effect        , WinEffect.class       ),
     ;
 
     private final int mDefaultPos, mDefaultType;
-    private final Class mClass;
+    private final Class<? extends BaseEffect> mClass;
     private int mCurrentPos, mCurrentType;
     private int mText;
 
-    Type(int dPos, int dType, int text, Class clazz )
+    Type(int dPos, int dType, int text, Class<? extends BaseEffect> clazz )
       {
       mDefaultPos  = mCurrentPos = dPos;
       mDefaultType = mCurrentType= dType;
@@ -256,4 +258,6 @@ public class BaseEffect
       return (pos/2)*100;
       }
     }
+
+  // END ENUM ////////////////////////////////////////////////////////////////////
   }
diff --git a/src/main/java/org/distorted/effect/win/WinEffect.java b/src/main/java/org/distorted/effect/win/WinEffect.java
new file mode 100644
index 00000000..7d7d149f
--- /dev/null
+++ b/src/main/java/org/distorted/effect/win/WinEffect.java
@@ -0,0 +1,237 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted 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.                                                           //
+//                                                                                               //
+// Distorted 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 Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.win;
+
+import org.distorted.effect.BaseEffect;
+import org.distorted.library.effect.Effect;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.library.message.EffectListener;
+import org.distorted.magic.RubikCube;
+import org.distorted.magic.RubikRenderer;
+
+import java.lang.reflect.Method;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class WinEffect extends BaseEffect implements EffectListener
+{
+  public enum Type
+    {
+    NONE   (WinEffectNone.class),
+    SPIN   (WinEffectSpin.class),
+    ;
+
+    final Class<? extends WinEffect> effect;
+
+    Type(Class<? extends WinEffect> effect)
+      {
+      this.effect = effect;
+      }
+    }
+
+  private static final int NUM_EFFECTS = Type.values().length;
+  private static final int NUM_PHASES  = 2;
+  private static final int FAKE_EFFECT_ID = -2;
+  private static final Type[] types;
+
+  static
+    {
+    int i=0;
+    types = new Type[NUM_EFFECTS];
+
+    for(Type type: Type.values())
+      {
+      types[i++] = type;
+      }
+    }
+
+  private EffectListener mListener;
+  private int mDuration;
+  private int mEffectReturned;
+  private int[] mCubeEffectNumber, mNodeEffectNumber;
+  private int mPhase;
+
+  RubikCube mCube;
+  DistortedScreen mScreen;
+  Effect[][] mCubeEffects;
+  int[][] mCubeEffectPosition;
+  Effect[][] mNodeEffects;
+  int[][] mNodeEffectPosition;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  WinEffect()
+    {
+    mPhase        =  0;
+
+    mCubeEffectNumber   = new int[NUM_PHASES];
+    mNodeEffectNumber   = new int[NUM_PHASES];
+    mCubeEffectPosition = new int[NUM_PHASES][];
+    mNodeEffectPosition = new int[NUM_PHASES][];
+    mCubeEffects        = new Effect[NUM_PHASES][];
+    mNodeEffects        = new Effect[NUM_PHASES][];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static String[] getNames()
+    {
+    String[] names = new String[NUM_EFFECTS];
+
+    for( int i=0; i<NUM_EFFECTS; i++)
+      {
+      names[i] = types[i].name();
+      }
+
+    return names;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static WinEffect create(int ordinal) throws InstantiationException, IllegalAccessException
+    {
+    return types[ordinal].effect.newInstance();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract void createEffectsPhase0(int duration);
+  abstract void createEffectsPhase1(int duration);
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void effectFinished(final long effectID)
+    {
+    int total = mCubeEffectNumber[mPhase]+mNodeEffectNumber[mPhase];
+
+    for(int i=0; i<mCubeEffectNumber[mPhase]; i++)
+      {
+      long id = mCubeEffects[mPhase][i].getID();
+
+      if( effectID == id )
+        {
+        if( ++mEffectReturned == total ) effectAction(mPhase);
+        mCube.remove(id);
+        return;
+        }
+      }
+    for(int i=0; i<mNodeEffectNumber[mPhase]; i++)
+      {
+      long id = mNodeEffects[mPhase][i].getID();
+
+      if( effectID == id )
+        {
+        if( ++mEffectReturned == total ) effectAction(mPhase);
+        mCube.getEffects().abortById(id);
+        return;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void effectAction(int phase)
+    {
+    switch(phase)
+      {
+      case 0: mEffectReturned = 0;
+              mPhase          = 1;
+              mCube.solve();
+              createEffectsPhase1(mDuration);
+              assignEffects(mPhase);
+              break;
+      case 1: mListener.effectFinished(FAKE_EFFECT_ID);
+              break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public long start(int duration, RubikRenderer renderer)
+    {
+    mScreen   = renderer.getScreen();
+    mCube     = renderer.getCube();
+    mListener = renderer;
+    mDuration = duration;
+
+    createEffectsPhase0(mDuration);
+    assignEffects(mPhase);
+
+    return FAKE_EFFECT_ID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void assignEffects(int phase)
+    {
+    mCubeEffectNumber[phase] = ( mCubeEffects[phase]!=null ) ? mCubeEffects[phase].length : 0;
+    mNodeEffectNumber[phase] = ( mNodeEffects[phase]!=null ) ? mNodeEffects[phase].length : 0;
+
+    if( mCubeEffectNumber[phase]==0 && mNodeEffectNumber[phase]==0 )
+      {
+      throw new RuntimeException("Cube and Node Effects ("+phase+" phase) both not created!");
+      }
+
+    for(int i=0; i<mCubeEffectNumber[phase]; i++)
+      {
+      mCube.apply(mCubeEffects[phase][i],mCubeEffectPosition[phase][i]);
+      mCubeEffects[phase][i].notifyWhenFinished(this);
+      }
+
+    DistortedEffects nodeEffects = mCube.getEffects();
+
+    for(int i=0; i<mNodeEffectNumber[phase]; i++)
+      {
+      nodeEffects.apply(mNodeEffects[phase][i],mNodeEffectPosition[phase][i]);
+      mNodeEffects[phase][i].notifyWhenFinished(this);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @SuppressWarnings("unused")
+  public static void enableEffects()
+    {
+    Method method;
+
+    for(Type type: Type.values())
+      {
+      try
+        {
+        method = type.effect.getDeclaredMethod("enable"); // enable not public, thus getDeclaredMethod
+        }
+      catch(NoSuchMethodException ex)
+        {
+        android.util.Log.e("SolveEffect", type.effect.getSimpleName()+": exception getting method: "+ex.getMessage());
+        method = null;
+        }
+
+      try
+        {
+        if( method!=null ) method.invoke(null);
+        }
+      catch(Exception ex)
+        {
+        android.util.Log.e("SolveEffect", type.effect.getSimpleName()+": exception invoking method: "+ex.getMessage());
+        }
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/effect/win/WinEffectNone.java b/src/main/java/org/distorted/effect/win/WinEffectNone.java
new file mode 100644
index 00000000..adebf3ae
--- /dev/null
+++ b/src/main/java/org/distorted/effect/win/WinEffectNone.java
@@ -0,0 +1,61 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted 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.                                                           //
+//                                                                                               //
+// Distorted 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 Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.win;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Static3D;
+
+public class WinEffectNone extends WinEffect
+  {
+  public void createEffectsPhase0(int duration)
+    {
+    Dynamic3D d0 = new Dynamic3D(1,0.5f);
+    d0.add(new Static3D(0,0,0));
+
+    mCubeEffectPosition[0] = new int[] {-1};
+    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
+    mCubeEffects[0][0]     = new MatrixEffectMove(d0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void createEffectsPhase1(int duration)
+    {
+    Dynamic3D d0 = new Dynamic3D(1,0.5f);
+    d0.add(new Static3D(0,0,0));
+
+    mCubeEffectPosition[1]  = new int[] {-1};
+    mCubeEffects[1]         = new Effect[mCubeEffectPosition[1].length];
+    mCubeEffects[1][0]      = new MatrixEffectMove(d0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/effect/win/WinEffectSpin.java b/src/main/java/org/distorted/effect/win/WinEffectSpin.java
new file mode 100644
index 00000000..24f7dd32
--- /dev/null
+++ b/src/main/java/org/distorted/effect/win/WinEffectSpin.java
@@ -0,0 +1,81 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted 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.                                                           //
+//                                                                                               //
+// Distorted 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 Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.win;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectRotate;
+import org.distorted.library.type.Dynamic;
+import org.distorted.library.type.Dynamic1D;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static3D;
+
+public class WinEffectSpin extends WinEffect
+  {
+  public void createEffectsPhase0(int duration)
+    {
+    mCubeEffectPosition[0] = new int[] {3};
+    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
+
+    Static3D axis  = new Static3D(1,0,0);
+    Static3D center= new Static3D(0,0,0);
+
+    Dynamic1D d0 = new Dynamic1D(duration/2, 1.0f);
+    d0.setMode(Dynamic.MODE_JUMP);
+    d0.setConvexity(0.0f);          // otherwise speed of the rotation would be strangely uneven
+    d0.add(new Static1D( 0*36));
+    d0.add(new Static1D( 1*36));
+    d0.add(new Static1D( 3*36));
+    d0.add(new Static1D( 6*36));
+    d0.add(new Static1D(10*36));
+    mCubeEffects[0][0] = new MatrixEffectRotate(d0,axis,center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void createEffectsPhase1(int duration)
+    {
+    mCubeEffectPosition[1] = new int[] {3};
+    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
+
+    Static3D axis  = new Static3D(1,0,0);
+    Static3D center= new Static3D(0,0,0);
+
+    Dynamic1D d1 = new Dynamic1D(duration/2, 1.0f);
+    d1.setMode(Dynamic.MODE_JUMP);
+    d1.setConvexity(0.0f);
+    d1.add(new Static1D( 0*36));
+    d1.add(new Static1D( 4*36));
+    d1.add(new Static1D( 7*36));
+    d1.add(new Static1D( 9*36));
+    d1.add(new Static1D(10*36));
+    mCubeEffects[1][0] = new MatrixEffectRotate(d1,axis,center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/magic/RubikActivity.java b/src/main/java/org/distorted/magic/RubikActivity.java
index 405e6e80..b2265814 100644
--- a/src/main/java/org/distorted/magic/RubikActivity.java
+++ b/src/main/java/org/distorted/magic/RubikActivity.java
@@ -30,6 +30,7 @@ import android.support.v7.app.AppCompatActivity;
 import android.view.View;
 
 import org.distorted.component.HorizontalNumberPicker;
+import org.distorted.effect.win.WinEffect;
 import org.distorted.library.main.DistortedLibrary;
 import org.distorted.effect.BaseEffect;
 
diff --git a/src/main/java/org/distorted/magic/RubikCube.java b/src/main/java/org/distorted/magic/RubikCube.java
index 07bfbaed..0e5dfb8d 100644
--- a/src/main/java/org/distorted/magic/RubikCube.java
+++ b/src/main/java/org/distorted/magic/RubikCube.java
@@ -53,9 +53,6 @@ public class RubikCube extends DistortedNode
     private static final Static3D VectY = new Static3D(0,1,0);
     private static final Static3D VectZ = new Static3D(0,0,1);
 
-    private static final float SQ2 = 0.5f*((float)Math.sqrt(2));
-    private static final float[] LEGAL = { 0.0f , 0.5f , -0.5f , 1.0f , -1.0f , SQ2 , -SQ2 };
-
     public static final int VECTX = 0;  //
     public static final int VECTY = 1;  // don't change this
     public static final int VECTZ = 2;  //
@@ -548,6 +545,9 @@ public class RubikCube extends DistortedNode
 // We also have to remember that the group of unit quaternions is a double-cover of rotations
 // in 3D ( q represents the same rotation as -q ) - so invert if needed.
 
+    private static final float SQ2 = 0.5f*((float)Math.sqrt(2));
+    private static final float[] LEGAL = { 0.0f , 0.5f , -0.5f , 1.0f , -1.0f , SQ2 , -SQ2 };
+
     private void normalizeScrambleQuat(int i, int j, int k)
       {
       Static4D quat = mQuatScramble[i][j][k];
diff --git a/src/main/java/org/distorted/magic/RubikRenderer.java b/src/main/java/org/distorted/magic/RubikRenderer.java
index bd42b2bf..85540442 100644
--- a/src/main/java/org/distorted/magic/RubikRenderer.java
+++ b/src/main/java/org/distorted/magic/RubikRenderer.java
@@ -109,8 +109,9 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
       if( mFinishRotation )
         {
-        mCanRotate = false;
-        mFinishRotation=false;
+        mFinishRotation = false;
+        mCanRotate      = false;
+        mCanUI          = false;
         mRotationFinishedID = mNewCube.finishRotationNow(this);
         }
 
@@ -121,10 +122,16 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
         if( mNewCube.isSolved() )
           {
-          android.util.Log.e("renderer", "CUBE IS SOLVED NOW");
+          mCanDrag        = false;
+          mCanRotate      = false;
+          mCanUI          = false;
+          doEffectNow( BaseEffect.Type.WIN );
+          }
+        else
+          {
+          mCanRotate = true;
+          mCanUI     = true;
           }
-
-        mCanRotate = true;
         }
 
       if( mSizeChangeCube )
@@ -132,16 +139,17 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
         mSizeChangeCube = false;
         mCanDrag        = false;
         mCanRotate      = false;
+        mCanUI          = false;
         createCubeNow(mNextCubeSize);
         doEffectNow( BaseEffect.Type.SIZECHANGE );
         }
 
       if( mSolveCube )
         {
-        mSolveCube   = false;
-        mCanDrag     = false;
-        mCanRotate   = false;
-        mCanUI       = false;
+        mSolveCube      = false;
+        mCanDrag        = false;
+        mCanRotate      = false;
+        mCanUI          = false;
         doEffectNow( BaseEffect.Type.SOLVE );
         }
 
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 414ec4d9..f33586d2 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -11,6 +11,7 @@
     <string name="sizechange_effect">Size Change Effect:</string>
     <string name="solve_effect">Solve Effect:</string>
     <string name="scramble_effect">Scramble Effect:</string>
+    <string name="win_effect">Win Effect:</string>
     <string name="duration">Duration:</string>
     <string name="type">Type:</string>
     <string name="credits1">Open Source app developed using the Distorted graphics library. </string>
