commit f404152dbadcd2c6f626b7db4b0b8bda0290a01f
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri May 12 14:58:38 2023 +0200

    Introduce 'BandagedObject' - an abstraction and a step towards creator of Bandaged Pyraminxes.

diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java b/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java
index 7b0cab00..cd67589b 100644
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java
@@ -20,9 +20,6 @@ import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
 
 import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ConfigurationInfo;
 import android.content.res.Resources;
 import android.opengl.GLES31;
 import android.opengl.GLSurfaceView;
@@ -43,10 +40,8 @@ import org.distorted.library.type.Static1D;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 import org.distorted.objectlib.json.JsonWriter;
-import org.distorted.objectlib.main.InitData;
-import org.distorted.objectlib.shape.ShapeHexahedron;
 import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.objects.TwistyBandagedCuboid;
+
 import org.json.JSONException;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -64,13 +59,11 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
    private final DistortedScreen mScreen;
    private final Static3D mScale;
    private final Static4D mQuatT, mQuatA;
-   private final int[] mObjSize;
+   private final BandagedObject mObject;
 
-   private BandagedCubit[] mCubits;
    private boolean mInitialPhase;
    private long mStartTime;
    private float mQuatX, mQuatY, mQuatZ, mQuatW;
-   private int mX, mY, mZ, mNumCubits;
    private boolean mResetQuats, mSetQuatT, mResettingObject, mConnectingCubits, mCreatingCubits, mRescaling;
    private int mIndex1, mIndex2;
    private int mSaveIcon;
@@ -100,83 +93,11 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
 
      mObjectScreenRatio= INIT_RATIO;
      mSaveIcon = -1;
-     mObjSize  = new int[3];
 
      mScreen = new DistortedScreen();
      mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
      mScale = new Static3D(1,1,1);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private boolean isAdjacent(float[] pos1, float[] pos2)
-     {
-     int len1 = pos1.length/3;
-     int len2 = pos2.length/3;
-
-     for(int i=0; i<len1; i++)
-       for(int j=0; j<len2; j++)
-         {
-         float d0 = pos1[3*i  ] - pos2[3*j  ];
-         float d1 = pos1[3*i+1] - pos2[3*j+1];
-         float d2 = pos1[3*i+2] - pos2[3*j+2];
-
-         if( d0*d0 + d1*d1 + d2*d2 == 1 ) return true;
-         }
-
-     return false;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private int computeNumCubits(int x, int y, int z)
-     {
-     return ( x<=1 || y<=1 || z<=1 ) ? x*y*z : x*y*z-(x-2)*(y-2)*(z-2);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void createCubits()
-     {
-     mCubits = new BandagedCubit[mNumCubits];
-     int c=0;
-
-     float begX = 0.5f*(1-mX);
-     float begY = 0.5f*(1-mY);
-     float begZ = 0.5f*(1-mZ);
-
-     for(int x=0; x<mX; x++)
-       for(int y=0; y<mY; y++)
-          for(int z=0; z<mZ; z++)
-            if( x==0 || x==mX-1 || y==0 || y==mY-1 || z==0 || z==mZ-1 )
-              {
-              float[] pos = new float[] { begX+x,begY+y,begZ+z };
-              mCubits[c] = new BandagedCubit(pos,mX,mY,mZ,mQuatT,mQuatA,mScale,false);
-              c++;
-              }
-
-     mView.setCubits(mCubits,mX,mY,mZ);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void resetObject()
-     {
-     mView.resetCubits();
-
-     for(int c=0; c<mNumCubits; c++)
-       {
-       if( !mCubits[c].isAttached() )
-         {
-         mCubits[c].attach();
-         mScreen.attach(mCubits[c].getNode());
-         }
-       if( mCubits[c].getPosition().length>3 )
-         {
-         mCubits[c].reset(mScaleValue);
-         }
-       mCubits[c].setUnmarked();
-       }
+     mObject = new BandagedObject(mScreen);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -236,28 +157,23 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
      if( mConnectingCubits )
        {
        mConnectingCubits = false;
-       tryConnectingCubits(mIndex1,mIndex2);
+       mObject.tryConnectingCubits(mIndex1,mIndex2,mScaleValue);
        }
 
      if( mCreatingCubits )
        {
        mCreatingCubits = false;
-
        rescaleObject();
 
        if( mCubitsCreated )
          {
-         createCubits();
-
+         mObject.createCubits(mQuatT,mQuatA,mScale);
+         float[] dist = mObject.getDist3D();
+         BandagedCreatorTouchControl control = mView.getTouchControl();
+         control.setDist3D(dist);
          mScreen.detachAll();
          mView.resetCubits();
-
-         for(int i=0; i<mNumCubits; i++)
-           {
-           mCubits[i].scaleMove(mScaleValue);
-           DistortedNode node = mCubits[i].getNode();
-           mScreen.attach(node);
-           }
+         mObject.attachCubits(mScaleValue);
          }
        }
 
@@ -265,7 +181,7 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
        {
        mRescaling = false;
        rescaleObject();
-       for(int i=0; i<mNumCubits; i++) mCubits[i].scaleMove(mScaleValue);
+       mObject.scaleCubits(mScaleValue);
        BandagedCreatorTouchControl control = mView.getTouchControl();
        control.setObjectRatio(mObjectScreenRatio);
        }
@@ -281,20 +197,9 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
         mWidth = width;
         mHeight= height;
         rescaleObject();
-
         mScreen.detachAll();
         int touched = mView.getTouched();
-
-        for(int i=0; i<mNumCubits; i++)
-          if( mCubits[i].isAttached() )
-            {
-            mCubits[i].scaleMove(mScaleValue);
-            if( touched==i ) mCubits[i].setMarked();
-            else             mCubits[i].setUnmarked();
-            DistortedNode node = mCubits[i].getNode();
-            mScreen.attach(node);
-            }
-
+        mObject.attachAndMarkCubits(mScaleValue,touched);
         mView.setScreenSize(width,height);
         mScreen.resize(width,height);
         }
@@ -306,22 +211,11 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
       {
       DistortedLibrary.setMax(EffectType.VERTEX,0);
-      MeshBase.setMaxEffComponents(120);
-
+      MeshBase.setMaxEffComponents(120); // what?
       FragmentEffectBrightness.enable();
-
       DistortedLibrary.onSurfaceCreated(this,1);
       DistortedLibrary.setCull(true);
-
-      if( mCubits==null )
-        {
-        createCubits();
-        }
-      else
-        {
-        for(int i=0; i<mNumCubits; i++) mCubits[i].recreateBitmap();
-        }
-
+      mObject.recreateCubits(mQuatT,mQuatA,mScale);
       mCubitsCreated = true;
       mWidth = 0;
       mHeight= 0;
@@ -343,24 +237,6 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
      mConnectingCubits = true;
      }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void tryConnectingCubits(int index1, int index2)
-     {
-     if( index1!=index2 )
-       {
-       float[] pos1 = mCubits[index1].getPosition();
-       float[] pos2 = mCubits[index2].getPosition();
-
-       if( isAdjacent(pos1,pos2) )
-         {
-         mCubits[index2].join(pos1,mScaleValue);
-         mCubits[index1].detach();
-         mScreen.detach(mCubits[index1].getNode());
-         }
-       }
-     }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
    public Static4D getQuatAccu()
@@ -400,29 +276,13 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
 
    public void saveObject()
      {
-     int numAttached=0;
-
-     for(int i=0; i<mNumCubits; i++)
-       if( mCubits[i].isAttached() ) numAttached++;
-
-     float[][] pos = new float[numAttached][];
-     int attached=0;
-
-     for(int i=0; i<mNumCubits; i++)
-       if( mCubits[i].isAttached() )
-         {
-         pos[attached++] = mCubits[i].getPosition();
-         }
-
-     InitData data = new InitData(mObjSize,pos);
-     TwistyObject obj = new TwistyBandagedCuboid( TwistyObject.MESH_NICE, TwistyObject.MODE_NORM,
-                                                  new Static4D(0,0,0,1), new Static3D(0,0,0), 1.0f, data, null );
+     TwistyObject obj = mObject.createObject(TwistyObject.MODE_NORM, 1.0f );
      String name = obj.getShortName();
      BandagedCreatorActivity act = (BandagedCreatorActivity) mView.getContext();
 
      if( act.objectDoesntExist(name) && createObjectJson(obj,act) )
        {
-       setupIconCreation(act,data);
+       setupIconCreation(act);
        act.addObject(obj.getShortName());
        }
      }
@@ -485,35 +345,14 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   private int computeProjectionAngle()
-     {
-     float quot1 = mObjSize[2]/ (float)mObjSize[0];
-     float quot2 = mObjSize[2]/ (float)mObjSize[1];
-     float quot3 = mObjSize[0]/ (float)mObjSize[2];
-     float quot4 = mObjSize[0]/ (float)mObjSize[1];
-
-     float quot5 = Math.max(quot1,quot2);
-     float quot6 = Math.max(quot3,quot4);
-     float quot7 = Math.max(quot5,quot6);
-
-          if( quot7<=1.0f ) return 120;
-     else if( quot7<=1.5f ) return 90;
-     else if( quot7<=2.0f ) return 60;
-     else                   return 30;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void setupIconCreation(Activity act, InitData data)
+   private void setupIconCreation(Activity act)
      {
      final float R=1.0f;
      final int FBO_WIDTH  = (int)(R*720);
      final int FBO_HEIGHT = (int)(R*1280);
      final float OBJECT_SIZE = R*0.35f;
 
-     TwistyObject obj = new TwistyBandagedCuboid( TwistyObject.MESH_NICE, TwistyObject.MODE_ICON,
-                                                  ShapeHexahedron.DEFAULT_ROT, new Static3D(0,0,0), OBJECT_SIZE, data, null );
-
+     TwistyObject obj = mObject.createObject(TwistyObject.MODE_ICON, OBJECT_SIZE );
      DistortedEffects effects = obj.getObjectEffects();
      DistortedNode node = obj.getNode();
 
@@ -523,7 +362,7 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
        mFramebuffer.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
        }
 
-     mFramebuffer.setProjection( computeProjectionAngle() ,0.1f);
+     mFramebuffer.setProjection( mObject.computeProjectionAngle() ,0.1f);
      mFramebuffer.detachAll();
      mFramebuffer.attach(node);
 
@@ -590,7 +429,7 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
 
    private void rescaleObject()
      {
-     final int size = mX>mY ? Math.max(mX, mZ) : Math.max(mY, mZ);
+     float size = mObject.getSize();
      final float Q = mObjectScreenRatio/size;
      mScaleValue = mWidth<mHeight ? Q*mWidth : Q*mHeight;
      mScale.set( mScaleValue,mScaleValue,mScaleValue );
@@ -600,20 +439,7 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
 
    public void changeObject(int x, int y, int z)
      {
-     if( mX!=x || mY!=y || mZ!=z )
-       {
-       mX = x;
-       mY = y;
-       mZ = z;
-
-       mObjSize[0] = mX;
-       mObjSize[1] = mY;
-       mObjSize[2] = mZ;
-
-       mNumCubits = computeNumCubits(mX,mY,mZ);
-
-       mCreatingCubits = true;
-       }
+     if( mObject.tryChangeObject(x,y,z) ) mCreatingCubits = true;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -644,7 +470,8 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
      if( mInitialPhase && quotient>0.5f )
        {
        mInitialPhase=false;
-       resetObject();
+       mView.resetCubits();
+       mObject.resetObject(mScaleValue);
        }
 
      double angle = 2*Math.PI*quotient*quotient*(3-2*quotient);
@@ -661,14 +488,21 @@ public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, Distorte
 
   public void touchCubit(int index)
     {
-    if( index>=0 && index<mNumCubits && mCubits[index]!=null ) mCubits[index].setMarked();
+    mObject.touchCubit(index);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void untouchCubit(int index)
     {
-    if( index>=0 && index<mNumCubits && mCubits[index]!=null ) mCubits[index].setUnmarked();
+    mObject.untouchCubit(index);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public BandagedCreatorTouchControl createTouchControl()
+    {
+    return new BandagedCreatorTouchControl( getObjectRatio() , mScreen.getFOV(), mObject );
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java b/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
index 41fdea14..d6d904bb 100644
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
@@ -12,6 +12,7 @@ package org.distorted.bandaged;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.content.SharedPreferences;
 import android.graphics.Bitmap;
@@ -124,7 +125,6 @@ public class BandagedCreatorScreen implements AdapterView.OnItemSelectedListener
     return ret;
     }
 
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   private void changeObject()
@@ -319,6 +319,7 @@ public class BandagedCreatorScreen implements AdapterView.OnItemSelectedListener
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+    @SuppressLint("SuspiciousIndentation")
     @Override
     public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
       {
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorTouchControl.java b/src/main/java/org/distorted/bandaged/BandagedCreatorTouchControl.java
index c3267289..3af2e630 100644
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorTouchControl.java
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorTouchControl.java
@@ -12,32 +12,20 @@ package org.distorted.bandaged;
 import org.distorted.library.helpers.QuatHelper;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
-import org.distorted.objectlib.touchcontrol.TouchControlHexahedron;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class BandagedCreatorTouchControl
 {
-  private static final float DIST2D = 0.5f;
-
+  private final BandagedObject mObject;
   private final Static4D CAMERA_POINT;
-  private final float[] mPoint, mCamera, mTouch, mPos;
+  private final float[] mPoint, mCamera, mTouch;
   private final float[] mPoint2D;
   private float mObjectRatio;
   private final Static3D[] mFaceAxis;
-  private final float[] mDist3D;
 
-  private BandagedCubit[] mCubits;
-  private int mNumCubits;
+  private float[] mDist3D;
   private int mLastTouchedFace;
-  private float mX, mY, mZ, mMax;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean isInsideFace(int face, float[] p)
-    {
-    return ( p[0]<=DIST2D && p[0]>=-DIST2D && p[1]<=DIST2D && p[1]>=-DIST2D );
-    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Convert the 3D point3D into a 2D point on the same face surface, but in a different
@@ -118,18 +106,6 @@ public class BandagedCreatorTouchControl
       }
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void stretchPoint(int face, float[] output)
-    {
-    switch(face/2)
-      {
-      case 0: output[0] *= (mMax/mZ); output[1] *= (mMax/mY); break;
-      case 1: output[0] *= (mMax/mX); output[1] *= (mMax/mZ); break;
-      case 2: output[0] *= (mMax/mX); output[1] *= (mMax/mY); break;
-      }
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   private boolean faceIsVisible(int face)
@@ -162,80 +138,27 @@ public class BandagedCreatorTouchControl
         float az = mFaceAxis[mLastTouchedFace].get2();
 
         convertTo2Dcoords(mTouch, ax,ay,az, mPoint2D);
-        stretchPoint(mLastTouchedFace,mPoint2D);
-        if( isInsideFace(mLastTouchedFace,mPoint2D) ) return true;
+        mObject.stretchPoint(mLastTouchedFace,mPoint2D);
+        if( mObject.isInsideFace(mLastTouchedFace,mPoint2D) ) return true;
         }
       }
 
     return false;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computePosition(int face, float pointX, float pointY)
-    {
-    switch(face)
-      {
-      case 0: mPos[0] = (mX-1)/2;
-              mPos[1] = (int)(+mY*pointY+mY/2)-(mY-1)/2;
-              mPos[2] = (int)(-mZ*pointX-mZ/2)+(mZ-1)/2;
-              break;
-      case 1: mPos[0] =-(mX-1)/2;
-              mPos[1] = (int)(+mY*pointY+mY/2)-(mY-1)/2;
-              mPos[2] = (int)(+mZ*pointX+mZ/2)-(mZ-1)/2;
-              break;
-      case 2: mPos[0] = (int)(+mX*pointX+mX/2)-(mX-1)/2;
-              mPos[1] = (mY-1)/2;
-              mPos[2] = (int)(-mZ*pointY-mZ/2)+(mZ-1)/2;
-              break;
-      case 3: mPos[0] = (int)(+mX*pointX+mX/2)-(mX-1)/2;
-              mPos[1] =-(mY-1)/2;
-              mPos[2] = (int)(+mZ*pointY+mZ/2)-(mZ-1)/2;
-              break;
-      case 4: mPos[0] = (int)(+mX*pointX+mX/2)-(mX-1)/2;
-              mPos[1] = (int)(+mY*pointY+mY/2)-(mY-1)/2;
-              mPos[2] = (mZ-1)/2;
-              break;
-      case 5: mPos[0] = (int)(-mX*pointX-mX/2)+(mX-1)/2;
-              mPos[1] = (int)(+mY*pointY+mY/2)-(mY-1)/2;
-              mPos[2] =-(mZ-1)/2;
-              break;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int whichCubitTouched(int face, float pointX, float pointY)
-    {
-    computePosition(face,pointX,pointY);
-
-    for(int c=0; c<mNumCubits; c++)
-      if( mCubits[c].isAttached() )
-        {
-        float[] pos = mCubits[c].getPosition();
-        int len = pos.length/3;
-
-        for(int p=0; p<len; p++)
-          if( pos[3*p]==mPos[0] && pos[3*p+1]==mPos[1] && pos[3*p+2]==mPos[2] ) return c;
-        }
-
-    android.util.Log.e("D", "whichCubitTouched: IMPOSSIBLE!!");
-    return -1;
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public BandagedCreatorTouchControl(float ratio, float fov)
+  public BandagedCreatorTouchControl(float ratio, float fov, BandagedObject object)
     {
+    mObject = object;
     mPoint = new float[3];
     mCamera= new float[3];
     mTouch = new float[3];
-    mPos   = new float[3];
     mPoint2D = new float[2];
     mDist3D  = new float[6];
-    mFaceAxis = TouchControlHexahedron.FACE_AXIS;
+    mFaceAxis = mObject.getFaceAxis();
     mObjectRatio = ratio;
 
     double halfFOV = fov * (Math.PI/360);
@@ -247,19 +170,9 @@ public class BandagedCreatorTouchControl
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void setCubits(BandagedCubit[] cubits, int x, int y, int z)
+  public void setDist3D(float[] dist3d)
     {
-    mCubits = cubits;
-    mNumCubits = cubits.length;
-
-    mX = x;
-    mY = y;
-    mZ = z;
-    mMax = mX>mY ? Math.max(mX,mZ) : Math.max(mY,mZ);
-
-    mDist3D[0] = mDist3D[1] = 0.5f*(mX/mMax);
-    mDist3D[2] = mDist3D[3] = 0.5f*(mY/mMax);
-    mDist3D[4] = mDist3D[5] = 0.5f*(mZ/mMax);
+    mDist3D = dist3d;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -282,7 +195,7 @@ public class BandagedCreatorTouchControl
 
     if( !touched ) return -1;
 
-    return whichCubitTouched(mLastTouchedFace,mPoint2D[0],mPoint2D[1]);
+    return mObject.whichCubitTouched(mLastTouchedFace,mPoint2D[0],mPoint2D[1]);
     }
 }
 
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorView.java b/src/main/java/org/distorted/bandaged/BandagedCreatorView.java
index acf03e90..3e7deb37 100644
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorView.java
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorView.java
@@ -9,6 +9,7 @@
 
 package org.distorted.bandaged;
 
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ConfigurationInfo;
@@ -19,8 +20,6 @@ import android.view.MotionEvent;
 
 import com.google.firebase.crashlytics.FirebaseCrashlytics;
 
-import org.distorted.library.main.DistortedScreen;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class BandagedCreatorView extends GLSurfaceView
@@ -53,8 +52,7 @@ public class BandagedCreatorView extends GLSurfaceView
         {
         BandagedCreatorActivity act = (BandagedCreatorActivity)context;
         mRenderer = new BandagedCreatorRenderer(this);
-        DistortedScreen screen = mRenderer.getScreen();
-        mTouchControl = new BandagedCreatorTouchControl( mRenderer.getObjectRatio() , screen.getFOV() );
+        mTouchControl = mRenderer.createTouchControl();
 
         final ActivityManager activityManager= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
 
@@ -105,13 +103,6 @@ public class BandagedCreatorView extends GLSurfaceView
       return mRenderer;
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void setCubits(BandagedCubit[] cubits, int x, int y, int z)
-      {
-      mTouchControl.setCubits(cubits,x,y,z);
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public void resetCubits()
@@ -321,6 +312,7 @@ public class BandagedCreatorView extends GLSurfaceView
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+    @SuppressLint("ClickableViewAccessibility")
     @Override
     public boolean onTouchEvent(MotionEvent event)
       {
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorWorkerThread.java b/src/main/java/org/distorted/bandaged/BandagedCreatorWorkerThread.java
index 8c5eb71a..2ccb1b08 100644
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorWorkerThread.java
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorWorkerThread.java
@@ -89,7 +89,7 @@ class BandagedCreatorWorkerThread extends Thread
           }
 
         try  { mThis.wait(); }
-        catch(InterruptedException ex) { }
+        catch(InterruptedException ignored) { }
         }
       }
     }
@@ -313,7 +313,7 @@ class BandagedCreatorWorkerThread extends Thread
       if(bos!=null)
         {
         try { bos.close(); }
-        catch(IOException io) {}
+        catch(IOException ignored) {}
         }
       }
 
diff --git a/src/main/java/org/distorted/bandaged/BandagedObject.java b/src/main/java/org/distorted/bandaged/BandagedObject.java
new file mode 100644
index 00000000..69729f79
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedObject.java
@@ -0,0 +1,351 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.bandaged;
+
+import org.distorted.library.main.DistortedNode;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.objectlib.main.InitData;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.objects.TwistyBandagedCuboid;
+import org.distorted.objectlib.shape.ShapeHexahedron;
+import org.distorted.objectlib.touchcontrol.TouchControlHexahedron;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedObject
+{
+    private static final float DIST2D = 0.5f;
+
+    private final DistortedScreen mScreen;
+    private final float[] mPos;
+
+    private BandagedCubit[] mCubits;
+    private int mX, mY, mZ;
+    private int mNumCubits;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   BandagedObject(DistortedScreen screen)
+     {
+     mScreen = screen;
+     mPos = new float[3];
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void resetObject(float scale)
+     {
+     for(int c=0; c<mNumCubits; c++)
+       {
+       if( !mCubits[c].isAttached() )
+         {
+         mCubits[c].attach();
+         mScreen.attach(mCubits[c].getNode());
+         }
+       if( mCubits[c].getPosition().length>3 )
+         {
+         mCubits[c].reset(scale);
+         }
+       mCubits[c].setUnmarked();
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private boolean isAdjacent(float[] pos1, float[] pos2)
+     {
+     int len1 = pos1.length/3;
+     int len2 = pos2.length/3;
+
+     for(int i=0; i<len1; i++)
+       for(int j=0; j<len2; j++)
+         {
+         float d0 = pos1[3*i  ] - pos2[3*j  ];
+         float d1 = pos1[3*i+1] - pos2[3*j+1];
+         float d2 = pos1[3*i+2] - pos2[3*j+2];
+
+         if( d0*d0 + d1*d1 + d2*d2 == 1 ) return true;
+         }
+
+     return false;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private int computeNumCubits(int x, int y, int z)
+     {
+     return ( x<=1 || y<=1 || z<=1 ) ? x*y*z : x*y*z-(x-2)*(y-2)*(z-2);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   float getSize()
+     {
+     return mX>mY ? Math.max(mX, mZ) : Math.max(mY, mZ);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   float[][] getCubitPositions()
+     {
+     int numAttached = 0;
+
+     for(int i=0; i<mNumCubits; i++)
+       if( mCubits[i].isAttached() ) numAttached++;
+
+     float[][] pos = new float[numAttached][];
+     int attached=0;
+
+     for(int i=0; i<mNumCubits; i++)
+       if( mCubits[i].isAttached() )
+         {
+         pos[attached++] = mCubits[i].getPosition();
+         }
+
+     return pos;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   TwistyObject createObject(int mode, float size)
+     {
+     float[][] pos = getCubitPositions();
+     InitData data = new InitData( new int[] {mX,mY,mZ},pos);
+     return new TwistyBandagedCuboid( TwistyObject.MESH_NICE, mode, ShapeHexahedron.DEFAULT_ROT, new Static3D(0,0,0), size, data, null );
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void tryConnectingCubits(int index1, int index2, float scale)
+     {
+     if( index1!=index2 )
+       {
+       float[] pos1 = mCubits[index1].getPosition();
+       float[] pos2 = mCubits[index2].getPosition();
+
+       if( isAdjacent(pos1,pos2) )
+         {
+         mCubits[index2].join(pos1,scale);
+         mCubits[index1].detach();
+         mScreen.detach(mCubits[index1].getNode());
+         }
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void attachCubits(float scale)
+     {
+     for(int i=0; i<mNumCubits; i++)
+       {
+       mCubits[i].scaleMove(scale);
+       DistortedNode node = mCubits[i].getNode();
+       mScreen.attach(node);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void attachAndMarkCubits(float scale, int touched)
+     {
+     for(int i=0; i<mNumCubits; i++)
+       {
+       if( mCubits[i].isAttached() )
+         {
+         mCubits[i].scaleMove(scale);
+         if( touched==i ) mCubits[i].setMarked();
+         else             mCubits[i].setUnmarked();
+         DistortedNode node = mCubits[i].getNode();
+         mScreen.attach(node);
+         }
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   boolean tryChangeObject(int x, int y, int z)
+     {
+     if( mX!=x || mY!=y || mZ!=z )
+       {
+       mX = x;
+       mY = y;
+       mZ = z;
+       mNumCubits = computeNumCubits(mX,mY,mZ);
+       return true;
+       }
+
+     return false;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   int computeProjectionAngle()
+     {
+     float quot1 = mZ/ (float)mX;
+     float quot2 = mZ/ (float)mY;
+     float quot3 = mX/ (float)mZ;
+     float quot4 = mX/ (float)mY;
+
+     float quot5 = Math.max(quot1,quot2);
+     float quot6 = Math.max(quot3,quot4);
+     float quot7 = Math.max(quot5,quot6);
+
+          if( quot7<=1.0f ) return 120;
+     else if( quot7<=1.5f ) return 90;
+     else if( quot7<=2.0f ) return 60;
+     else                   return 30;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void scaleCubits(float scale)
+     {
+     for(int i=0; i<mNumCubits; i++) mCubits[i].scaleMove(scale);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void recreateCubits(Static4D quatT, Static4D quatA, Static3D scale)
+     {
+     if( mCubits==null )
+        {
+        createCubits(quatT,quatA,scale);
+        }
+      else
+        {
+        for(int i=0; i<mNumCubits; i++) mCubits[i].recreateBitmap();
+        }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   float[] getDist3D()
+     {
+     float max = getSize();
+
+     float x = 0.5f*(mX/max);
+     float y = 0.5f*(mY/max);
+     float z = 0.5f*(mZ/max);
+
+     return new float[] {x,x,y,y,z,z};
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void createCubits(Static4D quatT, Static4D quatA, Static3D scale)
+     {
+     mCubits = new BandagedCubit[mNumCubits];
+     int c=0;
+
+     float begX = 0.5f*(1-mX);
+     float begY = 0.5f*(1-mY);
+     float begZ = 0.5f*(1-mZ);
+
+     for(int x=0; x<mX; x++)
+       for(int y=0; y<mY; y++)
+          for(int z=0; z<mZ; z++)
+            if( x==0 || x==mX-1 || y==0 || y==mY-1 || z==0 || z==mZ-1 )
+              {
+              float[] pos = new float[] { begX+x,begY+y,begZ+z };
+              mCubits[c] = new BandagedCubit(pos,mX,mY,mZ,quatT,quatA,scale,false);
+              c++;
+              }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Static3D[] getFaceAxis()
+    {
+    return TouchControlHexahedron.FACE_AXIS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void touchCubit(int index)
+    {
+    if( index>=0 && index<mNumCubits && mCubits[index]!=null ) mCubits[index].setMarked();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void untouchCubit(int index)
+    {
+    if( index>=0 && index<mNumCubits && mCubits[index]!=null ) mCubits[index].setUnmarked();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void stretchPoint(int face, float[] output)
+    {
+    float max = getSize();
+
+    switch(face/2)
+      {
+      case 0: output[0] *= (max/mZ); output[1] *= (max/mY); break;
+      case 1: output[0] *= (max/mX); output[1] *= (max/mZ); break;
+      case 2: output[0] *= (max/mX); output[1] *= (max/mY); break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int whichCubitTouched(int face, float pointX, float pointY)
+    {
+    switch(face)
+      {
+      case 0: mPos[0] = (mX-1)/2.0f;
+              mPos[1] = (int)( mY*pointY+mY/2.0f)-(mY-1)/2.0f;
+              mPos[2] = (int)(-mZ*pointX-mZ/2.0f)+(mZ-1)/2.0f;
+              break;
+      case 1: mPos[0] =-(mX-1)/2.0f;
+              mPos[1] = (int)( mY*pointY+mY/2.0f)-(mY-1)/2.0f;
+              mPos[2] = (int)( mZ*pointX+mZ/2.0f)-(mZ-1)/2.0f;
+              break;
+      case 2: mPos[0] = (int)( mX*pointX+mX/2.0f)-(mX-1)/2.0f;
+              mPos[1] = (mY-1)/2.0f;
+              mPos[2] = (int)(-mZ*pointY-mZ/2.0f)+(mZ-1)/2.0f;
+              break;
+      case 3: mPos[0] = (int)( mX*pointX+mX/2.0f)-(mX-1)/2.0f;
+              mPos[1] =-(mY-1)/2.0f;
+              mPos[2] = (int)( mZ*pointY+mZ/2.0f)-(mZ-1)/2.0f;
+              break;
+      case 4: mPos[0] = (int)( mX*pointX+mX/2.0f)-(mX-1)/2.0f;
+              mPos[1] = (int)( mY*pointY+mY/2.0f)-(mY-1)/2.0f;
+              mPos[2] = (mZ-1)/2.0f;
+              break;
+      case 5: mPos[0] = (int)(-mX*pointX-mX/2.0f)+(mX-1)/2.0f;
+              mPos[1] = (int)( mY*pointY+mY/2.0f)-(mY-1)/2.0f;
+              mPos[2] =-(mZ-1)/2.0f;
+              break;
+      }
+
+    for(int c=0; c<mNumCubits; c++)
+      if( mCubits[c].isAttached() )
+        {
+        float[] pos = mCubits[c].getPosition();
+        int len = pos.length/3;
+
+        for(int p=0; p<len; p++)
+          if( pos[3*p]==mPos[0] && pos[3*p+1]==mPos[1] && pos[3*p+2]==mPos[2] ) return c;
+        }
+
+    android.util.Log.e("D", "whichCubitTouched: IMPOSSIBLE!!");
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean isInsideFace(int face, float[] p)
+    {
+    return ( p[0]<=DIST2D && p[0]>=-DIST2D && p[1]<=DIST2D && p[1]>=-DIST2D );
+    }
+}
