| 1 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 2 | // Copyright 2020 Leszek Koltunski                                                               //
 | 
  
    | 3 | //                                                                                               //
 | 
  
    | 4 | // This file is part of Magic Cube.                                                              //
 | 
  
    | 5 | //                                                                                               //
 | 
  
    | 6 | // Magic Cube is proprietary software licensed under an EULA which you should have received      //
 | 
  
    | 7 | // along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
 | 
  
    | 8 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 9 | 
 | 
  
    | 10 | package org.distorted.objectlib.touchcontrol;
 | 
  
    | 11 | 
 | 
  
    | 12 | import org.distorted.library.helpers.QuatHelper;
 | 
  
    | 13 | import org.distorted.library.type.Static4D;
 | 
  
    | 14 | import org.distorted.objectlib.main.TwistyObject;
 | 
  
    | 15 | 
 | 
  
    | 16 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 17 | 
 | 
  
    | 18 | public abstract class TouchControl
 | 
  
    | 19 |   {
 | 
  
    | 20 |   // it doesn't matter where we touch a face - the list of enabled rotAxis will always be the same
 | 
  
    | 21 |   public static final int TYPE_NOT_SPLIT      = 0;
 | 
  
    | 22 |   // each face is split into several parts by lines coming from its center to the midpoints of each edge
 | 
  
    | 23 |   public static final int TYPE_SPLIT_EDGE     = 1;
 | 
  
    | 24 |   // each face is split into several parts by lines coming from its center to the vertices
 | 
  
    | 25 |   public static final int TYPE_SPLIT_CORNER   = 2;
 | 
  
    | 26 |   // each face is split into several parts by lines coming from its center to the midpoints of each edge,
 | 
  
    | 27 |   // and also it has an inscribed circle (see coin tetrahedron!)
 | 
  
    | 28 |   public static final int TYPE_SPLIT_EDGE_COIN= 3;
 | 
  
    | 29 | 
 | 
  
    | 30 |   static final float D_TRIANGLE = 0.95f;
 | 
  
    | 31 |   static final float D_SQUARE   = 0.75f;
 | 
  
    | 32 |   static final float D_PENTA    = 0.65f;
 | 
  
    | 33 | 
 | 
  
    | 34 |   public static final int TC_HEXAHEDRON        =   6;
 | 
  
    | 35 |   public static final int TC_TETRAHEDRON       =   4;
 | 
  
    | 36 |   public static final int TC_OCTAHEDRON        =   8;
 | 
  
    | 37 |   public static final int TC_DODECAHEDRON      =  12;
 | 
  
    | 38 |   public static final int TC_ICOSAHEDRON       =  20;
 | 
  
    | 39 |   public static final int TC_CUBOID            =   0;
 | 
  
    | 40 |   public static final int TC_BALL              =   1;
 | 
  
    | 41 |   public static final int TC_BARREL            =   2;
 | 
  
    | 42 |   public static final int TC_CHANGING_MIRROR   = 100;
 | 
  
    | 43 |   public static final int TC_CHANGING_SQUARE   = 101;
 | 
  
    | 44 |   public static final int TC_CHANGING_SHAPEMOD = 102;
 | 
  
    | 45 | 
 | 
  
    | 46 |   float mObjectRatio;
 | 
  
    | 47 |   int mGhostAxisEnabled;
 | 
  
    | 48 |   float[][] mTouchBorders;
 | 
  
    | 49 | 
 | 
  
    | 50 |   private final float[][] mRotationFactor;
 | 
  
    | 51 | 
 | 
  
    | 52 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 53 | 
 | 
  
    | 54 |   public TouchControl(TwistyObject object)
 | 
  
    | 55 |     {
 | 
  
    | 56 |     mObjectRatio = (object!=null ? object.getObjectRatio() : 1.0f);
 | 
  
    | 57 |     mRotationFactor = (object!=null ? object.returnRotationFactor() : null);
 | 
  
    | 58 |     }
 | 
  
    | 59 | 
 | 
  
    | 60 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 61 | 
 | 
  
    | 62 |   public void setObjectRatio(float ratio)
 | 
  
    | 63 |     {
 | 
  
    | 64 |     mObjectRatio = ratio;
 | 
  
    | 65 |     }
 | 
  
    | 66 | 
 | 
  
    | 67 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 68 | // Convert the 3D point3D into a 2D point on the same face surface, but in a different
 | 
  
    | 69 | // coordinate system: a in-plane 2D coord where the origin is in the point where the axis intersects
 | 
  
    | 70 | // the surface, and whose Y axis points 'north' i.e. is in the plane given by the 3D origin, the
 | 
  
    | 71 | // original 3D Y axis and our 2D in-plane origin.
 | 
  
    | 72 | // If those 3 points constitute a degenerate triangle which does not define a plane - which can only
 | 
  
    | 73 | // happen if axis is vertical (or in theory when 2D origin and 3D origin meet, but that would have to
 | 
  
    | 74 | // mean that the distance between the center of the Object and its faces is 0) - then we arbitrarily
 | 
  
    | 75 | // decide that 2D Y = (0,0,-1) in the North Pole and (0,0,1) in the South Pole)
 | 
  
    | 76 | // (ax,ay,az) - vector normal to the face surface.
 | 
  
    | 77 | 
 | 
  
    | 78 |   void convertTo2Dcoords(float[] point3D, float ax, float ay, float az , float[] output)
 | 
  
    | 79 |     {
 | 
  
    | 80 |     float y0,y1,y2; // base Y vector of the 2D coord system
 | 
  
    | 81 | 
 | 
  
    | 82 |     if( ax==0.0f && az==0.0f )
 | 
  
    | 83 |       {
 | 
  
    | 84 |       y0=0; y1=0; y2=-ay;
 | 
  
    | 85 |       }
 | 
  
    | 86 |     else if( ay==0.0f )
 | 
  
    | 87 |       {
 | 
  
    | 88 |       y0=0; y1=1; y2=0;
 | 
  
    | 89 |       }
 | 
  
    | 90 |     else
 | 
  
    | 91 |       {
 | 
  
    | 92 |       float norm = (float)(-ay/Math.sqrt(1-ay*ay));
 | 
  
    | 93 |       y0 = norm*ax; y1= norm*(ay-1/ay); y2=norm*az;
 | 
  
    | 94 |       }
 | 
  
    | 95 | 
 | 
  
    | 96 |     float x0 = y1*az - y2*ay;  //
 | 
  
    | 97 |     float x1 = y2*ax - y0*az;  // (2D coord baseY) x (axis) = 2D coord baseX
 | 
  
    | 98 |     float x2 = y0*ay - y1*ax;  //
 | 
  
    | 99 | 
 | 
  
    | 100 |     float originAlpha = point3D[0]*ax + point3D[1]*ay + point3D[2]*az;
 | 
  
    | 101 | 
 | 
  
    | 102 |     float origin0 = originAlpha*ax; // coords of the point where axis
 | 
  
    | 103 |     float origin1 = originAlpha*ay; // intersects surface plane i.e.
 | 
  
    | 104 |     float origin2 = originAlpha*az; // the origin of our 2D coord system
 | 
  
    | 105 | 
 | 
  
    | 106 |     float v0 = point3D[0] - origin0;
 | 
  
    | 107 |     float v1 = point3D[1] - origin1;
 | 
  
    | 108 |     float v2 = point3D[2] - origin2;
 | 
  
    | 109 | 
 | 
  
    | 110 |     output[0] = v0*x0 + v1*x1 + v2*x2;
 | 
  
    | 111 |     output[1] = v0*y0 + v1*y1 + v2*y2;
 | 
  
    | 112 |     }
 | 
  
    | 113 | 
 | 
  
    | 114 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 115 | // find the casted axis with which our move2D vector forms an angle closest to 90 deg.
 | 
  
    | 116 | 
 | 
  
    | 117 |   int computeRotationIndex(float[][] rotAxis, float[] move2D, int[] enabled)
 | 
  
    | 118 |     {
 | 
  
    | 119 |     float cosAngle, minCosAngle = Float.MAX_VALUE;
 | 
  
    | 120 |     int minIndex=0, index;
 | 
  
    | 121 |     float m0 = move2D[0];
 | 
  
    | 122 |     float m1 = move2D[1];
 | 
  
    | 123 |     int numAxis = enabled[0];
 | 
  
    | 124 | 
 | 
  
    | 125 |     for(int axis=1; axis<=numAxis; axis++)
 | 
  
    | 126 |       {
 | 
  
    | 127 |       index = enabled[axis];
 | 
  
    | 128 |       cosAngle = m0*rotAxis[index][0] + m1*rotAxis[index][1];
 | 
  
    | 129 |       if( cosAngle<0 ) cosAngle = -cosAngle;
 | 
  
    | 130 | 
 | 
  
    | 131 |       if( cosAngle<minCosAngle )
 | 
  
    | 132 |         {
 | 
  
    | 133 |         minCosAngle=cosAngle;
 | 
  
    | 134 |         minIndex = index;
 | 
  
    | 135 |         }
 | 
  
    | 136 |       }
 | 
  
    | 137 | 
 | 
  
    | 138 |     return minIndex;
 | 
  
    | 139 |     }
 | 
  
    | 140 | 
 | 
  
    | 141 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 142 | 
 | 
  
    | 143 |   public float returnRotationFactor(int axis, int row)
 | 
  
    | 144 |     {
 | 
  
    | 145 |     return mRotationFactor==null ? 1.0f : mRotationFactor[axis][row];
 | 
  
    | 146 |     }
 | 
  
    | 147 | 
 | 
  
    | 148 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 149 | 
 | 
  
    | 150 |   public void enableGhostAxis(int axNum, boolean enable)
 | 
  
    | 151 |     {
 | 
  
    | 152 |     mGhostAxisEnabled = enable ? -1 : axNum;
 | 
  
    | 153 |     }
 | 
  
    | 154 | 
 | 
  
    | 155 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 156 | 
 | 
  
    | 157 |   private float[] computeBorder(float[] cuts, boolean[] rotatable, float size)
 | 
  
    | 158 |     {
 | 
  
    | 159 |     if( cuts==null ) return null;
 | 
  
    | 160 | 
 | 
  
    | 161 |     int len = cuts.length;
 | 
  
    | 162 |     float[] border = new float[len];
 | 
  
    | 163 | 
 | 
  
    | 164 |     for(int i=0; i<len; i++)
 | 
  
    | 165 |       {
 | 
  
    | 166 |       if( !rotatable[i] )
 | 
  
    | 167 |         {
 | 
  
    | 168 |         border[i] = i>0 ? border[i-1] : -Float.MAX_VALUE;
 | 
  
    | 169 |         }
 | 
  
    | 170 |       else
 | 
  
    | 171 |         {
 | 
  
    | 172 |         if( rotatable[i+1] ) border[i] = cuts[i]/size;
 | 
  
    | 173 |         else
 | 
  
    | 174 |           {
 | 
  
    | 175 |           int found = -1;
 | 
  
    | 176 | 
 | 
  
    | 177 |           for(int j=i+2; j<=len; j++)
 | 
  
    | 178 |             {
 | 
  
    | 179 |             if( rotatable[j] )
 | 
  
    | 180 |               {
 | 
  
    | 181 |               found=j;
 | 
  
    | 182 |               break;
 | 
  
    | 183 |               }
 | 
  
    | 184 |             }
 | 
  
    | 185 | 
 | 
  
    | 186 |           border[i] = found>0 ? (cuts[i]+cuts[found-1])/(2*size) : Float.MAX_VALUE;
 | 
  
    | 187 |           }
 | 
  
    | 188 |         }
 | 
  
    | 189 |       }
 | 
  
    | 190 | 
 | 
  
    | 191 |     return border;
 | 
  
    | 192 |     }
 | 
  
    | 193 | 
 | 
  
    | 194 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 195 | // size, not numLayers (see Master Skewb where size!=numLayers) - also cuboids.
 | 
  
    | 196 | 
 | 
  
    | 197 |   void computeBorders(float[][] cuts, boolean[][] rotatable, float size)
 | 
  
    | 198 |     {
 | 
  
    | 199 |     int numCuts = cuts.length;
 | 
  
    | 200 |     mTouchBorders = new float[numCuts][];
 | 
  
    | 201 | 
 | 
  
    | 202 |     for(int axis=0; axis<numCuts; axis++)
 | 
  
    | 203 |       {
 | 
  
    | 204 |       mTouchBorders[axis] = computeBorder(cuts[axis],rotatable[axis],size);
 | 
  
    | 205 |       }
 | 
  
    | 206 |     }
 | 
  
    | 207 | 
 | 
  
    | 208 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 209 | // cast the 3D axis we are currently rotating along (which is already casted to the surface of the
 | 
  
    | 210 | // currently touched face AND converted into a 4D vector - fourth 0) to a 2D in-screen-surface axis
 | 
  
    | 211 | 
 | 
  
    | 212 |   void getCastedRotAxis(float[] output, Static4D quat, float x, float y, float z, float w)
 | 
  
    | 213 |     {
 | 
  
    | 214 |     Static4D result = QuatHelper.rotateVectorByQuat(x,y,z,w, quat);
 | 
  
    | 215 | 
 | 
  
    | 216 |     float cx = result.get0();
 | 
  
    | 217 |     float cy = result.get1();
 | 
  
    | 218 |     float len= (float)Math.sqrt(cx*cx+cy*cy);
 | 
  
    | 219 | 
 | 
  
    | 220 |     if( len!=0 )
 | 
  
    | 221 |       {
 | 
  
    | 222 |       output[0] = cx/len;
 | 
  
    | 223 |       output[1] = cy/len;
 | 
  
    | 224 |       }
 | 
  
    | 225 |     else
 | 
  
    | 226 |       {
 | 
  
    | 227 |       output[0] = 1;
 | 
  
    | 228 |       output[1] = 0;
 | 
  
    | 229 |       }
 | 
  
    | 230 |     }
 | 
  
    | 231 | 
 | 
  
    | 232 | ///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
  
    | 233 | 
 | 
  
    | 234 |   public abstract boolean objectTouched(Static4D rotatedTouchPoint, Static4D rotatedCamera);
 | 
  
    | 235 |   public abstract void newRotation(int[] output, Static4D rotatedTouchPoint, Static4D quat);
 | 
  
    | 236 |   public abstract void getCastedRotAxis(float[] output, Static4D quat, int rotIndex);
 | 
  
    | 237 |   public abstract boolean axisAndFaceAgree(int rotIndex);
 | 
  
    | 238 |   public abstract int getTouchedCubitFace();
 | 
  
    | 239 |   public abstract int getTouchedCubit();
 | 
  
    | 240 |   public abstract float[] getTouchedPuzzleCenter();
 | 
  
    | 241 |   }
 |