commit 49cd85813a2873fe82fc8ac8b0d45772a0b554df
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon Oct 19 01:41:04 2020 +0100

    Beginnings of the Ivy Cube.

diff --git a/src/main/java/org/distorted/objects/FactoryCubit.java b/src/main/java/org/distorted/objects/FactoryCubit.java
index 598968b5..27c14939 100644
--- a/src/main/java/org/distorted/objects/FactoryCubit.java
+++ b/src/main/java/org/distorted/objects/FactoryCubit.java
@@ -39,6 +39,9 @@ class FactoryCubit
   private static final float SQ3 = (float)Math.sqrt(3);
   private static final float SQ6 = (float)Math.sqrt(6);
 
+  private static final float IVY_D = 0.05f;
+  private static final int   IVY_N = 8;
+
   private static final Static1D RADIUS = new Static1D(1);
 
   private static FactoryCubit mThis;
@@ -495,6 +498,46 @@ class FactoryCubit
     return new MeshJoined(meshes);
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MeshBase createFacesIvyCorner()
+    {
+    MeshBase mesh = createFacesSkewbCorner();
+    mesh.addEmptyTexComponent();
+    return mesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MeshBase createFacesIvyFace()
+    {
+    MeshBase[] meshes = new MeshBase[2];
+
+    final float angle = (float)Math.PI/(2*IVY_N);
+    float[] vertices = new float[4*IVY_N];
+
+    for(int i=0; i<IVY_N; i++)
+      {
+      float sin = (float)Math.sin(i*angle);
+      float cos = (float)Math.cos(i*angle);
+
+      vertices[2*i          ] = 0.5f-cos;
+      vertices[2*i+1        ] = 0.5f-sin;
+      vertices[2*i  +2*IVY_N] = cos-0.5f;
+      vertices[2*i+1+2*IVY_N] = sin-0.5f;
+      }
+
+    float[] bands0 = computeBands(+0.08f,35,0.5f,0.7f,6);
+    float[] bands1 = computeBands(-0.10f,45,0.5f,0.0f,2);
+
+    meshes[0] = new MeshPolygon(vertices,bands0,1,1);
+    meshes[0].setEffectAssociation(0,1,0);
+    meshes[1] = new MeshPolygon(vertices,bands1,0,0);
+    meshes[1].setEffectAssociation(0,2,0);
+
+    return new MeshJoined(meshes);
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // EFFECTS
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -896,6 +939,13 @@ class FactoryCubit
     return effect;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  VertexEffect[] createVertexEffectsIvyCorner()
+    {
+    return createVertexEffectsSkewbCorner();
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // OBJECTS
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1145,6 +1195,51 @@ class FactoryCubit
 
     mesh.mergeEffComponents();
 
+    return mesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MeshBase createIvyCornerMesh()
+    {
+    MeshBase mesh = createFacesIvyCorner();
+    VertexEffect[] effects = createVertexEffectsIvyCorner();
+    for( VertexEffect effect : effects ) mesh.apply(effect);
+
+    Static3D center = new Static3D(-0.5f,-0.5f,-0.5f);
+    Static3D[] vertices = new Static3D[4];
+    vertices[0] = new Static3D(+0.5f,+0.5f,+0.5f);
+    vertices[1] = new Static3D(-0.5f,+0.5f,+0.5f);
+    vertices[2] = new Static3D(+0.5f,+0.5f,-0.5f);
+    vertices[3] = new Static3D(+0.5f,-0.5f,+0.5f);
+
+    roundCorners(mesh,center,vertices,0.06f,0.12f);
+
+    mesh.mergeEffComponents();
+
+    return mesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MeshBase createIvyFaceMesh()
+    {
+    MeshBase mesh = createFacesIvyFace();
+
+    Static3D center = new Static3D(0.0f,0.0f,-0.5f);
+    Static3D[] vertices = new Static3D[2];
+    vertices[0] = new Static3D(+SQ2/2 -IVY_D,-SQ2/2 +IVY_D,+0.0f);
+    vertices[1] = new Static3D(-SQ2/2 +IVY_D,+SQ2/2 -IVY_D,+0.0f);
+
+    roundCorners(mesh,center,vertices,0.06f,0.12f);
+
+    mesh.mergeEffComponents();
+    mesh.addEmptyTexComponent();
+    mesh.addEmptyTexComponent();
+    mesh.addEmptyTexComponent();
+    mesh.addEmptyTexComponent();
+    mesh.addEmptyTexComponent();
+
     return mesh;
     }
   }
diff --git a/src/main/java/org/distorted/objects/FactorySticker.java b/src/main/java/org/distorted/objects/FactorySticker.java
index 148ef1a2..b8c32eed 100644
--- a/src/main/java/org/distorted/objects/FactorySticker.java
+++ b/src/main/java/org/distorted/objects/FactorySticker.java
@@ -157,4 +157,27 @@ class FactorySticker
         }
       }
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  void drawIvyCornerSticker(Canvas canvas, Paint paint, int left, int top, int color, float R)
+    {
+    paint.setColor(color);
+    paint.setStyle(Paint.Style.FILL);
+
+    canvas.drawRect(left,top,left+TEXTURE_HEIGHT,top+TEXTURE_HEIGHT,paint);
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  void drawIvyCenterSticker(Canvas canvas, Paint paint, int left, int top, int color, float R)
+    {
+    paint.setColor(color);
+    paint.setStyle(Paint.Style.FILL);
+
+    canvas.drawRect(left,top,left+TEXTURE_HEIGHT,top+TEXTURE_HEIGHT,paint);
+    }
   }
diff --git a/src/main/java/org/distorted/objects/MovementIvy.java b/src/main/java/org/distorted/objects/MovementIvy.java
new file mode 100644
index 00000000..8a01bf0c
--- /dev/null
+++ b/src/main/java/org/distorted/objects/MovementIvy.java
@@ -0,0 +1,98 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.objects;
+
+import org.distorted.library.type.Static3D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class MovementIvy extends Movement
+{
+  static final float DIST3D = 0.25f;
+  static final float DIST2D = 0.25f;
+
+  static final Static3D[] FACE_AXIS = new Static3D[]
+         {
+           new Static3D(1,0,0), new Static3D(-1,0,0),
+           new Static3D(0,1,0), new Static3D(0,-1,0),
+           new Static3D(0,0,1), new Static3D(0,0,-1)
+         };
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MovementIvy()
+    {
+    super(TwistyIvy.ROT_AXIS, FACE_AXIS, DIST3D, DIST2D);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int computeRowFromOffset(int face, int size, float offset)
+    {
+    return offset<DIST2D ? 0:1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float returnRotationFactor(int size, int row)
+    {
+    return 1.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// faces 0,1,2,3  --> /
+// faces 4,5      --> \
+
+  private boolean isTopHalf(int face, float[] touchPoint)
+    {
+    if( face==4 || face==5 ) return touchPoint[1] >=-touchPoint[0];
+    else                     return touchPoint[1] >= touchPoint[0];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean isInsideFace(int face, float[] p)
+    {
+    return ( p[0]<=DIST2D && p[0]>=-DIST2D && p[1]<=DIST2D && p[1]>=-DIST2D );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 0 +++
+// 1 ++-
+// 2 +-+
+// 3 +--
+
+  void computeEnabledAxis(int face, float[] touchPoint, int[] enabled)
+    {
+    enabled[0] = 1;
+
+    boolean isTop = isTopHalf(face,touchPoint);
+
+    switch(face)
+      {
+      case 0: enabled[1] = isTop ? 0:3; break;
+      case 1: enabled[1] = isTop ? 2:1; break;
+      case 2: enabled[1] = isTop ? 2:0; break;
+      case 3: enabled[1] = isTop ? 1:3; break;
+      case 4: enabled[1] = isTop ? 0:1; break;
+      case 5: enabled[1] = isTop ? 2:3; break;
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/objects/ObjectList.java b/src/main/java/org/distorted/objects/ObjectList.java
index 3f01e4fb..5c250b17 100644
--- a/src/main/java/org/distorted/objects/ObjectList.java
+++ b/src/main/java/org/distorted/objects/ObjectList.java
@@ -111,6 +111,15 @@ public enum ObjectList
          new MovementSkewb(),
          3
        ),
+
+   IVY (
+         new int[][] {
+                       {2 , 8, R.raw.skewb, R.drawable.ui_small_skewb, R.drawable.ui_medium_skewb, R.drawable.ui_big_skewb, R.drawable.ui_huge_skewb} ,
+                     },
+         TwistyIvy.class,
+         new MovementIvy(),
+         3
+       ),
   ;
 
   public static final int NUM_OBJECTS = values().length;
@@ -491,6 +500,7 @@ public enum ObjectList
       case 5: return new TwistyRedi      (size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 6: return new TwistyHelicopter(size, quat, texture, mesh, effects, moves, res, scrWidth);
       case 7: return new TwistySkewb     (size, quat, texture, mesh, effects, moves, res, scrWidth);
+      case 8: return new TwistyIvy       (size, quat, texture, mesh, effects, moves, res, scrWidth);
       }
 
     return null;
diff --git a/src/main/java/org/distorted/objects/TwistyIvy.java b/src/main/java/org/distorted/objects/TwistyIvy.java
new file mode 100644
index 00000000..878814b3
--- /dev/null
+++ b/src/main/java/org/distorted/objects/TwistyIvy.java
@@ -0,0 +1,409 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.objects;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import org.distorted.library.effect.MatrixEffectQuaternion;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.mesh.MeshSquare;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.main.RubikSurfaceView;
+
+import java.util.Random;
+
+import static org.distorted.effects.scramble.ScrambleEffect.START_AXIS;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class TwistyIvy extends TwistyObject
+{
+  private static final int FACES_PER_CUBIT =7;
+
+  // the four rotation axis of a RubikIvy. Must be normalized.
+  static final Static3D[] ROT_AXIS = new Static3D[]
+         {
+           new Static3D(+SQ3/3,+SQ3/3,+SQ3/3),
+           new Static3D(+SQ3/3,+SQ3/3,-SQ3/3),
+           new Static3D(+SQ3/3,-SQ3/3,+SQ3/3),
+           new Static3D(+SQ3/3,-SQ3/3,-SQ3/3)
+         };
+
+  private static final int[] FACE_COLORS = new int[]
+         {
+           COLOR_YELLOW, COLOR_WHITE,
+           COLOR_BLUE  , COLOR_GREEN,
+           COLOR_RED   , COLOR_BROWN
+         };
+
+  // All legal rotation quats of a RubikIvy
+  private static final Static4D[] QUATS = new Static4D[]
+         {
+           new Static4D(  0.0f,  0.0f,  0.0f,  1.0f ),
+           new Static4D(  1.0f,  0.0f,  0.0f,  0.0f ),
+           new Static4D(  0.0f,  1.0f,  0.0f,  0.0f ),
+           new Static4D(  0.0f,  0.0f,  1.0f,  0.0f ),
+
+           new Static4D(  0.5f,  0.5f,  0.5f,  0.5f ),
+           new Static4D(  0.5f,  0.5f,  0.5f, -0.5f ),
+           new Static4D(  0.5f,  0.5f, -0.5f,  0.5f ),
+           new Static4D(  0.5f,  0.5f, -0.5f, -0.5f ),
+           new Static4D(  0.5f, -0.5f,  0.5f,  0.5f ),
+           new Static4D(  0.5f, -0.5f,  0.5f, -0.5f ),
+           new Static4D(  0.5f, -0.5f, -0.5f,  0.5f ),
+           new Static4D(  0.5f, -0.5f, -0.5f, -0.5f )
+         };
+
+  private static final int[][] mCornerMap =
+         {
+           {  4, 2, 0, 12,12,12,12 },
+           {  5, 2, 1, 12,12,12,12 },
+           {  4, 3, 1, 12,12,12,12 },
+           {  5, 3, 0, 12,12,12,12 },
+         };
+
+  private static final int[][] mCenterMap =
+         {
+           {  6, 12,12,12,12,12,12 },
+           {  7, 12,12,12,12,12,12 },
+           {  8, 12,12,12,12,12,12 },
+           {  9, 12,12,12,12,12,12 },
+           { 10, 12,12,12,12,12,12 },
+           { 11, 12,12,12,12,12,12 },
+         };
+
+  private static MeshBase mCornerMesh, mFaceMesh;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  TwistyIvy(int size, Static4D quat, DistortedTexture texture,
+            MeshSquare mesh, DistortedEffects effects, int[][] moves, Resources res, int scrWidth)
+    {
+    super(size, size, 60, quat, texture, mesh, effects, moves, ObjectList.IVY, res, scrWidth);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float getScreenRatio()
+    {
+    return 1.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Static4D[] getQuats()
+    {
+    return QUATS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumFaces()
+    {
+    return FACE_COLORS.length;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean shouldResetTextureMaps()
+    {
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumStickerTypes()
+    {
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[] getCuts(int numLayers)
+    {
+    float[] cuts = new float[1];
+    cuts[0] = 0.0f;
+
+    return cuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumCubitFaces()
+    {
+    return FACES_PER_CUBIT;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  Static3D[] getCubitPositions(int numLayers)
+    {
+    final float DIST_CORNER = (numLayers-1)*0.50f;
+    final float DIST_CENTER = (numLayers-1)*0.50f;
+
+    final Static3D[] CENTERS = new Static3D[10];
+
+    CENTERS[0] = new Static3D( DIST_CORNER, DIST_CORNER, DIST_CORNER );
+    CENTERS[1] = new Static3D(-DIST_CORNER, DIST_CORNER,-DIST_CORNER );
+    CENTERS[2] = new Static3D(-DIST_CORNER,-DIST_CORNER, DIST_CORNER );
+    CENTERS[3] = new Static3D( DIST_CORNER,-DIST_CORNER,-DIST_CORNER );
+    CENTERS[4] = new Static3D( DIST_CENTER,           0,           0 );
+    CENTERS[5] = new Static3D(-DIST_CENTER,           0,           0 );
+    CENTERS[6] = new Static3D(           0, DIST_CENTER,           0 );
+    CENTERS[7] = new Static3D(           0,-DIST_CENTER,           0 );
+    CENTERS[8] = new Static3D(           0,           0, DIST_CENTER );
+    CENTERS[9] = new Static3D(           0,           0,-DIST_CENTER );
+
+    return CENTERS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getQuat(int cubit)
+    {
+    switch(cubit)
+      {
+      case  0: return 0;
+      case  1: return 2;
+      case  2: return 3;
+      case  3: return 1;
+
+      case  4: return 8;
+      case  5: return 11;
+      case  6: return 10;
+      case  7: return 9;
+      case  8: return 0;
+      case  9: return 2;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MeshBase createCubitMesh(int cubit)
+    {
+    MeshBase mesh;
+
+    if( cubit<4 )
+      {
+      if( mCornerMesh==null ) mCornerMesh = FactoryCubit.getInstance().createIvyCornerMesh();
+      mesh = mCornerMesh.copy(true);
+      }
+    else
+      {
+      if( mFaceMesh==null ) mFaceMesh = FactoryCubit.getInstance().createIvyFaceMesh();
+      mesh = mFaceMesh.copy(true);
+      }
+
+    MatrixEffectQuaternion quat = new MatrixEffectQuaternion( QUATS[getQuat(cubit)], new Static3D(0,0,0) );
+    mesh.apply(quat,0xffffffff,0);
+
+    return mesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getFaceColor(int cubit, int cubitface, int numLayers)
+    {
+    if( cubit<4 )
+      {
+      return mCornerMap[cubit][cubitface];
+      }
+    else
+      {
+      return mCenterMap[cubit-4][cubitface];
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void createFaceTexture(Canvas canvas, Paint paint, int face, int left, int top)
+    {
+    int COLORS = FACE_COLORS.length;
+    FactorySticker factory = FactorySticker.getInstance();
+    float R = 0.05f;
+
+    if( face<COLORS )
+      {
+      factory.drawIvyCornerSticker(canvas, paint, left, top, FACE_COLORS[face%COLORS], R);
+      }
+    else
+      {
+      factory.drawIvyCenterSticker(canvas, paint, left, top, FACE_COLORS[face%COLORS], R);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float returnMultiplier()
+    {
+    return 2.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  float[] getRowChances()
+    {
+    float[] chances = new float[2];
+    chances[0] = 0.5f;
+    chances[1] = 1.0f;
+
+    return chances;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public Static3D[] getRotationAxis()
+    {
+    return ROT_AXIS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getBasicAngle()
+    {
+    return 3;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int randomizeNewRotAxis(Random rnd, int oldRotAxis)
+    {
+    int numAxis = ROTATION_AXIS.length;
+
+    if( oldRotAxis == START_AXIS )
+      {
+      return rnd.nextInt(numAxis);
+      }
+    else
+      {
+      int newVector = rnd.nextInt(numAxis-1);
+      return (newVector>=oldRotAxis ? newVector+1 : newVector);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int randomizeNewRow(Random rnd, int oldRotAxis, int oldRow, int newRotAxis)
+    {
+    float rowFloat = rnd.nextFloat();
+
+    for(int row=0; row<mRowChances.length; row++)
+      {
+      if( rowFloat<=mRowChances[row] ) return row;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// remember about the double cover or unit quaternions!
+
+  private int mulQuat(int q1, int q2)
+    {
+    Static4D result = RubikSurfaceView.quatMultiply(QUATS[q1],QUATS[q2]);
+
+    float rX = result.get0();
+    float rY = result.get1();
+    float rZ = result.get2();
+    float rW = result.get3();
+
+    final float MAX_ERROR = 0.1f;
+    float dX,dY,dZ,dW;
+
+    for(int i=0; i<QUATS.length; i++)
+      {
+      dX = QUATS[i].get0() - rX;
+      dY = QUATS[i].get1() - rY;
+      dZ = QUATS[i].get2() - rZ;
+      dW = QUATS[i].get3() - rW;
+
+      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
+          dY<MAX_ERROR && dY>-MAX_ERROR &&
+          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
+          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
+
+      dX = QUATS[i].get0() + rX;
+      dY = QUATS[i].get1() + rY;
+      dZ = QUATS[i].get2() + rZ;
+      dW = QUATS[i].get3() + rW;
+
+      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
+          dY<MAX_ERROR && dY>-MAX_ERROR &&
+          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
+          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// The Ivy is solved if and only if:
+//
+// 1) all 4 of its corner cubits are rotated with the same quat
+// 2) all its face cubits are rotated with the same quat like the corner ones,
+//    and optionally they also might be upside down.
+//
+// i.e.
+// cubits [4] and [5] - might be extra QUAT[1]
+// cubits [6] and [7] - might be extra QUAT[2]
+// cubits [8] and [9] - might be extra QUAT[3]
+
+  public boolean isSolved()
+    {
+    int q1,q = CUBITS[0].mQuatIndex;
+
+    if( CUBITS[1].mQuatIndex == q &&
+        CUBITS[2].mQuatIndex == q &&
+        CUBITS[3].mQuatIndex == q  )
+      {
+      q1 = mulQuat(q,1);
+      if( CUBITS[4].mQuatIndex != q && CUBITS[4].mQuatIndex != q1 ) return false;
+      if( CUBITS[5].mQuatIndex != q && CUBITS[5].mQuatIndex != q1 ) return false;
+
+      q1 = mulQuat(q,2);
+      if( CUBITS[6].mQuatIndex != q && CUBITS[6].mQuatIndex != q1 ) return false;
+      if( CUBITS[7].mQuatIndex != q && CUBITS[7].mQuatIndex != q1 ) return false;
+
+      q1 = mulQuat(q,3);
+      if( CUBITS[8].mQuatIndex != q && CUBITS[8].mQuatIndex != q1 ) return false;
+      if( CUBITS[9].mQuatIndex != q && CUBITS[9].mQuatIndex != q1 ) return false;
+
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// only needed for solvers - there are no Ivy solvers ATM)
+
+  public String retObjectString()
+    {
+    return "";
+    }
+}
diff --git a/src/main/java/org/distorted/objects/TwistyObject.java b/src/main/java/org/distorted/objects/TwistyObject.java
index eaa7fd33..b0dceb41 100644
--- a/src/main/java/org/distorted/objects/TwistyObject.java
+++ b/src/main/java/org/distorted/objects/TwistyObject.java
@@ -78,7 +78,7 @@ public abstract class TwistyObject extends DistortedNode
   private static final float MAX_SIZE_CHANGE = 1.35f;
   private static final float MIN_SIZE_CHANGE = 0.8f;
 
-  private static boolean mCreateFromDMesh = true;
+  private static boolean mCreateFromDMesh = false;
 
   private static final Static3D CENTER = new Static3D(0,0,0);
   private static final int POST_ROTATION_MILLISEC = 500;
