commit 15bcc6f7fc4fc507c95c8691635707caf78f6870
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sat Apr 6 15:58:05 2019 +0100

    Improve the architecture of the Rubik App: new RubikCube class.

diff --git a/src/main/java/org/distorted/examples/rubik/RubikCube.java b/src/main/java/org/distorted/examples/rubik/RubikCube.java
new file mode 100644
index 0000000..ca3b063
--- /dev/null
+++ b/src/main/java/org/distorted/examples/rubik/RubikCube.java
@@ -0,0 +1,349 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.examples.rubik;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.effect.MatrixEffectQuaternion;
+import org.distorted.library.effect.MatrixEffectRotate;
+import org.distorted.library.effect.MatrixEffectScale;
+import org.distorted.library.effect.VertexEffectSink;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshCubes;
+import org.distorted.library.type.Dynamic1D;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class RubikCube
+{
+    static final int TEXTURE_SIZE = 200;
+
+    private static final Static3D VectX = new Static3D(1,0,0);
+    private static final Static3D VectY = new Static3D(0,1,0);
+    private static final Static3D VectZ = new Static3D(0,0,1);
+
+    private MeshCubes[][][] mCubes;
+    private DistortedEffects[][][] mEffects;
+    private Static4D[][][] mQuatScramble;
+    private Static3D[][][] mRotationAxis;
+    private Dynamic1D[][][] mRotationAngle;
+    private Static3D[][][] mCurrentPosition;
+    private Static1D mRotationAngleStatic;
+    private DistortedTexture mTexture;
+
+    private int mRotAxis, mRotRow;
+    private int mSize;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    RubikCube(int size, Static3D move, Static3D scale, Static4D quatC, Static4D quatA)
+      {
+      mSize = size;
+
+      mRotationAngleStatic = new Static1D(0);
+      mRotAxis= RubikSurfaceView.VECTN;
+      mTexture = new DistortedTexture(TEXTURE_SIZE,TEXTURE_SIZE);
+
+      mCubes          = new MeshCubes[mSize][mSize][mSize];
+      mEffects        = new DistortedEffects[mSize][mSize][mSize];
+      mQuatScramble   = new Static4D[mSize][mSize][mSize];
+      mRotationAxis   = new Static3D[mSize][mSize][mSize];
+      mRotationAngle  = new Dynamic1D[mSize][mSize][mSize];
+      mCurrentPosition= new Static3D[mSize][mSize][mSize];
+
+      Static3D[][][] cubeVectors = new Static3D[mSize][mSize][mSize];
+
+      Static3D center = new Static3D(TEXTURE_SIZE*0.5f, TEXTURE_SIZE*0.5f, TEXTURE_SIZE*0.5f);
+      Static4D region = new Static4D(0,0,0, TEXTURE_SIZE*0.72f);
+
+      VertexEffectSink        sinkEffect = new VertexEffectSink( new Static1D(3.0f - 1.8f/mSize), center, region );
+      MatrixEffectMove        moveEffect = new MatrixEffectMove(move);
+      MatrixEffectScale      scaleEffect = new MatrixEffectScale(scale);
+      MatrixEffectQuaternion quatCEffect = new MatrixEffectQuaternion(quatC, center);
+      MatrixEffectQuaternion quatAEffect = new MatrixEffectQuaternion(quatA, center);
+
+      // 3x2 bitmap = 6 squares:
+      //
+      // RED     GREEN   BLUE
+      // YELLOW  WHITE   BROWN
+
+      final float ze = 0.0f;
+      final float ot = 1.0f/3.0f;
+      final float tt = 2.0f/3.0f;
+      final float oh = 1.0f/2.0f;
+      final float of = 1.0f/40.0f;
+
+      final Static4D mapFront = new Static4D(ze,oh, ze+ot,oh+oh);
+      final Static4D mapBack  = new Static4D(tt,ze, tt+ot,ze+oh);
+      final Static4D mapLeft  = new Static4D(ot,ze, ot+ot,ze+oh);
+      final Static4D mapRight = new Static4D(ze,ze, ze+ot,ze+oh);
+      final Static4D mapTop   = new Static4D(tt,oh, tt+ot,oh+oh);
+      final Static4D mapBottom= new Static4D(ot,oh, ot+ot,oh+oh);
+
+      final Static4D mapBlack = new Static4D(ze,ze, ze+of,ze+of);
+
+      Static4D tmpFront, tmpBack, tmpLeft, tmpRight, tmpTop, tmpBottom;
+      float nc = 0.5f*(mSize-1);
+      int vertices = (int)(24.0f/mSize + 2.0f);
+
+      for(int x = 0; x< mSize; x++)
+        for(int y = 0; y< mSize; y++)
+          for(int z = 0; z< mSize; z++)
+            {
+            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 ) // only the external walls
+              {
+              tmpLeft  = (x==       0 ? mapLeft  :mapBlack);
+              tmpRight = (x== mSize-1 ? mapRight :mapBlack);
+              tmpFront = (z== mSize-1 ? mapFront :mapBlack);
+              tmpBack  = (z==       0 ? mapBack  :mapBlack);
+              tmpTop   = (y== mSize-1 ? mapTop   :mapBlack);
+              tmpBottom= (y==       0 ? mapBottom:mapBlack);
+
+              mCubes[x][y][z]           = new MeshCubes(vertices,vertices,vertices, tmpFront, tmpBack, tmpLeft, tmpRight, tmpTop, tmpBottom);
+              cubeVectors[x][y][z]      = new Static3D( TEXTURE_SIZE*(x-nc), TEXTURE_SIZE*(y-nc), TEXTURE_SIZE*(z-nc) );
+              mQuatScramble[x][y][z]    = new Static4D(0,0,0,1);
+              mRotationAngle[x][y][z]   = new Dynamic1D();
+              mRotationAxis[x][y][z]    = new Static3D(1,0,0);
+              mCurrentPosition[x][y][z] = new Static3D(x,y,z);
+
+              mEffects[x][y][z] = new DistortedEffects();
+              mEffects[x][y][z].apply(sinkEffect);
+              mEffects[x][y][z].apply(moveEffect);
+              mEffects[x][y][z].apply(scaleEffect);
+              mEffects[x][y][z].apply(quatCEffect);
+              mEffects[x][y][z].apply(quatAEffect);
+              mEffects[x][y][z].apply( new MatrixEffectRotate( mRotationAngle[x][y][z], mRotationAxis[x][y][z], center));
+              mEffects[x][y][z].apply( new MatrixEffectQuaternion(mQuatScramble[x][y][z], center));
+              mEffects[x][y][z].apply( new MatrixEffectMove(cubeVectors[x][y][z]) );
+              }
+            }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void attachToScreen(DistortedScreen screen)
+      {
+      for(int x=0; x<mSize; x++)
+        for(int y=0; y<mSize; y++)
+          for(int z=0; z<mSize; z++)
+            {
+            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
+              {
+              screen.attach(mTexture,mEffects[x][y][z],mCubes[x][y][z]);
+              }
+            }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void addNewRotation(int vector, int row )
+      {
+      Static3D axis = VectX;
+
+      switch(vector)
+        {
+        case RubikSurfaceView.VECTX: axis = VectX; break;
+        case RubikSurfaceView.VECTY: axis = VectY; break;
+        case RubikSurfaceView.VECTZ: axis = VectZ; break;
+        }
+
+      mRotAxis = vector;
+      mRotRow  = row;
+
+      mRotationAngleStatic.set1(0.0f);
+
+      for(int x=0; x<mSize; x++)
+        for(int y=0; y<mSize; y++)
+          for(int z=0; z<mSize; z++)
+            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
+              {
+              if( belongsToRotation(x,y,z,vector,row) )
+                {
+                mRotationAxis[x][y][z].set(axis);
+                mRotationAngle[x][y][z].add(mRotationAngleStatic);
+                }
+              }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void continueRotation(float angle, int screenMin)
+      {
+      mRotationAngleStatic.set1(200.0f*angle/screenMin);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void finishRotationCalledOnNextRender()
+      {
+      float nearestAngle = (mRotationAngleStatic.get1()+45.0f)/90.0f;
+      if( nearestAngle<0 ) nearestAngle-=1.0f;
+      int nearestAngleInDegrees = 90*(4-((int)nearestAngle+4)%4);
+      double nearestAngleInRadians = nearestAngleInDegrees*Math.PI/180;
+      float sinA = (float)Math.sin(nearestAngleInRadians*0.5);
+      float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
+
+      mRotationAngleStatic.set1(0);
+
+      float qx=0,qy=0,qz=0;
+
+      switch(mRotAxis)
+        {
+        case RubikSurfaceView.VECTX: qx=1; break;
+        case RubikSurfaceView.VECTY: qy=1; break;
+        case RubikSurfaceView.VECTZ: qz=1; break;
+        }
+
+      Static4D quat = new Static4D(qx*sinA, qy*sinA, qz*sinA, cosA);
+
+      for(int x=0; x<mSize; x++)
+        for(int y=0; y<mSize; y++)
+          for(int z=0; z<mSize; z++)
+            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
+              {
+              if( belongsToRotation(x,y,z,mRotAxis,mRotRow) )
+                {
+                mRotationAngle[x][y][z].removeAll();
+                mQuatScramble[x][y][z].set(RubikSurfaceView.quatMultiply(quat,mQuatScramble[x][y][z]));
+                modifyCurrentPosition(x,y,z,quat);
+                }
+              }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private boolean belongsToRotation(int x, int y, int z, int vector, int row)
+      {
+      switch(vector)
+        {
+        case RubikSurfaceView.VECTX: return mCurrentPosition[x][y][z].get1()==row;
+        case RubikSurfaceView.VECTY: return mCurrentPosition[x][y][z].get2()==row;
+        case RubikSurfaceView.VECTZ: return mCurrentPosition[x][y][z].get3()==row;
+        }
+
+      return false;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void modifyCurrentPosition(int x, int y, int z, Static4D quat)
+      {
+      Static3D current = mCurrentPosition[x][y][z];
+      float beforeX = current.get1();
+      float beforeY = current.get2();
+      float beforeZ = current.get3();
+
+      float diff = 0.5f*(mSize-1);
+
+      float cubitCenterX = beforeX - diff;
+      float cubitCenterY = beforeY - diff;
+      float cubitCenterZ = beforeZ - diff;
+
+      Static4D cubitCenter =  new Static4D(cubitCenterX, cubitCenterY, cubitCenterZ, 0);
+      Static4D rotatedCenter = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat);
+
+      float rotatedX = rotatedCenter.get1() + diff;
+      float rotatedY = rotatedCenter.get2() + diff;
+      float rotatedZ = rotatedCenter.get3() + diff;
+
+      int roundedX = (int)(rotatedX+0.1f);
+      int roundedY = (int)(rotatedY+0.1f);
+      int roundedZ = (int)(rotatedZ+0.1f);
+
+      //android.util.Log.e("rubik", "before: ("+((int)beforeX)+","+((int)beforeY)+","+((int)beforeZ)+") after: ("+roundedX+","+roundedY+","+roundedZ+")");
+
+      mCurrentPosition[x][y][z].set1(roundedX);
+      mCurrentPosition[x][y][z].set2(roundedY);
+      mCurrentPosition[x][y][z].set3(roundedZ);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void createTexture()
+      {
+      Bitmap bitmap;
+
+      final int S = 128;
+      final int W = 3*S;
+      final int H = 2*S;
+      final int R = S/10;
+      final int M = S/20;
+
+      Paint paint = new Paint();
+      bitmap = Bitmap.createBitmap(W,H, Bitmap.Config.ARGB_8888);
+      Canvas canvas = new Canvas(bitmap);
+
+      paint.setAntiAlias(true);
+      paint.setTextAlign(Paint.Align.CENTER);
+      paint.setStyle(Paint.Style.FILL);
+
+      // 3x2 bitmap = 6 squares:
+      //
+      // RED     GREEN   BLUE
+      // YELLOW  WHITE   BROWN
+
+      paint.setColor(0xff000000);                                  // BLACK BACKGROUND
+      canvas.drawRect(0, 0, W, H, paint);                          //
+
+      paint.setColor(0xffff0000);                                  // RED
+      canvas.drawRoundRect(    M,   M,   S-M,   S-M, R, R, paint); //
+      paint.setColor(0xff00ff00);                                  // GREEN
+      canvas.drawRoundRect(  S+M,   M, 2*S-M,   S-M, R, R, paint); //
+      paint.setColor(0xff0000ff);                                  // BLUE
+      canvas.drawRoundRect(2*S+M,   M, 3*S-M,   S-M, R, R, paint); //
+      paint.setColor(0xffffff00);                                  // YELLOW
+      canvas.drawRoundRect(    M, S+M,   S-M, 2*S-M, R, R, paint); //
+      paint.setColor(0xffffffff);                                  // WHITE
+      canvas.drawRoundRect(  S+M, S+M, 2*S-M, 2*S-M, R, R, paint); //
+      paint.setColor(0xffb5651d);                                  // BROWN
+      canvas.drawRoundRect(2*S+M, S+M, 3*S-M, 2*S-M, R, R, paint); //
+
+      mTexture.setTexture(bitmap);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    float getWidth()
+      {
+      return mTexture.getWidth();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    float getHeight()
+      {
+      return mTexture.getHeight();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    float getDepth()
+      {
+      return mTexture.getDepth(mCubes[0][0][0]);
+      }
+}
