commit e844c1166b79fc62e8f1fda1cb4b1febbc98f48d
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Mar 5 23:42:50 2020 +0000

    Beginnings of Pyraminx.

diff --git a/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java b/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
index 8edd2bae..d1b0c470 100644
--- a/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
+++ b/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
@@ -70,6 +70,8 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
   private long mDurationSingleTurn;
   private Random mRnd;
   private RubikObject mObject;
+  private int mNumAxis;
+  private int mBasicAngle;
 
   Effect[] mNodeEffects;
   int[] mNodeEffectPosition;
@@ -82,7 +84,7 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
   ScrambleEffect()
     {
     mRnd = new Random( System.currentTimeMillis() );
-    mLastVector = 3;
+    mLastVector = -2;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -93,6 +95,11 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // first compute how many out of 'numScrambles' are double turns (this will matter when we compute
 // the time a single quarter-turn takes!
+//
+// Only works for
+// basicAngle==4, i.e. something whose rotations are by 90 degrees (RubikCube) or
+// basicAngle==3, i.e. something whose rotations are by 120 degrees (a Pyramix) or
+// basicAngle==2, i.e. something whose rotations are by 180 degrees (is there something like that?)
 
   private void createBaseEffects(int duration, int numScrambles)
     {
@@ -100,11 +107,14 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 
     mNumDoubleScramblesLeft=0;
 
-    for(int i=0; i<numScrambles; i++)
+    if( mBasicAngle>=4 )
       {
-      if( (mRnd.nextInt() % 3) == 0 )
+      for(int i=0; i<numScrambles; i++)
         {
-        mNumDoubleScramblesLeft++;
+        if( (mRnd.nextInt() % 3) == 0 )
+          {
+          mNumDoubleScramblesLeft++;
+          }
         }
       }
 
@@ -114,25 +124,21 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// only works if basicAngle<=4, i.e. wont work for something whose basic rotations are by less
+// than 90 degrees.
 
   private void addNewScramble()
     {
     if( mNumScramblesLeft>0 )
       {
-      if( mLastVector == 3 )
+      if( mLastVector == -2 )
         {
-        mLastVector = mRnd.nextInt(3);
+        mLastVector = mRnd.nextInt(mNumAxis);
         }
       else
         {
-        int newVector = mRnd.nextInt(2);
-
-        switch(mLastVector)
-          {
-          case 0: mLastVector = (newVector==0 ? 1:2); break;
-          case 1: mLastVector = (newVector==0 ? 0:2); break;
-          case 2: mLastVector = (newVector==0 ? 0:1); break;
-          }
+        int newVector = mRnd.nextInt(mNumAxis-1);
+        mLastVector = (newVector>=mLastVector ? newVector+1 : newVector);
         }
 
       int row  = mRnd.nextInt(mObject.getSize());
@@ -162,6 +168,7 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// only works for basicAngle==4, i.e. RubikCube or basicAngle==3, i.e. a Pyramix.
 
   private int randomizeAngle()
     {
@@ -301,6 +308,9 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 
     mObject.solve();
 
+    mNumAxis    = mObject.getNumAxis();
+    mBasicAngle = mObject.getBasicAngle();
+
     int numScrambles = renderer.getNumScrambles();
     int dura = (int)(duration*Math.pow(numScrambles,0.6f));
     createBaseEffects(dura,numScrambles);
diff --git a/src/main/java/org/distorted/object/Cubit.java b/src/main/java/org/distorted/object/Cubit.java
index 2e92ef4b..6d853d17 100644
--- a/src/main/java/org/distorted/object/Cubit.java
+++ b/src/main/java/org/distorted/object/Cubit.java
@@ -47,6 +47,7 @@ class Cubit
   private Static3D mRotationAxis;
   private MatrixEffectRotate mRotateEffect;
   private Static3D mCurrentPosition;
+  private int mNumAxis;
 
   Dynamic1D mRotationAngle;
   DistortedNode mNode;
@@ -155,13 +156,22 @@ class Cubit
 
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO: this is only right in case of RubikCube
+// cast current position on axis; use mStart and mStep to compute the rotation row for each axis.
 
   private void computeRotationRow()
     {
-    mRotationRow[0] = (int)mCurrentPosition.get0();
-    mRotationRow[1] = (int)mCurrentPosition.get1();
-    mRotationRow[2] = (int)mCurrentPosition.get2();
+    float tmp;
+    Static3D axis;
+    float x = mCurrentPosition.get0();
+    float y = mCurrentPosition.get1();
+    float z = mCurrentPosition.get2();
+
+    for(int i=0; i<mNumAxis; i++)
+      {
+      axis = mParent.ROTATION_AXIS[i];
+      tmp = x*axis.get0() + y*axis.get1() + z*axis.get2();
+      mRotationRow[i] = (int)( (tmp-mParent.mStart)/mParent.mStep + 0.5f );
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -184,7 +194,8 @@ class Cubit
     mCurrentPosition = position;
     mRotateEffect    = new MatrixEffectRotate(mRotationAngle, mRotationAxis, matrCenter);
 
-    mRotationRow = new int[mParent.ROTATION_AXIS.length];
+    mNumAxis     = mParent.ROTATION_AXIS.length;
+    mRotationRow = new int[mNumAxis];
     computeRotationRow();
 
     mEffect = new DistortedEffects();
diff --git a/src/main/java/org/distorted/object/RubikCube.java b/src/main/java/org/distorted/object/RubikCube.java
index 5b7cb048..654e141f 100644
--- a/src/main/java/org/distorted/object/RubikCube.java
+++ b/src/main/java/org/distorted/object/RubikCube.java
@@ -38,7 +38,7 @@ import org.distorted.library.type.Static4D;
 
 class RubikCube extends RubikObject
 {
-  // the three rotation axis of a RubikCube
+  // the three rotation axis of a RubikCube. Must be normalized.
   private static final Static3D[] AXIS = new Static3D[]
          {
            new Static3D(1,0,0),
@@ -171,4 +171,12 @@ class RubikCube extends RubikObject
 
     return new MeshJoined(meshes);
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public int getBasicAngle()
+    {
+    return 4;
+    }
 }
diff --git a/src/main/java/org/distorted/object/RubikObject.java b/src/main/java/org/distorted/object/RubikObject.java
index 606debf5..25baf97e 100644
--- a/src/main/java/org/distorted/object/RubikObject.java
+++ b/src/main/java/org/distorted/object/RubikObject.java
@@ -58,6 +58,7 @@ public abstract class RubikObject extends DistortedNode
   private Static4D mQuatAccumulated;
   private Cubit[] mCubits;
 
+  float mStart, mStep;
   int mSize;
 
   Static1D mRotationAngleStatic, mRotationAngleMiddle, mRotationAngleFinal;
@@ -84,6 +85,8 @@ public abstract class RubikObject extends DistortedNode
 
     mSize = size;
 
+    computeStartAndStep(positions);
+
     mRotationAngleStatic = new Static1D(0);
     mRotationAngleMiddle = new Static1D(0);
     mRotationAngleFinal  = new Static1D(0);
@@ -148,6 +151,28 @@ public abstract class RubikObject extends DistortedNode
     mesh.setTextureMap(maps);
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeStartAndStep(Static3D[] pos)
+    {
+    float min = Float.MAX_VALUE;
+    float max = Float.MIN_VALUE;
+    float axisX = ROTATION_AXIS[0].get0();
+    float axisY = ROTATION_AXIS[0].get1();
+    float axisZ = ROTATION_AXIS[0].get2();
+    float tmp;
+
+    for(int i=0; i<NUM_CUBITS; i++)
+      {
+      tmp = pos[i].get0()*axisX + pos[i].get1()*axisY + pos[i].get2()*axisZ;
+      if( tmp<min ) min=tmp;
+      if( tmp>max ) max=tmp;
+      }
+
+    mStart = min;
+    mStep  = (max-min+1.0f)/mSize;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   private boolean belongsToRotation( int cubit, int axis, int row)
@@ -396,7 +421,7 @@ public abstract class RubikObject extends DistortedNode
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public int getNumRotations()
+  public int getNumAxis()
     {
     return ROTATION_AXIS.length;
     }
@@ -409,4 +434,5 @@ public abstract class RubikObject extends DistortedNode
   abstract void createFaceTexture(Canvas canvas, Paint paint, int face);
   abstract MeshBase createCubitMesh(int vertices);
   abstract Static3D[] getRotationAxis();
+  public abstract int getBasicAngle();
   }
diff --git a/src/main/java/org/distorted/object/RubikPyraminx.java b/src/main/java/org/distorted/object/RubikPyraminx.java
new file mode 100644
index 00000000..df646597
--- /dev/null
+++ b/src/main/java/org/distorted/object/RubikPyraminx.java
@@ -0,0 +1,113 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 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.object;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.mesh.MeshRectangles;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikPyraminx extends RubikObject
+{
+  private static final Static3D[] AXIS = new Static3D[]
+         {
+           new Static3D(                     0,                       0,       1 ),
+           new Static3D( (float)Math.sqrt(6)/3,  -(float)Math.sqrt(3)/3, -1.0f/3 ),
+           new Static3D(-(float)Math.sqrt(6)/3,  -(float)Math.sqrt(3)/3, -1.0f/3 ),
+           new Static3D(                     0, 2*(float)Math.sqrt(2)/3, -1.0f/3 )
+         };
+
+  private static final int[] FACE_COLORS = new int[]
+         {
+           0xffffff00, 0xff00ff00,  // AXIS[0]right (YELLOW) AXIS[1]right (GREEN )
+           0xff0000ff, 0xffff0000   // AXIS[2]right (BLUE  ) AXIS[3]right (RED   )
+         };
+
+  private static final float[] LEGALQUATS = new float[]
+         {
+         // TODO;
+         };
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikPyraminx(int size, Static4D quatCur, Static4D quatAcc, DistortedTexture texture, MeshRectangles mesh, DistortedEffects effects)
+    {
+    super(size,quatCur,quatAcc,texture,mesh,effects);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  Static3D[] getCubitPositions(int size)
+    {
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[] getLegalQuats()
+    {
+    return LEGALQUATS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumFaces()
+    {
+    return FACE_COLORS.length;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Static3D[] getRotationAxis()
+    {
+    return AXIS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void createFaceTexture(Canvas canvas, Paint paint, int face)
+    {
+    // TODO
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  MeshBase createCubitMesh(int vertices)
+    {
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public int getBasicAngle()
+    {
+    return 3;
+    }
+}
