commit eaf464157c3cdba015b7632c3549fae33ed1d11f
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sat Oct 2 12:07:00 2021 +0200

    Move PreRender to objectlib.
    This code is now shared betweeen the Rubik and Tutorial activities.

diff --git a/src/main/java/org/distorted/helpers/MovesAndLockController.java b/src/main/java/org/distorted/helpers/MovesAndLockController.java
index 2b3bf3b5..73a39a61 100644
--- a/src/main/java/org/distorted/helpers/MovesAndLockController.java
+++ b/src/main/java/org/distorted/helpers/MovesAndLockController.java
@@ -32,7 +32,7 @@ import org.distorted.main.RubikActivity;
 import org.distorted.objectlib.helpers.BlockController;
 import org.distorted.objectlib.helpers.MovesFinished;
 import org.distorted.objectlib.helpers.TwistyActivity;
-import org.distorted.objectlib.helpers.TwistyPreRender;
+import org.distorted.objectlib.main.ObjectPreRender;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -55,7 +55,7 @@ public class MovesAndLockController implements MovesFinished
 
   private final ArrayList<Move> mMoves;
   private boolean mCanPrevMove;
-  private TwistyPreRender mPre;
+  private ObjectPreRender mPre;
   private ImageButton mPrevButton, mLockButton;
   private long mLockTime;
   private Timer mTimer;
@@ -149,7 +149,7 @@ public class MovesAndLockController implements MovesFinished
         if( angle!=0 )
           {
           mCanPrevMove = false;
-          mPre = act.getTwistyPreRender();
+          mPre = act.getPreRender();
           mPre.blockTouch(BlockController.MOVES_PLACE_0);
           mPre.addRotation(this, axis, row, -angle, duration);
           }
diff --git a/src/main/java/org/distorted/main/RubikActivity.java b/src/main/java/org/distorted/main/RubikActivity.java
index 94678511..523fdc4f 100644
--- a/src/main/java/org/distorted/main/RubikActivity.java
+++ b/src/main/java/org/distorted/main/RubikActivity.java
@@ -39,10 +39,10 @@ import org.distorted.library.main.DistortedLibrary;
 import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.type.Static4D;
 
+import org.distorted.objectlib.main.ObjectPreRender;
 import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.objectlib.main.ObjectType;
 import org.distorted.objectlib.helpers.BlockController;
-import org.distorted.objectlib.helpers.TwistyPreRender;
 
 import org.distorted.dialogs.RubikDialogError;
 import org.distorted.dialogs.RubikDialogPrivacy;
@@ -405,7 +405,7 @@ public class RubikActivity extends TwistyActivity
     public TwistyObject getObject()
       {
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      RubikPreRender pre = view.getPreRender();
+      ObjectPreRender pre = view.getPreRender();
       return pre.getObject();
       }
 
@@ -420,15 +420,7 @@ public class RubikActivity extends TwistyActivity
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public RubikPreRender getPreRender()
-      {
-      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      return view.getPreRender();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public TwistyPreRender getTwistyPreRender()
+    public ObjectPreRender getPreRender()
       {
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
       return view.getPreRender();
@@ -460,7 +452,7 @@ public class RubikActivity extends TwistyActivity
     public void changeObject(ObjectType newObject, boolean reportChange)
       {
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      RubikPreRender pre = view.getPreRender();
+      ObjectPreRender pre = view.getPreRender();
 
       if( reportChange )
         {
@@ -518,7 +510,7 @@ public class RubikActivity extends TwistyActivity
     public void setupObject(ObjectType object, int[][] moves)
       {
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      RubikPreRender pre = view.getPreRender();
+      ObjectPreRender pre = view.getPreRender();
       pre.setupObject(object,moves);
       }
 
@@ -612,7 +604,7 @@ public class RubikActivity extends TwistyActivity
       {
       setLock();
 
-      TwistyPreRender pre = getPreRender();
+      ObjectPreRender pre = getPreRender();
       pre.blockEverything(place);
 
       RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
@@ -625,7 +617,7 @@ public class RubikActivity extends TwistyActivity
       {
       unsetLock();
 
-      TwistyPreRender pre = getPreRender();
+      ObjectPreRender pre = getPreRender();
       pre.unblockEverything();
 
       RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
diff --git a/src/main/java/org/distorted/main/RubikPreRender.java b/src/main/java/org/distorted/main/RubikPreRender.java
deleted file mode 100644
index 92636f5f..00000000
--- a/src/main/java/org/distorted/main/RubikPreRender.java
+++ /dev/null
@@ -1,592 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.main;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-
-import org.distorted.objectlib.helpers.ObjectStateActioner;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.effects.BaseEffect;
-import org.distorted.objectlib.effects.EffectController;
-import org.distorted.objectlib.effects.scramble.ScrambleEffect;
-import org.distorted.objectlib.helpers.BlockController;
-import org.distorted.objectlib.helpers.MovesFinished;
-import org.distorted.objectlib.helpers.TwistyPreRender;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikPreRender implements EffectController, TwistyPreRender
-  {
-  private final RubikSurfaceView mView;
-  private boolean mFinishRotation, mRemoveRotation, mRemovePatternRotation, mAddRotation,
-                  mSetQuat, mChangeObject, mSetupObject, mSolveObject, mScrambleObject,
-                  mInitializeObject, mSetTextureMap, mResetAllTextureMaps, mSolve;
-  private boolean mUIBlocked, mTouchBlocked;
-  private boolean mIsSolved;
-  private ObjectType mNextObject;
-  private long mRotationFinishedID;
-  private final long[] mEffectID;
-  private int mScreenWidth;
-  private SharedPreferences mPreferences;
-  private int[][] mNextMoves;
-  private TwistyObject mOldObject, mNewObject;
-  private int mScrambleObjectNum;
-  private int mAddRotationAxis, mAddRotationRowBitmap, mAddRotationAngle;
-  private long mAddRotationDuration;
-  private MovesFinished mAddActionListener;
-  private long mAddRotationID, mRemoveRotationID;
-  private int mCubit, mFace, mNewColor;
-  private int mNearestAngle;
-  private long mDebugStartTime;
-  private final BlockController mBlockController;
-  private final ObjectStateActioner mActioner;
-  private String mDebug;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  RubikPreRender(RubikSurfaceView view, ObjectStateActioner actioner)
-    {
-    mView = view;
-    mActioner = actioner;
-
-    mFinishRotation       = false;
-    mRemoveRotation       = false;
-    mRemovePatternRotation= false;
-    mAddRotation          = false;
-    mSetQuat              = false;
-    mChangeObject         = false;
-    mSetupObject          = false;
-    mSolveObject          = false;
-    mSolve                = false;
-    mScrambleObject       = false;
-
-    mOldObject = null;
-    mNewObject = null;
-
-    mDebug = "";
-
-    mScreenWidth = 0;
-    mScrambleObjectNum = 0;
-
-    mEffectID = new long[BaseEffect.Type.LENGTH];
-
-    RubikActivity act = (RubikActivity)mView.getContext();
-    mBlockController = new BlockController(act);
-    unblockEverything();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createObjectNow(ObjectType object, int[][] moves)
-    {
-    boolean firstTime = (mNewObject==null);
-
-    if( mOldObject!=null ) mOldObject.releaseResources();
-    mOldObject = mNewObject;
-
-    Context con = mView.getContext();
-    Resources res = con.getResources();
-
-    mNewObject = object.create(mView.getQuat(), moves, res, mScreenWidth);
-
-    if( mNewObject!=null )
-      {
-      mView.setMovement(mNewObject.getMovement());
-      if( firstTime ) mNewObject.restorePreferences(mPreferences);
-      mIsSolved = mNewObject.isSolved();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// do all 'adjustable' effects (SizeChange, Solve, Scramble)
-
-  private void doEffectNow(BaseEffect.Type type)
-    {
-    try
-      {
-      int index = type.ordinal();
-      mEffectID[index] = type.startEffect(mView.getRenderer().getScreen(),this);
-      }
-    catch( Exception ex )
-      {
-      android.util.Log.e("renderer", "exception starting effect: "+ex.getMessage());
-      unblockEverything();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void removeRotationNow()
-    {
-    mRemoveRotation=false;
-    mNewObject.removeRotationNow();
-
-    boolean solved = mNewObject.isSolved();
-
-    if( solved && !mIsSolved )
-      {
-      mActioner.onSolved();
-      unblockEverything();
-      doEffectNow( BaseEffect.Type.WIN );
-      }
-    else
-      {
-      unblockEverything();
-      }
-
-    mIsSolved = solved;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void removeRotation()
-    {
-    mRemoveRotation = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void removePatternRotation()
-    {
-    mRemovePatternRotation = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void removePatternRotationNow()
-    {
-    mRemovePatternRotation=false;
-    mNewObject.removeRotationNow();
-    mAddActionListener.onActionFinished(mRemoveRotationID);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void addRotationNow()
-    {
-    mAddRotation = false;
-    mAddRotationID = mNewObject.addNewRotation( mAddRotationAxis, mAddRotationRowBitmap,
-                                                mAddRotationAngle, mAddRotationDuration, this);
-
-    if( mAddRotationID==0 ) // failed to add effect - should never happen
-      {
-      unblockEverything();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void finishRotationNow()
-    {
-    mFinishRotation = false;
-    blockEverything(BlockController.RUBIK_PLACE_0);
-    mRotationFinishedID = mNewObject.finishRotationNow(this, mNearestAngle);
-
-    if( mRotationFinishedID==0 ) // failed to add effect - should never happen
-      {
-      unblockEverything();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void changeObjectNow()
-    {
-    mChangeObject = false;
-
-    if ( mNewObject==null || mNewObject.getObjectType()!=mNextObject )
-      {
-      blockEverything(BlockController.RUBIK_PLACE_1);
-      createObjectNow(mNextObject, null);
-      doEffectNow( BaseEffect.Type.SIZECHANGE );
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupObjectNow()
-    {
-    mSetupObject = false;
-
-    if ( mNewObject==null || mNewObject.getObjectType()!=mNextObject)
-      {
-      blockEverything(BlockController.RUBIK_PLACE_2);
-      createObjectNow(mNextObject, mNextMoves);
-      doEffectNow( BaseEffect.Type.SIZECHANGE );
-      }
-    else
-      {
-      mNewObject.initializeObject(mNextMoves);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void scrambleObjectNow()
-    {
-    mScrambleObject = false;
-    mIsSolved       = false;
-    blockEverything(BlockController.RUBIK_PLACE_3);
-    doEffectNow( BaseEffect.Type.SCRAMBLE );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void solveObjectNow()
-    {
-    mSolveObject = false;
-    blockEverything(BlockController.RUBIK_PLACE_4);
-    doEffectNow( BaseEffect.Type.SOLVE );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void solveNow()
-    {
-    mSolve = false;
-    mNewObject.solve();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void initializeObjectNow()
-    {
-    mInitializeObject = false;
-    mNewObject.initializeObject(mNextMoves);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setTextureMapNow()
-    {
-    mSetTextureMap = false;
-
-    if( mNewObject!=null ) mNewObject.setTextureMap(mCubit,mFace,mNewColor);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void resetAllTextureMapsNow()
-    {
-    mResetAllTextureMaps = false;
-    if( mNewObject!=null ) mNewObject.resetAllTextureMaps();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setQuatNow()
-    {
-    mSetQuat = false;
-    mView.setQuat();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-//
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void rememberMove(int axis, int row, int angle)
-    {
-    mDebug += (" (m "+axis+" "+(1<<row)+" "+angle+" "+(System.currentTimeMillis()-mDebugStartTime)+")");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setScreenSize(int width)
-    {
-    if( mNewObject!=null )
-      {
-      mNewObject.createTexture();
-      mNewObject.recomputeScaleFactor(width);
-      }
-    mScreenWidth = width;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void savePreferences(SharedPreferences.Editor editor)
-    {
-    if( mNewObject!=null )
-      {
-      mNewObject.savePreferences(editor);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void restorePreferences(SharedPreferences preferences)
-    {
-    mPreferences = preferences;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void finishRotation(int nearestAngle)
-    {
-    mNearestAngle   = nearestAngle;
-    mFinishRotation = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void changeObject(ObjectType object)
-    {
-    mChangeObject = true;
-    mNextObject = object;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setupObject(ObjectType object, int[][] moves)
-    {
-    mSetupObject= true;
-    mNextObject = object;
-    mNextMoves  = moves;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setTextureMap(int cubit, int face, int newColor)
-    {
-    mSetTextureMap = true;
-
-    mCubit    = cubit;
-    mFace     = face;
-    mNewColor = newColor;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean isTouchBlocked()
-    {
-    return mTouchBlocked;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean isUINotBlocked()
-    {
-    return !mUIBlocked;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void blockEverything(int place)
-    {
-    mUIBlocked   = true;
-    mTouchBlocked= true;
-    mBlockController.touchBlocked(place);
-    mBlockController.uiBlocked(place);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void blockTouch(int place)
-    {
-    mTouchBlocked= true;
-    mBlockController.touchBlocked(place);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void unblockEverything()
-    {
-    mUIBlocked   = false;
-    mTouchBlocked= false;
-    mBlockController.touchUnblocked();
-    mBlockController.uiUnblocked();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void unblockTouch()
-    {
-    mTouchBlocked= false;
-    mBlockController.touchUnblocked();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void unblockUI()
-    {
-    mUIBlocked= false;
-    mBlockController.uiUnblocked();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setQuatOnNextRender()
-    {
-    mSetQuat = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void preRender()
-    {
-    if( mSolve                 ) solveNow();
-    if( mSetQuat               ) setQuatNow();
-    if( mFinishRotation        ) finishRotationNow();
-    if( mRemoveRotation        ) removeRotationNow();
-    if( mRemovePatternRotation ) removePatternRotationNow();
-    if( mChangeObject          ) changeObjectNow();
-    if( mSetupObject           ) setupObjectNow();
-    if( mSolveObject           ) solveObjectNow();
-    if( mScrambleObject        ) scrambleObjectNow();
-    if( mAddRotation           ) addRotationNow();
-    if( mInitializeObject      ) initializeObjectNow();
-    if( mResetAllTextureMaps   ) resetAllTextureMapsNow();
-    if( mSetTextureMap         ) setTextureMapNow();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void addRotation(MovesFinished listener, int axis, int rowBitmap, int angle, long duration)
-    {
-    mAddRotation = true;
-
-    mAddActionListener    = listener;
-    mAddRotationAxis      = axis;
-    mAddRotationRowBitmap = rowBitmap;
-    mAddRotationAngle     = angle;
-    mAddRotationDuration  = duration;
-
-    if( listener instanceof ScrambleEffect )
-      {
-      mDebug += (" (a "+axis+" "+rowBitmap+" "+angle+" "+(System.currentTimeMillis()-mDebugStartTime)+")");
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void initializeObject(int[][] moves)
-    {
-    mInitializeObject = true;
-    mNextMoves = moves;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void scrambleObject(int num)
-    {
-    if( !mUIBlocked )
-      {
-      mScrambleObject = true;
-      mScrambleObjectNum = num;
-      mDebug = "";
-      mDebugStartTime = System.currentTimeMillis();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this starts the Solve Effect
-
-  public void solveObject()
-    {
-    if( !mUIBlocked )
-      {
-      mSolveObject = true;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this only sets the cubits state to solved
-
-  public void solve()
-    {
-    mSolve = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void resetAllTextureMaps()
-    {
-    mResetAllTextureMaps = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public TwistyObject getObject()
-    {
-    return mNewObject;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public TwistyObject getOldObject()
-    {
-    return mOldObject;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getNumScrambles()
-    {
-    return mScrambleObjectNum;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void effectFinished(final long effectID)
-    {
-    if( effectID == mRotationFinishedID )
-      {
-      mRotationFinishedID = 0;
-      removeRotation();
-      }
-    else if( effectID == mAddRotationID )
-      {
-      mAddRotationID = 0;
-      mRemoveRotationID = effectID;
-      removePatternRotation();
-      }
-    else
-      {
-      for(int i=0; i<BaseEffect.Type.LENGTH; i++)
-        {
-        if( effectID == mEffectID[i] )
-          {
-          if( i!=BaseEffect.Type.WIN.ordinal() )
-            {
-            unblockEverything();
-            }
-
-          if( i==BaseEffect.Type.SCRAMBLE.ordinal() )
-            {
-            RubikActivity act = (RubikActivity)mView.getContext();
-            mActioner.onScrambleEffectFinished(act);
-            }
-
-          if( i==BaseEffect.Type.WIN.ordinal() )
-            {
-            RubikActivity act = (RubikActivity)mView.getContext();
-            mActioner.onWinEffectFinished(act,mDebug,mScrambleObjectNum);
-            }
-
-          break;
-          }
-        }
-      }
-    }
-  }
diff --git a/src/main/java/org/distorted/main/RubikSurfaceView.java b/src/main/java/org/distorted/main/RubikSurfaceView.java
index 738e8b37..3d1b0ac7 100644
--- a/src/main/java/org/distorted/main/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/main/RubikSurfaceView.java
@@ -30,10 +30,14 @@ import android.view.MotionEvent;
 
 import com.google.firebase.crashlytics.FirebaseCrashlytics;
 
+import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.type.Static2D;
 import org.distorted.library.type.Static4D;
 import org.distorted.library.main.QuatHelper;
 
+import org.distorted.objectlib.helpers.ObjectSurfaceView;
+import org.distorted.objectlib.helpers.TwistyActivity;
+import org.distorted.objectlib.main.ObjectPreRender;
 import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.objectlib.main.Movement;
 
@@ -46,7 +50,7 @@ import org.distorted.solvers.SolverMain;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikSurfaceView extends GLSurfaceView
+public class RubikSurfaceView extends GLSurfaceView implements ObjectSurfaceView
 {
     public static final int NUM_SPEED_PROBES = 10;
     public static final int INVALID_POINTER_ID = -1;
@@ -64,8 +68,7 @@ public class RubikSurfaceView extends GLSurfaceView
     private final Static4D CAMERA_POINT = new Static4D(0, 0, 0, 0);
 
     private RubikRenderer mRenderer;
-    private RubikPreRender mPreRender;
-    private RubikObjectStateActioner mActioner;
+    private ObjectPreRender mPreRender;
     private Movement mMovement;
     private boolean mDragging, mBeginningRotation, mContinuingRotation;
     private int mScreenWidth, mScreenHeight, mScreenMin;
@@ -116,32 +119,11 @@ public class RubikSurfaceView extends GLSurfaceView
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    RubikPreRender getPreRender()
+    ObjectPreRender getPreRender()
       {
       return mPreRender;
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void setQuat()
-      {
-      mQuat.set(mTemp);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    Static4D getQuat()
-      {
-      return mQuat;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void setMovement(Movement movement)
-      {
-      mMovement = movement;
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // cast the 3D axis we are currently rotating along (which is already casted to the surface of the
 // currently touched face AND converted into a 4D vector - fourth 0) to a 2D in-screen-surface axis
@@ -237,7 +219,7 @@ public class RubikSurfaceView extends GLSurfaceView
         Static4D rotatedTouchPoint= QuatHelper.rotateVectorByInvertedQuat(touchPoint, mQuat);
         Static4D rotatedCamera= QuatHelper.rotateVectorByInvertedQuat(CAMERA_POINT, mQuat);
 
-        if( mMovement!=null && mMovement.faceTouched(rotatedTouchPoint,rotatedCamera,object.getObjectRatio() ) )
+        if( object!=null && mMovement!=null && mMovement.faceTouched(rotatedTouchPoint,rotatedCamera,object.getObjectRatio() ) )
           {
           mDragging           = false;
           mContinuingRotation = false;
@@ -535,9 +517,8 @@ public class RubikSurfaceView extends GLSurfaceView
         mFirstIndex =0;
         mLastIndex  =0;
 
-        mActioner  = new RubikObjectStateActioner();
         mRenderer  = new RubikRenderer(this);
-        mPreRender = new RubikPreRender(this,mActioner);
+        mPreRender = new ObjectPreRender(this,new RubikObjectStateActioner());
 
         RubikActivity act = (RubikActivity)context;
         DisplayMetrics dm = new DisplayMetrics();
@@ -573,6 +554,41 @@ public class RubikSurfaceView extends GLSurfaceView
         }
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void setQuat()
+      {
+      mQuat.set(mTemp);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public Static4D getQuat()
+      {
+      return mQuat;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void setMovement(Movement movement)
+      {
+      mMovement = movement;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public TwistyActivity getActivity()
+      {
+      return (TwistyActivity)getContext();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public DistortedScreen getScreen()
+      {
+      return mRenderer.getScreen();
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public void prepareDown()
diff --git a/src/main/java/org/distorted/patterns/RubikPattern.java b/src/main/java/org/distorted/patterns/RubikPattern.java
index 81966869..76dcbcb4 100644
--- a/src/main/java/org/distorted/patterns/RubikPattern.java
+++ b/src/main/java/org/distorted/patterns/RubikPattern.java
@@ -23,7 +23,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.distorted.objectlib.helpers.MovesFinished;
-import org.distorted.main.RubikPreRender;
+import org.distorted.objectlib.main.ObjectPreRender;
 
 import static org.distorted.patterns.RubikPatternList.NUM_OBJECTS;
 
@@ -151,7 +151,7 @@ public class RubikPattern
 
   /////////////////////////////////////////////////////////////
 
-    void makeMove(RubikPreRender pre, int pattern)
+    void makeMove(ObjectPreRender pre, int pattern)
       {
       if( !mInitialized ) initialize();
 
@@ -164,7 +164,7 @@ public class RubikPattern
 
   /////////////////////////////////////////////////////////////
 
-    void backMove(RubikPreRender pre, int pattern)
+    void backMove(ObjectPreRender pre, int pattern)
       {
       if( !mInitialized ) initialize();
 
@@ -317,7 +317,7 @@ public class RubikPattern
 
   /////////////////////////////////////////////////////////////
 
-    void makeMove(RubikPreRender pre)
+    void makeMove(ObjectPreRender pre)
       {
       if( !mInitialized ) initialize();
 
@@ -358,7 +358,7 @@ public class RubikPattern
 
   /////////////////////////////////////////////////////////////
 
-    void backMove(RubikPreRender pre)
+    void backMove(ObjectPreRender pre)
       {
       if( !mInitialized ) initialize();
 
@@ -576,7 +576,7 @@ public class RubikPattern
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void makeMove(RubikPreRender pre, int tab, int cat, int pat)
+  public void makeMove(ObjectPreRender pre, int tab, int cat, int pat)
     {
     Category c = getCategory(tab,cat);
     if( c!=null ) c.makeMove(pre,pat);
@@ -584,7 +584,7 @@ public class RubikPattern
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void backMove(RubikPreRender pre, int tab, int cat, int pat)
+  public void backMove(ObjectPreRender pre, int tab, int cat, int pat)
     {
     Category c = getCategory(tab,cat);
     if( c!=null ) c.backMove(pre,pat);
diff --git a/src/main/java/org/distorted/screens/RubikScreenPattern.java b/src/main/java/org/distorted/screens/RubikScreenPattern.java
index 482ca625..1c06d48e 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPattern.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPattern.java
@@ -36,7 +36,7 @@ import org.distorted.main.R;
 import org.distorted.dialogs.RubikDialogPattern;
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.RubikActivity;
-import org.distorted.main.RubikPreRender;
+import org.distorted.objectlib.main.ObjectPreRender;
 import org.distorted.patterns.RubikPattern;
 import org.distorted.patterns.RubikPatternList;
 
@@ -179,7 +179,7 @@ public class RubikScreenPattern extends RubikScreenAbstract
       public void onClick(View v)
         {
         RubikPattern pattern = RubikPattern.getInstance();
-        RubikPreRender pre = act.getPreRender();
+        ObjectPreRender pre = act.getPreRender();
         pattern.backMove( pre, mPatternOrdinal, mCategory, mPattern);
         int currMove = pattern.getCurMove(mPatternOrdinal, mCategory, mPattern);
         mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
@@ -200,7 +200,7 @@ public class RubikScreenPattern extends RubikScreenAbstract
       public void onClick(View v)
         {
         RubikPattern pattern = RubikPattern.getInstance();
-        RubikPreRender pre = act.getPreRender();
+        ObjectPreRender pre = act.getPreRender();
         pattern.makeMove( pre, mPatternOrdinal, mCategory, mPattern);
         int currMove = pattern.getCurMove(mPatternOrdinal, mCategory, mPattern);
         mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
diff --git a/src/main/java/org/distorted/screens/RubikScreenPlay.java b/src/main/java/org/distorted/screens/RubikScreenPlay.java
index b305a3f9..f44fbc08 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPlay.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -39,7 +39,7 @@ import org.distorted.objectlib.main.ObjectType;
 
 import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
-import org.distorted.main.RubikPreRender;
+import org.distorted.objectlib.main.ObjectPreRender;
 import org.distorted.dialogs.RubikDialogAbout;
 import org.distorted.dialogs.RubikDialogPattern;
 import org.distorted.dialogs.RubikDialogScores;
@@ -550,7 +550,7 @@ public class RubikScreenPlay extends RubikScreenBase
         @Override
         public void onClick(View v)
           {
-          RubikPreRender pre = act.getPreRender();
+          ObjectPreRender pre = act.getPreRender();
 
           if(pre.isUINotBlocked())
             {
diff --git a/src/main/java/org/distorted/screens/RubikScreenSolution.java b/src/main/java/org/distorted/screens/RubikScreenSolution.java
index 2284a1bf..f4df8729 100644
--- a/src/main/java/org/distorted/screens/RubikScreenSolution.java
+++ b/src/main/java/org/distorted/screens/RubikScreenSolution.java
@@ -34,7 +34,7 @@ import org.distorted.objectlib.helpers.MovesFinished;
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
-import org.distorted.main.RubikPreRender;
+import org.distorted.objectlib.main.ObjectPreRender;
 import org.distorted.patterns.RubikPattern;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -121,7 +121,7 @@ public class RubikScreenSolution extends RubikScreenAbstract implements MovesFin
       @Override
       public void onClick(View v)
         {
-        RubikPreRender pre = act.getPreRender();
+        ObjectPreRender pre = act.getPreRender();
         backMove(pre);
         mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
         }
@@ -140,7 +140,7 @@ public class RubikScreenSolution extends RubikScreenAbstract implements MovesFin
       @Override
       public void onClick(View v)
         {
-        RubikPreRender pre = act.getPreRender();
+        ObjectPreRender pre = act.getPreRender();
         makeMove(pre);
         mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
         }
@@ -187,7 +187,7 @@ public class RubikScreenSolution extends RubikScreenAbstract implements MovesFin
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void makeMove(RubikPreRender pre)
+  private void makeMove(ObjectPreRender pre)
     {
     if( mCanRotate )
       {
@@ -226,7 +226,7 @@ public class RubikScreenSolution extends RubikScreenAbstract implements MovesFin
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void backMove(RubikPreRender pre)
+  private void backMove(ObjectPreRender pre)
     {
     if( mCanRotate )
       {
diff --git a/src/main/java/org/distorted/screens/RubikScreenSolver.java b/src/main/java/org/distorted/screens/RubikScreenSolver.java
index 568e81ba..37afdbdb 100644
--- a/src/main/java/org/distorted/screens/RubikScreenSolver.java
+++ b/src/main/java/org/distorted/screens/RubikScreenSolver.java
@@ -40,7 +40,7 @@ import org.distorted.dialogs.RubikDialogSolverError;
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
-import org.distorted.main.RubikPreRender;
+import org.distorted.objectlib.main.ObjectPreRender;
 import org.distorted.solvers.ImplementedSolversList;
 import org.distorted.solvers.SolverMain;
 
@@ -237,7 +237,7 @@ public class RubikScreenSolver extends RubikScreenAbstract
       @Override
       public void onClick(View v)
         {
-        RubikPreRender pre = act.getPreRender();
+        ObjectPreRender pre = act.getPreRender();
         pre.resetAllTextureMaps();
         ScreenList.goBack(act);
         }
diff --git a/src/main/java/org/distorted/tutorials/TutorialActivity.java b/src/main/java/org/distorted/tutorials/TutorialActivity.java
index 5c60cfff..8636ea75 100644
--- a/src/main/java/org/distorted/tutorials/TutorialActivity.java
+++ b/src/main/java/org/distorted/tutorials/TutorialActivity.java
@@ -19,8 +19,10 @@
 
 package org.distorted.tutorials;
 
+import android.content.SharedPreferences;
 import android.os.Build;
 import android.os.Bundle;
+import android.preference.PreferenceManager;
 import android.util.DisplayMetrics;
 import android.view.View;
 import android.view.ViewGroup;
@@ -32,14 +34,17 @@ import com.google.firebase.analytics.FirebaseAnalytics;
 
 import org.distorted.library.main.DistortedLibrary;
 
+import org.distorted.main.RubikSurfaceView;
+import org.distorted.network.RubikScores;
+import org.distorted.objectlib.effects.BaseEffect;
+import org.distorted.objectlib.main.ObjectPreRender;
 import org.distorted.objectlib.main.ObjectType;
 import org.distorted.objectlib.main.TwistyObject;
-
-import org.distorted.dialogs.RubikDialogError;
 import org.distorted.objectlib.helpers.BlockController;
 import org.distorted.objectlib.helpers.TwistyActivity;
-import org.distorted.objectlib.helpers.TwistyPreRender;
+
 import org.distorted.main.R;
+import org.distorted.dialogs.RubikDialogError;
 import org.distorted.screens.ScreenList;
 
 import static org.distorted.main.RubikRenderer.BRIGHTNESS;
@@ -205,6 +210,7 @@ public class TutorialActivity extends TwistyActivity
       TutorialSurfaceView view = findViewById(R.id.tutorialSurfaceView);
       view.onResume();
       view.initialize();
+      restorePreferences();
 
       if( mWebView!=null ) mWebView.onResume();
 
@@ -224,6 +230,15 @@ public class TutorialActivity extends TwistyActivity
       DistortedLibrary.onDestroy(1);
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void restorePreferences()
+      {
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      TutorialSurfaceView view = findViewById(R.id.tutorialSurfaceView);
+      view.getPreRender().restorePreferences(preferences);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     void OpenGLError()
@@ -253,7 +268,7 @@ public class TutorialActivity extends TwistyActivity
     public TwistyObject getObject()
       {
       TutorialSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      TutorialPreRender pre = view.getPreRender();
+      ObjectPreRender pre = view.getPreRender();
       return pre.getObject();
       }
 
@@ -273,15 +288,7 @@ public class TutorialActivity extends TwistyActivity
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public TutorialPreRender getPreRender()
-      {
-      TutorialSurfaceView view = findViewById(R.id.tutorialSurfaceView);
-      return view.getPreRender();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public TwistyPreRender getTwistyPreRender()
+    public ObjectPreRender getPreRender()
       {
       TutorialSurfaceView view = findViewById(R.id.tutorialSurfaceView);
       return view.getPreRender();
diff --git a/src/main/java/org/distorted/tutorials/TutorialPreRender.java b/src/main/java/org/distorted/tutorials/TutorialPreRender.java
deleted file mode 100644
index 7cf2d0b3..00000000
--- a/src/main/java/org/distorted/tutorials/TutorialPreRender.java
+++ /dev/null
@@ -1,460 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.tutorials;
-
-import android.content.Context;
-import android.content.res.Resources;
-
-import org.distorted.objectlib.helpers.ObjectStateActioner;
-import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.helpers.BlockController;
-import org.distorted.objectlib.helpers.MovesFinished;
-import org.distorted.objectlib.helpers.TwistyPreRender;
-import org.distorted.objectlib.effects.BaseEffect;
-import org.distorted.objectlib.effects.EffectController;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class TutorialPreRender implements EffectController, TwistyPreRender
-  {
-  private MovesFinished mAddActionListener;
-  private final TutorialSurfaceView mView;
-  private boolean mFinishRotation, mRemoveRotation, mAddRotation,
-                  mSetQuat, mChangeObject, mSetupObject, mSolveObject, mScrambleObject,
-                  mInitializeObject, mRemovePatternRotation, mSolve;
-  private boolean mUIBlocked, mTouchBlocked;
-  private boolean mIsSolved;
-  private ObjectType mNextObject;
-  private long mRotationFinishedID;
-  private int mScreenWidth;
-  private TwistyObject mOldObject, mNewObject;
-  private int mAddRotationAxis, mAddRotationRowBitmap, mAddRotationAngle;
-  private long mAddRotationDuration;
-  private long mAddRotationID, mRemoveRotationID;
-  private int mNearestAngle;
-  private int mScrambleObjectNum;
-  private final BlockController mBlockController;
-  private final ObjectStateActioner mActioner;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  TutorialPreRender(TutorialSurfaceView view, ObjectStateActioner actioner)
-    {
-    mView = view;
-    mActioner = actioner;
-
-    mFinishRotation = false;
-    mRemoveRotation = false;
-    mAddRotation    = false;
-    mSetQuat        = false;
-    mChangeObject   = false;
-    mSetupObject    = false;
-    mSolveObject    = false;
-    mSolve          = false;
-    mScrambleObject = false;
-
-    mOldObject      = null;
-    mNewObject      = null;
-
-    mScreenWidth       = 0;
-    mScrambleObjectNum = 0;
-
-    mRemovePatternRotation= false;
-
-    TutorialActivity act = (TutorialActivity)mView.getContext();
-    mBlockController = new BlockController(act);
-    unblockEverything();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createObjectNow(ObjectType object)
-    {
-    if( mOldObject!=null ) mOldObject.releaseResources();
-    mOldObject = mNewObject;
-
-    Context con = mView.getContext();
-    Resources res = con.getResources();
-
-    mNewObject = object.create(mView.getQuat(), null, res, mScreenWidth);
-
-    if( mNewObject!=null )
-      {
-      mView.setMovement(mNewObject.getMovement());
-      mIsSolved = mNewObject.isSolved();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void doEffectNow(BaseEffect.Type type)
-    {
-    try
-      {
-      type.startEffect(mView.getRenderer().getScreen(),this);
-      }
-    catch( Exception ex )
-      {
-      android.util.Log.e("renderer", "exception starting effect: "+ex.getMessage());
-      unblockEverything();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void removePatternRotation()
-    {
-    mRemovePatternRotation = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void removePatternRotationNow()
-    {
-    mRemovePatternRotation=false;
-    mNewObject.removeRotationNow();
-    mAddActionListener.onActionFinished(mRemoveRotationID);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void removeRotationNow()
-    {
-    mRemoveRotation=false;
-    mNewObject.removeRotationNow();
-
-    boolean solved = mNewObject.isSolved();
-    unblockEverything();
-    if( solved && !mIsSolved ) doEffectNow( BaseEffect.Type.WIN );
-
-    mIsSolved = solved;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void removeRotation()
-    {
-    mRemoveRotation = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void addRotationNow()
-    {
-    mAddRotation = false;
-    mAddRotationID = mNewObject.addNewRotation( mAddRotationAxis, mAddRotationRowBitmap,
-                                                mAddRotationAngle, mAddRotationDuration, this);
-
-    if( mAddRotationID==0 ) // failed to add effect - should never happen
-      {
-      unblockEverything();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void finishRotationNow()
-    {
-    mFinishRotation = false;
-    blockEverything(BlockController.TUTORIAL_PLACE_0);
-    mRotationFinishedID = mNewObject.finishRotationNow(this, mNearestAngle);
-
-    if( mRotationFinishedID==0 ) // failed to add effect - should never happen
-      {
-      unblockEverything();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void changeObjectNow()
-    {
-    mChangeObject = false;
-
-    if ( mNewObject==null || mNewObject.getObjectType()!=mNextObject)
-      {
-      blockEverything(BlockController.TUTORIAL_PLACE_1);
-      createObjectNow(mNextObject);
-      doEffectNow( BaseEffect.Type.SIZECHANGE );
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupObjectNow()
-    {
-    mSetupObject = false;
-
-    if ( mNewObject==null || mNewObject.getObjectType()!=mNextObject)
-      {
-      blockEverything(BlockController.TUTORIAL_PLACE_2);
-      createObjectNow(mNextObject);
-      doEffectNow( BaseEffect.Type.SIZECHANGE );
-      }
-    else
-      {
-      mNewObject.initializeObject(null);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void scrambleObjectNow()
-    {
-    mScrambleObject = false;
-    mIsSolved       = false;
-    blockEverything(BlockController.TUTORIAL_PLACE_3);
-    doEffectNow( BaseEffect.Type.SCRAMBLE );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void solveObjectNow()
-    {
-    mSolveObject = false;
-    blockEverything(BlockController.TUTORIAL_PLACE_4);
-    doEffectNow( BaseEffect.Type.SOLVE );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void solveNow()
-    {
-    mSolve = false;
-    mNewObject.solve();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void initializeObjectNow()
-    {
-    mInitializeObject = false;
-    mNewObject.initializeObject(null);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setQuatNow()
-    {
-    mSetQuat = false;
-    mView.setQuat();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-//
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setScreenSize(int width)
-    {
-    if( mNewObject!=null )
-      {
-      mNewObject.createTexture();
-      mNewObject.recomputeScaleFactor(width);
-      }
-
-    mScreenWidth = width;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void finishRotation(int nearestAngle)
-    {
-    mNearestAngle   = nearestAngle;
-    mFinishRotation = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void changeObject(ObjectType object)
-    {
-    mChangeObject = true;
-    mNextObject = object;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setQuatOnNextRender()
-    {
-    mSetQuat = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void preRender()
-    {
-    if( mSolve                 ) solveNow();
-    if( mSetQuat               ) setQuatNow();
-    if( mFinishRotation        ) finishRotationNow();
-    if( mRemoveRotation        ) removeRotationNow();
-    if( mChangeObject          ) changeObjectNow();
-    if( mSetupObject           ) setupObjectNow();
-    if( mSolveObject           ) solveObjectNow();
-    if( mScrambleObject        ) scrambleObjectNow();
-    if( mAddRotation           ) addRotationNow();
-    if( mInitializeObject      ) initializeObjectNow();
-    if( mRemovePatternRotation ) removePatternRotationNow();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean isTouchBlocked()
-    {
-    return mTouchBlocked;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean isUINotBlocked()
-    {
-    return !mUIBlocked;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void blockEverything(int place)
-    {
-    mUIBlocked   = true;
-    mTouchBlocked= true;
-    mBlockController.touchBlocked(place);
-    mBlockController.uiBlocked(place);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void blockTouch(int place)
-    {
-    mTouchBlocked= true;
-    mBlockController.touchBlocked(place);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void unblockEverything()
-    {
-    mUIBlocked   = false;
-    mTouchBlocked= false;
-    mBlockController.touchUnblocked();
-    mBlockController.uiUnblocked();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void unblockTouch()
-    {
-    mTouchBlocked= false;
-    mBlockController.touchUnblocked();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void unblockUI()
-    {
-    mUIBlocked= false;
-    mBlockController.uiUnblocked();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void addRotation(MovesFinished listener, int axis, int rowBitmap, int angle, long duration)
-    {
-    mAddRotation = true;
-
-    mAddActionListener    = listener;
-    mAddRotationAxis      = axis;
-    mAddRotationRowBitmap = rowBitmap;
-    mAddRotationAngle     = angle;
-    mAddRotationDuration  = duration;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getNumScrambles()
-    {
-    return mScrambleObjectNum;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this starts the SolveEffect
-
-  public void solveObject()
-    {
-    if( !mUIBlocked )
-      {
-      mSolveObject = true;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this only solves the object
-
-  public void solve()
-    {
-    mSolve = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void scrambleObject(int num)
-    {
-    if( !mUIBlocked )
-      {
-      mScrambleObject = true;
-      mScrambleObjectNum = num;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public TwistyObject getObject()
-    {
-    return mNewObject;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public TwistyObject getOldObject()
-    {
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void effectFinished(final long effectID)
-    {
-    if( effectID == mRotationFinishedID )
-      {
-      mRotationFinishedID = 0;
-      removeRotation();
-      }
-    else if( effectID == mAddRotationID )
-      {
-      mAddRotationID = 0;
-      mRemoveRotationID = effectID;
-      removePatternRotation();
-      }
-    else
-      {
-      unblockEverything();  // buggy? I think we shouldn't do it if the effect is of type 'WIN'
-      }
-    }
-  }
diff --git a/src/main/java/org/distorted/tutorials/TutorialState.java b/src/main/java/org/distorted/tutorials/TutorialState.java
index acc60f44..6653daa4 100644
--- a/src/main/java/org/distorted/tutorials/TutorialState.java
+++ b/src/main/java/org/distorted/tutorials/TutorialState.java
@@ -23,11 +23,11 @@ import android.view.View;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 
+import org.distorted.objectlib.main.ObjectPreRender;
 import org.distorted.objectlib.main.ObjectType;
 
 import org.distorted.helpers.MovesAndLockController;
 import org.distorted.objectlib.helpers.TwistyActivity;
-import org.distorted.objectlib.helpers.TwistyPreRender;
 import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
 import org.distorted.screens.RubikScreenPlay;
@@ -90,7 +90,7 @@ public class TutorialState
       @Override
       public void onClick(View v)
         {
-        TwistyPreRender pre = act.getPreRender();
+        ObjectPreRender pre = act.getPreRender();
         if( pre!=null ) pre.unblockEverything();
         act.finish();
         }
diff --git a/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java b/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java
index b017ef2b..5b3ceef3 100644
--- a/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java
+++ b/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java
@@ -30,11 +30,15 @@ import android.view.MotionEvent;
 
 import com.google.firebase.crashlytics.FirebaseCrashlytics;
 
+import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.type.Static2D;
 import org.distorted.library.type.Static4D;
 import org.distorted.library.main.QuatHelper;
 
+import org.distorted.objectlib.helpers.ObjectSurfaceView;
+import org.distorted.objectlib.helpers.TwistyActivity;
 import org.distorted.objectlib.main.Movement;
+import org.distorted.objectlib.main.ObjectPreRender;
 import org.distorted.objectlib.main.TwistyObject;
 
 import static org.distorted.main.RubikSurfaceView.INVALID_POINTER_ID;
@@ -44,12 +48,11 @@ import static org.distorted.main.RubikSurfaceView.SWIPING_SENSITIVITY;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class TutorialSurfaceView extends GLSurfaceView
+public class TutorialSurfaceView extends GLSurfaceView implements ObjectSurfaceView
 {
     private final Static4D CAMERA_POINT = new Static4D(0, 0, 0, 0);
     private TutorialRenderer mRenderer;
-    private TutorialPreRender mPreRender;
-    private TutorialObjectStateActioner mActioner;
+    private ObjectPreRender mPreRender;
     private Movement mMovement;
     private boolean mDragging, mBeginningRotation, mContinuingRotation;
     private int mScreenWidth, mScreenHeight, mScreenMin;
@@ -90,39 +93,11 @@ public class TutorialSurfaceView extends GLSurfaceView
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    TutorialRenderer getRenderer()
-      {
-      return mRenderer;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    TutorialPreRender getPreRender()
+    ObjectPreRender getPreRender()
       {
       return mPreRender;
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void setQuat()
-      {
-      mQuat.set(mTemp);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    Static4D getQuat()
-      {
-      return mQuat;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void setMovement(Movement movement)
-      {
-      mMovement = movement;
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // cast the 3D axis we are currently rotating along (which is already casted to the surface of the
 // currently touched face AND converted into a 4D vector - fourth 0) to a 2D in-screen-surface axis
@@ -570,9 +545,8 @@ public class TutorialSurfaceView extends GLSurfaceView
         mFirstIndex =0;
         mLastIndex  =0;
 
-        mActioner  = new TutorialObjectStateActioner();
         mRenderer  = new TutorialRenderer(this);
-        mPreRender = new TutorialPreRender(this,mActioner);
+        mPreRender = new ObjectPreRender(this,new TutorialObjectStateActioner());
 
         TutorialActivity act = (TutorialActivity)context;
         DisplayMetrics dm = new DisplayMetrics();
@@ -608,6 +582,41 @@ public class TutorialSurfaceView extends GLSurfaceView
         }
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void setQuat()
+      {
+      mQuat.set(mTemp);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public Static4D getQuat()
+      {
+      return mQuat;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void setMovement(Movement movement)
+      {
+      mMovement = movement;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public TwistyActivity getActivity()
+      {
+      return (TwistyActivity)getContext();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public DistortedScreen getScreen()
+      {
+      return mRenderer.getScreen();
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override
