commit c18507d98276f0923fe5b47e8784c85296b5e9f3
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sat May 13 11:35:05 2023 +0200

    Introduce BandagedObjectPyraminx.

diff --git a/src/main/java/org/distorted/objectlib/helpers/FactoryBandagedCubit.java b/src/main/java/org/distorted/objectlib/helpers/FactoryBandagedCubit.java
deleted file mode 100644
index e5da66d3..00000000
--- a/src/main/java/org/distorted/objectlib/helpers/FactoryBandagedCubit.java
+++ /dev/null
@@ -1,921 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 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.objectlib.helpers;
-
-import java.util.ArrayList;
-
-import org.distorted.library.mesh.MeshBase;
-import org.distorted.library.type.Static3D;
-import org.distorted.objectlib.touchcontrol.TouchControlHexahedron;
-
-import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class FactoryBandagedCubit
-  {
-  private static final int WALL_MARKED=0;
-  private static final int WALL_EMPTY =-1;
-
-  private static final int AXIS_XP = 0;
-  private static final int AXIS_XM = 1;
-  private static final int AXIS_YP = 2;
-  private static final int AXIS_YM = 3;
-  private static final int AXIS_ZP = 4;
-  private static final int AXIS_ZM = 5;
-
-  private static final float[][] VECTOR =
-      {
-          {-1.0f,-1.0f,-1.0f},
-          {-1.0f,-1.0f,+1.0f},
-          {-1.0f,+1.0f,-1.0f},
-          {-1.0f,+1.0f,+1.0f},
-          {+1.0f,-1.0f,-1.0f},
-          {+1.0f,-1.0f,+1.0f},
-          {+1.0f,+1.0f,-1.0f},
-          {+1.0f,+1.0f,+1.0f}
-      };
-
-  private static FactoryBandagedCubit mThis;
-  private ArrayList<float[]> mVertexArray;
-  private ArrayList<float[]> mTmpArray;
-  private float[][][] mVertices;
-  private int[][][] mIndices;
-  private int[][] mBandIndices;
-  private float[][] mMove;
-
-  private int mX, mY, mZ, mMax;
-  private float dX, dY, dZ;
-  private int[][] mWall;
-  private int[][] mPoints;
-  private boolean[][][] mTmp;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private FactoryBandagedCubit()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float[][] getVertices(ArrayList<float[]> list, float[] move, int variant)
-    {
-    int numMoves = move.length/3;
-    mMove[variant][0]=0.0f;
-    mMove[variant][1]=0.0f;
-    mMove[variant][2]=0.0f;
-
-    for(int m=0; m<numMoves; m++)
-      {
-      mMove[variant][0] += move[3*m  ];
-      mMove[variant][1] += move[3*m+1];
-      mMove[variant][2] += move[3*m+2];
-      }
-
-    mMove[variant][0]/=numMoves;
-    mMove[variant][1]/=numMoves;
-    mMove[variant][2]/=numMoves;
-
-    int total  = 0;
-    int length = list.size();
-    float[][] vertices = new float[length][];
-
-    for( int i=0; i<length; i++ )
-      {
-      vertices[i] = list.get(i);
-      total += vertices[i].length/3;
-      }
-
-    float[][] verts = new float[total][3];
-    int pointer = 0;
-
-    for(int i=0; i<length; i++)
-      {
-      int len = vertices[i].length/3;
-
-      for(int j=0; j<len; j++)
-        {
-        verts[pointer][0] = vertices[i][3*j  ] - mMove[variant][0];
-        verts[pointer][1] = vertices[i][3*j+1] - mMove[variant][1];
-        verts[pointer][2] = vertices[i][3*j+2] - mMove[variant][2];
-        pointer++;
-        }
-      }
-
-    return verts;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[][] getIndices(ArrayList<float[]> list)
-    {
-    int indicesSoFar=0;
-    int length = list.size();
-    int[][] indices = new int[length][];
-
-    for( int i=0; i<length; i++ )
-      {
-      float[] f = list.get(i);
-      int len = f.length/3;
-      int[] ind = new int[len];
-      for(int j=0; j<len; j++) ind[j] = (indicesSoFar++);
-      indices[i] = ind;
-      }
-
-    return indices;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void markAllVertices(float[] vertex, float[][] vertices, int[][] indices, int pointer, int variant)
-    {
-    int lenI = indices.length;
-
-    for(int index=0; index<lenI; index++)
-      {
-      int len = indices[index].length;
-
-      for(int i=0; i<len; i++)
-        {
-        if( mIndices[variant][index][i] == -1 )
-          {
-          int ind = indices[index][i];
-          float[] ver = vertices[ind];
-
-          if( vertex[0]==ver[0] && vertex[1]==ver[1] && vertex[2]==ver[2] )
-            {
-            mIndices[variant][index][i] = pointer;
-            }
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// So far the 'vertices/indices' are stored inefficiently, with each vertex stored three times
-// (each one normally is a corner of three faces) or even six times. Compress!
-// Example of six times: the central vertex here:
-//
-// { 1.0f,  0.0f, -1.0f,
-//   1.0f, -1.0f, -1.0f,
-//   1.0f, -1.0f, +0.0f,
-//   0.0f, -1.0f, -1.0f },
-
-  private void compressVerticesAndIndices(int variant, float[][] vertices, int[][] indices)
-    {
-    if( mTmpArray==null ) mTmpArray = new ArrayList<>();
-
-    int lenI = indices.length;
-    int pointer=0;
-
-    mIndices[variant] = new int[lenI][];
-
-    for(int index=0; index<lenI; index++)
-      {
-      int len = indices[index].length;
-      mIndices[variant][index] = new int[len];
-      for(int i=0; i<len; i++) mIndices[variant][index][i] = -1;
-      }
-
-    for(int index=0; index<lenI; index++)
-      {
-      int len = indices[index].length;
-
-      for(int i=0; i<len; i++)
-        {
-        if( mIndices[variant][index][i] == -1 )
-          {
-          int ind = indices[index][i];
-          float[] ver = vertices[ind];
-          mTmpArray.add(ver);
-          markAllVertices(ver,vertices,indices,pointer,variant);
-          pointer++;
-          }
-        }
-      }
-
-    int len = mTmpArray.size();
-    mVertices[variant] = new float[len][];
-
-    for(int i=0; i<len; i++)
-      {
-      mVertices[variant][i] = mTmpArray.remove(0);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean cubitExists(float[] pos, float x, float y, float z)
-    {
-    int len = pos.length/3;
-
-    for(int i=0; i<len; i++)
-      if( pos[3*i]==x && pos[3*i+1]==y && pos[3*i+2]==z ) return true;
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createRight(int x, ArrayList<float[]> list)
-    {
-    for(int y=0; y<mMax; y++)
-      for(int z=0; z<mMax; z++)
-        {
-        boolean b = (y<mY && z<mZ) && ( mTmp[x][mY-1-y][mZ-1-z] && (x+1>=mX || !mTmp[x+1][mY-1-y][mZ-1-z]) );
-        mWall[z][y] = b ? WALL_MARKED : WALL_EMPTY;
-        }
-
-    createVertices(list,mWall,AXIS_XP,x);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createLeft(int x, ArrayList<float[]> list)
-    {
-    for(int y=0; y<mMax; y++)
-      for(int z=0; z<mMax; z++)
-        {
-        boolean b = (y<mY && z<mZ) && ( mTmp[x][mY-1-y][z] && (x<1 || !mTmp[x-1][mY-1-y][z]) );
-        mWall[z][y] = b ? WALL_MARKED : WALL_EMPTY;
-        }
-
-    createVertices(list,mWall,AXIS_XM,x);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createTop(int y, ArrayList<float[]> list)
-    {
-    for(int z=0; z<mMax; z++)
-      for(int x=0; x<mMax; x++)
-        {
-        boolean b = (x<mX && z<mZ) && ( mTmp[x][y][z] && (y+1>=mY || !mTmp[x][y+1][z]) );
-        mWall[x][z] = b ? WALL_MARKED : WALL_EMPTY;
-        }
-
-    createVertices(list,mWall,AXIS_YP,y);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createBottom(int y, ArrayList<float[]> list)
-    {
-    for(int z=0; z<mMax; z++)
-      for(int x=0; x<mMax; x++)
-        {
-        boolean b = (x<mX && z<mZ) && ( mTmp[x][y][mZ-1-z] && (y<1 || !mTmp[x][y-1][mZ-1-z]) );
-        mWall[x][z] = b ? WALL_MARKED : WALL_EMPTY;
-        }
-
-    createVertices(list,mWall,AXIS_YM,y);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createFront(int z, ArrayList<float[]> list)
-    {
-    for(int y=0; y<mMax; y++)
-      for(int x=0; x<mMax; x++)
-        {
-        boolean b = (x<mX && y<mY) && ( mTmp[x][mY-1-y][z] && (z+1>=mZ || !mTmp[x][mY-1-y][z+1]) );
-        mWall[x][y] = b ? WALL_MARKED : WALL_EMPTY;
-        }
-
-    createVertices(list,mWall,AXIS_ZP,z);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createBack(int z, ArrayList<float[]> list)
-    {
-    for(int y=0; y<mMax; y++)
-      for(int x=0; x<mMax; x++)
-        {
-        boolean b = (x<mX && y<mY) && ( mTmp[mX-1-x][mY-1-y][z] && (z<1 || !mTmp[mX-1-x][mY-1-y][z-1]) );
-        mWall[x][y] = b ? WALL_MARKED : WALL_EMPTY;
-        }
-
-    createVertices(list,mWall,AXIS_ZM,z);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void markNeighbours(int[][] wall, int x, int y, int section)
-    {
-    wall[x][y] = section;
-
-    if( x  >   0 && wall[x-1][y]==WALL_MARKED ) markNeighbours(wall,x-1,y,section);
-    if( x+1<mMax && wall[x+1][y]==WALL_MARKED ) markNeighbours(wall,x+1,y,section);
-    if( y  >   0 && wall[x][y-1]==WALL_MARKED ) markNeighbours(wall,x,y-1,section);
-    if( y+1<mMax && wall[x][y+1]==WALL_MARKED ) markNeighbours(wall,x,y+1,section);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int markSections(int[][] wall)
-    {
-    int sections = 0;
-
-    for(int x=0; x<mMax; x++)
-      for(int y=0; y<mMax; y++)
-        if( wall[x][y]==WALL_MARKED )
-          {
-          sections++;
-          markNeighbours(wall,x,y,sections);
-          }
-
-    return sections;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return true iff exactly three or exactly one of the four values (x1,x2,x3,x4) are equal to 'value'.
-
-  private boolean threeOrOne(int x1, int x2, int x3, int x4, int value)
-    {
-    if( x1==value ) return x2==value ? (x3==value)^(x4==value) : (x3==value)^(x4!=value);
-    else            return x2==value ? (x3==value)^(x4!=value) : (x3==value)^(x4==value);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean isOddVertical(int x, int y)
-    {
-    int number = 0;
-
-    for(int i=0; i<y; i++)
-      if( mPoints[x][i]==0 ) number++;
-
-    return (number%2)==0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean isOddHorizontal(int x, int y)
-    {
-    int number = 0;
-
-    for(int i=0; i<x; i++)
-      if( mPoints[i][y]==0 ) number++;
-
-    return (number%2)==0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int moveUp(int x, int y)
-    {
-    for(int i=y-1; i>=0; i--)
-      if( mPoints[x][i]==0 ) return i;
-
-    android.util.Log.e("D", "moveUp error!");
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int moveDown(int x, int y)
-    {
-    for(int i=y+1; i<=mMax; i++)
-      if( mPoints[x][i]==0 ) return i;
-
-    android.util.Log.e("D", "moveDown error!");
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int moveLeft(int x, int y)
-    {
-    for(int i=x-1; i>=0; i--)
-      if( mPoints[i][y]==0 ) return i;
-
-    android.util.Log.e("D", "moveLeft error!");
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int moveRight(int x, int y)
-    {
-    for(int i=x+1; i<=mMax; i++)
-      if( mPoints[i][y]==0 ) return i;
-
-    android.util.Log.e("D", "moveRight error!");
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float[] buildVertices(int[][] wall, int section, float dx, float dy)
-    {
-    int numPoints = buildPoints(wall,section);
-    int numSections = numPoints/2;
-    float[] vertices = new float[3*numPoints];
-    int x=0,y=0,pointer=0;
-
-    for(int i=0; i<=mMax; i++)
-      for(int j=0; j<=mMax; j++)
-        if( mPoints[i][j]==0 )
-          {
-          x = i;
-          y = j;
-          i = j = mMax+1;
-          }
-
-    for(int s=0; s<numSections; s++)
-      {
-      vertices[6*pointer  ] = x-dx;
-      vertices[6*pointer+1] = dy-y;
-      vertices[6*pointer+2] = 0.0f;
-
-      y = isOddVertical(x,y) ? moveDown(x,y) : moveUp(x,y);
-
-      vertices[6*pointer+3] = x-dx;
-      vertices[6*pointer+4] = dy-y;
-      vertices[6*pointer+5] = 0.0f;
-
-      x = isOddHorizontal(x,y) ? moveRight(x,y) : moveLeft(x,y);
-
-      pointer++;
-      }
-
-    return vertices;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int buildPoints(int[][] wall, int section)
-    {
-    int numPoints=0;
-    boolean thereIsCorner;
-
-    for(int x=0; x<=mMax; x++)
-      for(int y=0; y<=mMax; y++)
-        {
-        if( y==0 )
-          {
-               if( x==   0 ) thereIsCorner = (wall[     0][0]==section);
-          else if( x==mMax ) thereIsCorner = (wall[mMax-1][0]==section);
-          else               thereIsCorner = (wall[x-1][0]==section)^(wall[x][0]==section);
-          }
-        else if( y==mMax )
-          {
-               if( x==   0 ) thereIsCorner = (wall[     0][mMax-1]==section);
-          else if( x==mMax ) thereIsCorner = (wall[mMax-1][mMax-1]==section);
-          else               thereIsCorner = (wall[x-1][mMax-1]==section)^(wall[x][mMax-1]==section);
-          }
-        else if( x==0 )
-          {
-          thereIsCorner = (wall[0][y-1]==section)^(wall[0][y]==section);
-          }
-        else if( x==mMax )
-          {
-          thereIsCorner = (wall[mMax-1][y-1]==section)^(wall[mMax-1][y]==section);
-          }
-        else
-          {
-          thereIsCorner = threeOrOne(wall[x-1][y-1],wall[x-1][y],wall[x][y-1],wall[x][y],section);
-          }
-
-        if( thereIsCorner )
-          {
-          mPoints[x][y] = 0;
-          numPoints++;
-          }
-        else
-          {
-          mPoints[x][y] =-1;
-          }
-        }
-
-    return numPoints;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void rotateAndMoveVertices(float[] vertices, int axis, int layer)
-    {
-    int i,len = vertices.length/3;
-
-    switch(axis)
-      {
-      case AXIS_XP: for(i=0; i<len; i++)
-                      {
-                      vertices[3*i+2] =-vertices[3*i  ];
-                      vertices[3*i  ] = layer-(dX-1.0f);
-                      }
-                    break;
-      case AXIS_XM: for(i=0; i<len; i++)
-                      {
-                      vertices[3*i+2] = vertices[3*i  ];
-                      vertices[3*i  ] = layer-dX;
-                      }
-                    break;
-      case AXIS_YP: for(i=0; i<len; i++)
-                      {
-                      vertices[3*i+2] =-vertices[3*i+1];
-                      vertices[3*i+1] = layer-(dY-1.0f);
-                      }
-                    break;
-      case AXIS_YM: for(i=0; i<len; i++)
-                      {
-                      vertices[3*i+2] = vertices[3*i+1];
-                      vertices[3*i+1] = layer-dY;
-                      }
-                    break;
-      case AXIS_ZP: for(i=0; i<len; i++)
-                      {
-                      vertices[3*i+2] = layer-(dZ-1.0f);
-                      }
-                    break;
-      case AXIS_ZM: for(i=0; i<len; i++)
-                      {
-                      vertices[3*i+2] = layer-dZ;
-                      vertices[3*i  ] =-vertices[3*i  ];
-                      }
-                    break;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 1. assume the 'wall' is in the XY plane
-// 2. split the wall into individual connected regions and for each such region:
-//   a. build the list of vertices (Z=0)
-//   b. take the axis into consideration and rotate the vertices.
-//   c. take layer into consideration and move the vertices.
-//   d. add the resulting vertices to the list.
-
-  private void createVertices(ArrayList<float[]> list, int[][] wall, int axis, int layer)
-    {
-    int sections = markSections(wall);
-
-    float dx = (axis==AXIS_XP || axis==AXIS_XM) ? dZ : dX;
-    float dy = (axis==AXIS_YP || axis==AXIS_YM) ? dZ : dY;
-
-    for(int i=0; i<sections; i++)
-      {
-      float[] vertices = buildVertices(wall,i+1,dx,dy);
-
-      rotateAndMoveVertices(vertices,axis,layer);
-      list.add(vertices);
-/*
-      int len = vertices.length/3;
-      String w="";
-
-      for(int j=0; j<len; j++)
-        {
-        w += ( "["+vertices[3*j]+" "+vertices[3*j+1]+" "+vertices[3*j+2]+"] ");
-        }
-      android.util.Log.e("D", "1 section: "+i+" axis: "+axis+" layer: "+layer+" vertices after: "+w);
-*/
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static boolean vertInFace(float[] vertex, float[] move, Static3D faceAxis, float dist)
-    {
-    final float MAX_ERROR = 0.01f;
-
-    float x= faceAxis.get0();
-    float y= faceAxis.get1();
-    float z= faceAxis.get2();
-
-    float a = (vertex[0]+move[0])*x + (vertex[1]+move[1])*y + (vertex[2]+move[2])*z;
-    float diff = a - dist;
-
-    return diff>-MAX_ERROR && diff<MAX_ERROR;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// (vertices,indices) define a cubit face, i.e. a connected subset of the NxN grid.
-// Return its 'diameter', i.e. max(width,height)
-
-  private int faceDiameter(float[][] vertices, int[] indices)
-    {
-    float maxX = -dX;
-    float minX =  dX;
-    float maxY = -dY;
-    float minY =  dY;
-    float maxZ = -dZ;
-    float minZ =  dZ;
-
-    for (int index : indices)
-      {
-      float[] v = vertices[index];
-
-      if (v[0] > maxX) maxX = v[0];
-      if (v[0] < minX) minX = v[0];
-      if (v[1] > maxY) maxY = v[1];
-      if (v[1] < minY) minY = v[1];
-      if (v[2] > maxZ) maxZ = v[2];
-      if (v[2] < minZ) minZ = v[2];
-      }
-
-    float diffX = maxX-minX;
-    float diffY = maxY-minY;
-    float diffZ = maxZ-minZ;
-
-    float max = diffX>diffY ? Math.max(diffX,diffZ) : Math.max(diffY,diffZ);
-
-    return (int)max;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return array of:
-// 0 if this is an inner face, 1 if its diameter is 1, 2 if diameter is 2, 3 if 3, etc
-// but only up to 5 (because the number of bands is 6 - see createIrregularFaceShape() )
-
-  private int[] generateBandIndices(float[][] vertices, int[][] indices, float[] move)
-    {
-    int numCubitFaces = indices.length;
-    int[] bandIndices = new int[numCubitFaces];
-
-    for(int cubitFace=0; cubitFace<numCubitFaces; cubitFace++)
-      {
-      bandIndices[cubitFace] = 0xffffffff;
-      int numVertices = indices[cubitFace].length;
-
-      for(int vertex=0; vertex<numVertices; vertex++)
-        {
-        int vertBelongsBitmap = 0x00000000;
-        float[] vert = vertices[ indices[cubitFace][vertex] ];
-
-        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[0], dX) ) vertBelongsBitmap |= (1   );
-        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[1], dX) ) vertBelongsBitmap |= (1<<1);
-        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[2], dY) ) vertBelongsBitmap |= (1<<2);
-        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[3], dY) ) vertBelongsBitmap |= (1<<3);
-        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[4], dZ) ) vertBelongsBitmap |= (1<<4);
-        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[5], dZ) ) vertBelongsBitmap |= (1<<5);
-
-        bandIndices[cubitFace] &= vertBelongsBitmap;
-        }
-
-      if( bandIndices[cubitFace]!=0 ) // outer face
-        {
-        int diameter = faceDiameter(vertices, indices[cubitFace]);
-        bandIndices[cubitFace] = (diameter>=6 ? 5:diameter);
-        }
-
-      //android.util.Log.e("D", "cubit face "+cubitFace+" : "+bandIndices[cubitFace]);
-      }
-
-    return bandIndices;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeVectorFace(float[] prev, float[] curr, float[] next)
-    {
-    float ax = prev[0]-curr[0];
-    float ay = prev[1]-curr[1];
-    float az = prev[2]-curr[2];
-
-    float bx = next[0]-curr[0];
-    float by = next[1]-curr[1];
-    float bz = next[2]-curr[2];
-
-    float lena = (float)Math.sqrt(ax*ax + ay*ay + az*az);
-    float lenb = (float)Math.sqrt(bx*bx + by*by + bz*bz);
-
-    ax /= lena;
-    ay /= lena;
-    az /= lena;
-
-    bx /= lenb;
-    by /= lenb;
-    bz /= lenb;
-
-    float cx = ax + bx + ay*bz-az*by;
-    float cy = ay + by + az*bx-ax*bz;
-    float cz = az + bz + ax*by-ay*bx;
-
-    return (cx<0 ? 0:4) + (cy<0 ? 0:2) + (cz<0 ? 0:1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float[] computeVector(int index, float[][] vertices, int[][] indices, int[] bandIndices)
-    {
-    int band=0;
-    int len = indices.length;
-    int vector=-1;
-
-    for(int i=0; i<len; i++)
-      {
-      int len2 = indices[i].length;
-
-      for(int j=0; j<len2; j++)
-        {
-        if( indices[i][j]==index )
-          {
-          int prev = j>0 ? j-1 : len2-1;
-          int next = j<len2-1 ? j+1 : 0;
-
-          int prevIndex = indices[i][prev];
-          int currIndex = indices[i][j];
-          int nextIndex = indices[i][next];
-
-          int newVector = computeVectorFace(vertices[prevIndex],vertices[currIndex],vertices[nextIndex]);
-          if( vector!=-1 && vector!=newVector ) return null;
-
-          vector = newVector;
-          band |= bandIndices[i];
-          }
-        }
-      }
-
-    return band==0 ? null : VECTOR[vector];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float[][] generateVectors(float[][] vertices, int[][] indices, int[] bandIndices)
-    {
-    int len = vertices.length;
-    float[][] vectors = new float[len][];
-
-    for(int i=0; i<len; i++)
-      {
-      vectors[i] = computeVector(i,vertices,indices,bandIndices);
-      }
-
-    return vectors;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void debug(float[][] vert, int[][] ind)
-    {
-    String vv="VERTICES: ";
-    for (float[] floats : vert)
-      {
-      vv += "\n";
-      int lenV2 = floats.length / 3;
-
-      for (int v2 = 0; v2 < lenV2; v2++)
-        {
-        vv += " {";
-        vv += (floats[3 * v2] + " ");
-        vv += (floats[3 * v2 + 1] + " ");
-        vv += (floats[3 * v2 + 2] + " ");
-        vv += "}";
-        }
-      }
-    android.util.Log.e("D", vv);
-
-    String ii="INDICES: ";
-    for (int[] ints : ind)
-      {
-      ii += "\n";
-      int lenI2 = ints.length;
-
-      for (int i2 = 0; i2 < lenI2; i2++)
-        {
-        ii += (ints[i2] + " ");
-        }
-      }
-    android.util.Log.e("D", ii);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-
-  public static FactoryBandagedCubit getInstance()
-    {
-    if( mThis==null ) mThis = new FactoryBandagedCubit();
-    return mThis;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void prepare(int numVariants, int x, int y, int z)
-    {
-    if( mVertexArray==null ) mVertexArray = new ArrayList<>();
-    mVertices= new float[numVariants][][];
-    mIndices = new int[numVariants][][];
-    mMove = new float[numVariants][3];
-    mBandIndices = new int[numVariants][];
-
-    mX = x;
-    mY = y;
-    mZ = z;
-
-    dX = mX/2.0f;
-    dY = mY/2.0f;
-    dZ = mZ/2.0f;
-
-    mMax = mX>mY ? Math.max(mX,mZ) : Math.max(mY,mZ);
-
-    mWall   = new int[mMax][mMax];
-    mPoints = new int[mMax+1][mMax+1];
-    mTmp    = new boolean[mX][mY][mZ];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ObjectShape createIrregularShape(int variant, float[] pos)
-    {
-    mVertexArray.clear();
-
-    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++) mTmp[x][y][z] = cubitExists(pos,begX+x,begY+y,begZ+z);
-
-    for(int x=0; x<mX; x++) createRight (x,mVertexArray);
-    for(int x=0; x<mX; x++) createLeft  (x,mVertexArray);
-    for(int y=0; y<mY; y++) createTop   (y,mVertexArray);
-    for(int y=0; y<mY; y++) createBottom(y,mVertexArray);
-    for(int z=0; z<mZ; z++) createFront (z,mVertexArray);
-    for(int z=0; z<mZ; z++) createBack  (z,mVertexArray);
-
-    float[][] verts = getVertices(mVertexArray,pos,variant);
-    int[][] inds    = getIndices(mVertexArray);
-
-    compressVerticesAndIndices(variant,verts,inds);
-    mBandIndices[variant] = generateBandIndices(mVertices[variant], mIndices[variant], mMove[variant]);
-
-    return new ObjectShape(mVertices[variant], mIndices[variant]);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ObjectVertexEffects createVertexEffects(int variant, boolean roundCorners)
-    {
-    float[][] vertVec= generateVectors(mVertices[variant], mIndices[variant], mBandIndices[variant]);
-    int numEffects   = mVertices[variant].length;
-    float S          = 0.04f;
-    float[] region   = {0,0,0,0.15f};
-    String[] names   = new String[numEffects];
-    float[][] regions= new float[numEffects][];
-    boolean[] uses   = new boolean[numEffects];
-    float[][] vars   = new float[numEffects][];
-
-    for(int i=0; i<numEffects; i++)
-      {
-      float[] v = vertVec[i];
-
-      if( v!=null )
-        {
-        names[i]  = FactoryCubit.NAME;
-        regions[i]= region;
-        uses[i]   = roundCorners;
-        vars[i]   = new float[] { 0, S*v[0], S*v[1], S*v[2], 1 };
-        }
-      }
-
-    return new ObjectVertexEffects(names,vars,mVertices[variant],regions,uses);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public ObjectFaceShape createIrregularFaceShape(int variant, boolean iconMode)
-    {
-    float height= iconMode ? 0.001f : 0.048f;
-    int angle   = 60;
-    float R     = 0.2f;
-    float S     = 0.5f;
-    int numVerts= 5;
-    int extraI  = 0;
-    int extraV  = 0;
-
-    float[][] bands  = { {  0.001f,angle,R,S,numVerts,extraV,extraI},
-                         {height/1,angle,R,S,numVerts,extraV,extraI},
-                         {height/2,angle,R,S,numVerts,extraV,extraI},
-                         {height/3,angle,R,S,numVerts,extraV,extraI},
-                         {height/4,angle,R,S,numVerts,extraV,extraI},
-                         {height/5,angle,R,S,numVerts,extraV,extraI} };
-
-    return new ObjectFaceShape(bands,mBandIndices[variant],null);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public MeshBase createMesh(float[] pos, int x, int y, int z, boolean iconMode, boolean roundCorners)
-    {
-    prepare(1,x,y,z);
-    ObjectShape shape           = createIrregularShape(0,pos);
-    ObjectFaceShape face        = createIrregularFaceShape(0,iconMode);
-    ObjectVertexEffects effects = createVertexEffects(0,roundCorners);
-    int[][] indices             = shape.getVertIndices();
-    int numComponents           = indices.length;
-
-    FactoryCubit factory = FactoryCubit.getInstance();
-    factory.clear();
-    factory.createNewFaceTransform(shape,null);
-    return factory.createRoundedSolid(shape,face,effects,MESH_NICE,numComponents);
-    }
-  }
diff --git a/src/main/java/org/distorted/objectlib/helpers/FactoryBandagedCuboid.java b/src/main/java/org/distorted/objectlib/helpers/FactoryBandagedCuboid.java
new file mode 100644
index 00000000..f3ac8af8
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/FactoryBandagedCuboid.java
@@ -0,0 +1,921 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.objectlib.helpers;
+
+import java.util.ArrayList;
+
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.type.Static3D;
+import org.distorted.objectlib.touchcontrol.TouchControlHexahedron;
+
+import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class FactoryBandagedCuboid
+  {
+  private static final int WALL_MARKED=0;
+  private static final int WALL_EMPTY =-1;
+
+  private static final int AXIS_XP = 0;
+  private static final int AXIS_XM = 1;
+  private static final int AXIS_YP = 2;
+  private static final int AXIS_YM = 3;
+  private static final int AXIS_ZP = 4;
+  private static final int AXIS_ZM = 5;
+
+  private static final float[][] VECTOR =
+      {
+          {-1.0f,-1.0f,-1.0f},
+          {-1.0f,-1.0f,+1.0f},
+          {-1.0f,+1.0f,-1.0f},
+          {-1.0f,+1.0f,+1.0f},
+          {+1.0f,-1.0f,-1.0f},
+          {+1.0f,-1.0f,+1.0f},
+          {+1.0f,+1.0f,-1.0f},
+          {+1.0f,+1.0f,+1.0f}
+      };
+
+  private static FactoryBandagedCuboid mThis;
+  private ArrayList<float[]> mVertexArray;
+  private ArrayList<float[]> mTmpArray;
+  private float[][][] mVertices;
+  private int[][][] mIndices;
+  private int[][] mBandIndices;
+  private float[][] mMove;
+
+  private int mX, mY, mZ, mMax;
+  private float dX, dY, dZ;
+  private int[][] mWall;
+  private int[][] mPoints;
+  private boolean[][][] mTmp;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private FactoryBandagedCuboid()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[][] getVertices(ArrayList<float[]> list, float[] move, int variant)
+    {
+    int numMoves = move.length/3;
+    mMove[variant][0]=0.0f;
+    mMove[variant][1]=0.0f;
+    mMove[variant][2]=0.0f;
+
+    for(int m=0; m<numMoves; m++)
+      {
+      mMove[variant][0] += move[3*m  ];
+      mMove[variant][1] += move[3*m+1];
+      mMove[variant][2] += move[3*m+2];
+      }
+
+    mMove[variant][0]/=numMoves;
+    mMove[variant][1]/=numMoves;
+    mMove[variant][2]/=numMoves;
+
+    int total  = 0;
+    int length = list.size();
+    float[][] vertices = new float[length][];
+
+    for( int i=0; i<length; i++ )
+      {
+      vertices[i] = list.get(i);
+      total += vertices[i].length/3;
+      }
+
+    float[][] verts = new float[total][3];
+    int pointer = 0;
+
+    for(int i=0; i<length; i++)
+      {
+      int len = vertices[i].length/3;
+
+      for(int j=0; j<len; j++)
+        {
+        verts[pointer][0] = vertices[i][3*j  ] - mMove[variant][0];
+        verts[pointer][1] = vertices[i][3*j+1] - mMove[variant][1];
+        verts[pointer][2] = vertices[i][3*j+2] - mMove[variant][2];
+        pointer++;
+        }
+      }
+
+    return verts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[][] getIndices(ArrayList<float[]> list)
+    {
+    int indicesSoFar=0;
+    int length = list.size();
+    int[][] indices = new int[length][];
+
+    for( int i=0; i<length; i++ )
+      {
+      float[] f = list.get(i);
+      int len = f.length/3;
+      int[] ind = new int[len];
+      for(int j=0; j<len; j++) ind[j] = (indicesSoFar++);
+      indices[i] = ind;
+      }
+
+    return indices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void markAllVertices(float[] vertex, float[][] vertices, int[][] indices, int pointer, int variant)
+    {
+    int lenI = indices.length;
+
+    for(int index=0; index<lenI; index++)
+      {
+      int len = indices[index].length;
+
+      for(int i=0; i<len; i++)
+        {
+        if( mIndices[variant][index][i] == -1 )
+          {
+          int ind = indices[index][i];
+          float[] ver = vertices[ind];
+
+          if( vertex[0]==ver[0] && vertex[1]==ver[1] && vertex[2]==ver[2] )
+            {
+            mIndices[variant][index][i] = pointer;
+            }
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// So far the 'vertices/indices' are stored inefficiently, with each vertex stored three times
+// (each one normally is a corner of three faces) or even six times. Compress!
+// Example of six times: the central vertex here:
+//
+// { 1.0f,  0.0f, -1.0f,
+//   1.0f, -1.0f, -1.0f,
+//   1.0f, -1.0f, +0.0f,
+//   0.0f, -1.0f, -1.0f },
+
+  private void compressVerticesAndIndices(int variant, float[][] vertices, int[][] indices)
+    {
+    if( mTmpArray==null ) mTmpArray = new ArrayList<>();
+
+    int lenI = indices.length;
+    int pointer=0;
+
+    mIndices[variant] = new int[lenI][];
+
+    for(int index=0; index<lenI; index++)
+      {
+      int len = indices[index].length;
+      mIndices[variant][index] = new int[len];
+      for(int i=0; i<len; i++) mIndices[variant][index][i] = -1;
+      }
+
+    for(int index=0; index<lenI; index++)
+      {
+      int len = indices[index].length;
+
+      for(int i=0; i<len; i++)
+        {
+        if( mIndices[variant][index][i] == -1 )
+          {
+          int ind = indices[index][i];
+          float[] ver = vertices[ind];
+          mTmpArray.add(ver);
+          markAllVertices(ver,vertices,indices,pointer,variant);
+          pointer++;
+          }
+        }
+      }
+
+    int len = mTmpArray.size();
+    mVertices[variant] = new float[len][];
+
+    for(int i=0; i<len; i++)
+      {
+      mVertices[variant][i] = mTmpArray.remove(0);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cubitExists(float[] pos, float x, float y, float z)
+    {
+    int len = pos.length/3;
+
+    for(int i=0; i<len; i++)
+      if( pos[3*i]==x && pos[3*i+1]==y && pos[3*i+2]==z ) return true;
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createRight(int x, ArrayList<float[]> list)
+    {
+    for(int y=0; y<mMax; y++)
+      for(int z=0; z<mMax; z++)
+        {
+        boolean b = (y<mY && z<mZ) && ( mTmp[x][mY-1-y][mZ-1-z] && (x+1>=mX || !mTmp[x+1][mY-1-y][mZ-1-z]) );
+        mWall[z][y] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_XP,x);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createLeft(int x, ArrayList<float[]> list)
+    {
+    for(int y=0; y<mMax; y++)
+      for(int z=0; z<mMax; z++)
+        {
+        boolean b = (y<mY && z<mZ) && ( mTmp[x][mY-1-y][z] && (x<1 || !mTmp[x-1][mY-1-y][z]) );
+        mWall[z][y] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_XM,x);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createTop(int y, ArrayList<float[]> list)
+    {
+    for(int z=0; z<mMax; z++)
+      for(int x=0; x<mMax; x++)
+        {
+        boolean b = (x<mX && z<mZ) && ( mTmp[x][y][z] && (y+1>=mY || !mTmp[x][y+1][z]) );
+        mWall[x][z] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_YP,y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createBottom(int y, ArrayList<float[]> list)
+    {
+    for(int z=0; z<mMax; z++)
+      for(int x=0; x<mMax; x++)
+        {
+        boolean b = (x<mX && z<mZ) && ( mTmp[x][y][mZ-1-z] && (y<1 || !mTmp[x][y-1][mZ-1-z]) );
+        mWall[x][z] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_YM,y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createFront(int z, ArrayList<float[]> list)
+    {
+    for(int y=0; y<mMax; y++)
+      for(int x=0; x<mMax; x++)
+        {
+        boolean b = (x<mX && y<mY) && ( mTmp[x][mY-1-y][z] && (z+1>=mZ || !mTmp[x][mY-1-y][z+1]) );
+        mWall[x][y] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_ZP,z);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createBack(int z, ArrayList<float[]> list)
+    {
+    for(int y=0; y<mMax; y++)
+      for(int x=0; x<mMax; x++)
+        {
+        boolean b = (x<mX && y<mY) && ( mTmp[mX-1-x][mY-1-y][z] && (z<1 || !mTmp[mX-1-x][mY-1-y][z-1]) );
+        mWall[x][y] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_ZM,z);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void markNeighbours(int[][] wall, int x, int y, int section)
+    {
+    wall[x][y] = section;
+
+    if( x  >   0 && wall[x-1][y]==WALL_MARKED ) markNeighbours(wall,x-1,y,section);
+    if( x+1<mMax && wall[x+1][y]==WALL_MARKED ) markNeighbours(wall,x+1,y,section);
+    if( y  >   0 && wall[x][y-1]==WALL_MARKED ) markNeighbours(wall,x,y-1,section);
+    if( y+1<mMax && wall[x][y+1]==WALL_MARKED ) markNeighbours(wall,x,y+1,section);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int markSections(int[][] wall)
+    {
+    int sections = 0;
+
+    for(int x=0; x<mMax; x++)
+      for(int y=0; y<mMax; y++)
+        if( wall[x][y]==WALL_MARKED )
+          {
+          sections++;
+          markNeighbours(wall,x,y,sections);
+          }
+
+    return sections;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return true iff exactly three or exactly one of the four values (x1,x2,x3,x4) are equal to 'value'.
+
+  private boolean threeOrOne(int x1, int x2, int x3, int x4, int value)
+    {
+    if( x1==value ) return x2==value ? (x3==value)^(x4==value) : (x3==value)^(x4!=value);
+    else            return x2==value ? (x3==value)^(x4!=value) : (x3==value)^(x4==value);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean isOddVertical(int x, int y)
+    {
+    int number = 0;
+
+    for(int i=0; i<y; i++)
+      if( mPoints[x][i]==0 ) number++;
+
+    return (number%2)==0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean isOddHorizontal(int x, int y)
+    {
+    int number = 0;
+
+    for(int i=0; i<x; i++)
+      if( mPoints[i][y]==0 ) number++;
+
+    return (number%2)==0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int moveUp(int x, int y)
+    {
+    for(int i=y-1; i>=0; i--)
+      if( mPoints[x][i]==0 ) return i;
+
+    android.util.Log.e("D", "moveUp error!");
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int moveDown(int x, int y)
+    {
+    for(int i=y+1; i<=mMax; i++)
+      if( mPoints[x][i]==0 ) return i;
+
+    android.util.Log.e("D", "moveDown error!");
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int moveLeft(int x, int y)
+    {
+    for(int i=x-1; i>=0; i--)
+      if( mPoints[i][y]==0 ) return i;
+
+    android.util.Log.e("D", "moveLeft error!");
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int moveRight(int x, int y)
+    {
+    for(int i=x+1; i<=mMax; i++)
+      if( mPoints[i][y]==0 ) return i;
+
+    android.util.Log.e("D", "moveRight error!");
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[] buildVertices(int[][] wall, int section, float dx, float dy)
+    {
+    int numPoints = buildPoints(wall,section);
+    int numSections = numPoints/2;
+    float[] vertices = new float[3*numPoints];
+    int x=0,y=0,pointer=0;
+
+    for(int i=0; i<=mMax; i++)
+      for(int j=0; j<=mMax; j++)
+        if( mPoints[i][j]==0 )
+          {
+          x = i;
+          y = j;
+          i = j = mMax+1;
+          }
+
+    for(int s=0; s<numSections; s++)
+      {
+      vertices[6*pointer  ] = x-dx;
+      vertices[6*pointer+1] = dy-y;
+      vertices[6*pointer+2] = 0.0f;
+
+      y = isOddVertical(x,y) ? moveDown(x,y) : moveUp(x,y);
+
+      vertices[6*pointer+3] = x-dx;
+      vertices[6*pointer+4] = dy-y;
+      vertices[6*pointer+5] = 0.0f;
+
+      x = isOddHorizontal(x,y) ? moveRight(x,y) : moveLeft(x,y);
+
+      pointer++;
+      }
+
+    return vertices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int buildPoints(int[][] wall, int section)
+    {
+    int numPoints=0;
+    boolean thereIsCorner;
+
+    for(int x=0; x<=mMax; x++)
+      for(int y=0; y<=mMax; y++)
+        {
+        if( y==0 )
+          {
+               if( x==   0 ) thereIsCorner = (wall[     0][0]==section);
+          else if( x==mMax ) thereIsCorner = (wall[mMax-1][0]==section);
+          else               thereIsCorner = (wall[x-1][0]==section)^(wall[x][0]==section);
+          }
+        else if( y==mMax )
+          {
+               if( x==   0 ) thereIsCorner = (wall[     0][mMax-1]==section);
+          else if( x==mMax ) thereIsCorner = (wall[mMax-1][mMax-1]==section);
+          else               thereIsCorner = (wall[x-1][mMax-1]==section)^(wall[x][mMax-1]==section);
+          }
+        else if( x==0 )
+          {
+          thereIsCorner = (wall[0][y-1]==section)^(wall[0][y]==section);
+          }
+        else if( x==mMax )
+          {
+          thereIsCorner = (wall[mMax-1][y-1]==section)^(wall[mMax-1][y]==section);
+          }
+        else
+          {
+          thereIsCorner = threeOrOne(wall[x-1][y-1],wall[x-1][y],wall[x][y-1],wall[x][y],section);
+          }
+
+        if( thereIsCorner )
+          {
+          mPoints[x][y] = 0;
+          numPoints++;
+          }
+        else
+          {
+          mPoints[x][y] =-1;
+          }
+        }
+
+    return numPoints;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void rotateAndMoveVertices(float[] vertices, int axis, int layer)
+    {
+    int i,len = vertices.length/3;
+
+    switch(axis)
+      {
+      case AXIS_XP: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] =-vertices[3*i  ];
+                      vertices[3*i  ] = layer-(dX-1.0f);
+                      }
+                    break;
+      case AXIS_XM: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] = vertices[3*i  ];
+                      vertices[3*i  ] = layer-dX;
+                      }
+                    break;
+      case AXIS_YP: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] =-vertices[3*i+1];
+                      vertices[3*i+1] = layer-(dY-1.0f);
+                      }
+                    break;
+      case AXIS_YM: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] = vertices[3*i+1];
+                      vertices[3*i+1] = layer-dY;
+                      }
+                    break;
+      case AXIS_ZP: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] = layer-(dZ-1.0f);
+                      }
+                    break;
+      case AXIS_ZM: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] = layer-dZ;
+                      vertices[3*i  ] =-vertices[3*i  ];
+                      }
+                    break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 1. assume the 'wall' is in the XY plane
+// 2. split the wall into individual connected regions and for each such region:
+//   a. build the list of vertices (Z=0)
+//   b. take the axis into consideration and rotate the vertices.
+//   c. take layer into consideration and move the vertices.
+//   d. add the resulting vertices to the list.
+
+  private void createVertices(ArrayList<float[]> list, int[][] wall, int axis, int layer)
+    {
+    int sections = markSections(wall);
+
+    float dx = (axis==AXIS_XP || axis==AXIS_XM) ? dZ : dX;
+    float dy = (axis==AXIS_YP || axis==AXIS_YM) ? dZ : dY;
+
+    for(int i=0; i<sections; i++)
+      {
+      float[] vertices = buildVertices(wall,i+1,dx,dy);
+
+      rotateAndMoveVertices(vertices,axis,layer);
+      list.add(vertices);
+/*
+      int len = vertices.length/3;
+      String w="";
+
+      for(int j=0; j<len; j++)
+        {
+        w += ( "["+vertices[3*j]+" "+vertices[3*j+1]+" "+vertices[3*j+2]+"] ");
+        }
+      android.util.Log.e("D", "1 section: "+i+" axis: "+axis+" layer: "+layer+" vertices after: "+w);
+*/
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static boolean vertInFace(float[] vertex, float[] move, Static3D faceAxis, float dist)
+    {
+    final float MAX_ERROR = 0.01f;
+
+    float x= faceAxis.get0();
+    float y= faceAxis.get1();
+    float z= faceAxis.get2();
+
+    float a = (vertex[0]+move[0])*x + (vertex[1]+move[1])*y + (vertex[2]+move[2])*z;
+    float diff = a - dist;
+
+    return diff>-MAX_ERROR && diff<MAX_ERROR;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// (vertices,indices) define a cubit face, i.e. a connected subset of the NxN grid.
+// Return its 'diameter', i.e. max(width,height)
+
+  private int faceDiameter(float[][] vertices, int[] indices)
+    {
+    float maxX = -dX;
+    float minX =  dX;
+    float maxY = -dY;
+    float minY =  dY;
+    float maxZ = -dZ;
+    float minZ =  dZ;
+
+    for (int index : indices)
+      {
+      float[] v = vertices[index];
+
+      if (v[0] > maxX) maxX = v[0];
+      if (v[0] < minX) minX = v[0];
+      if (v[1] > maxY) maxY = v[1];
+      if (v[1] < minY) minY = v[1];
+      if (v[2] > maxZ) maxZ = v[2];
+      if (v[2] < minZ) minZ = v[2];
+      }
+
+    float diffX = maxX-minX;
+    float diffY = maxY-minY;
+    float diffZ = maxZ-minZ;
+
+    float max = diffX>diffY ? Math.max(diffX,diffZ) : Math.max(diffY,diffZ);
+
+    return (int)max;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return array of:
+// 0 if this is an inner face, 1 if its diameter is 1, 2 if diameter is 2, 3 if 3, etc
+// but only up to 5 (because the number of bands is 6 - see createIrregularFaceShape() )
+
+  private int[] generateBandIndices(float[][] vertices, int[][] indices, float[] move)
+    {
+    int numCubitFaces = indices.length;
+    int[] bandIndices = new int[numCubitFaces];
+
+    for(int cubitFace=0; cubitFace<numCubitFaces; cubitFace++)
+      {
+      bandIndices[cubitFace] = 0xffffffff;
+      int numVertices = indices[cubitFace].length;
+
+      for(int vertex=0; vertex<numVertices; vertex++)
+        {
+        int vertBelongsBitmap = 0x00000000;
+        float[] vert = vertices[ indices[cubitFace][vertex] ];
+
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[0], dX) ) vertBelongsBitmap |= (1   );
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[1], dX) ) vertBelongsBitmap |= (1<<1);
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[2], dY) ) vertBelongsBitmap |= (1<<2);
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[3], dY) ) vertBelongsBitmap |= (1<<3);
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[4], dZ) ) vertBelongsBitmap |= (1<<4);
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[5], dZ) ) vertBelongsBitmap |= (1<<5);
+
+        bandIndices[cubitFace] &= vertBelongsBitmap;
+        }
+
+      if( bandIndices[cubitFace]!=0 ) // outer face
+        {
+        int diameter = faceDiameter(vertices, indices[cubitFace]);
+        bandIndices[cubitFace] = (diameter>=6 ? 5:diameter);
+        }
+
+      //android.util.Log.e("D", "cubit face "+cubitFace+" : "+bandIndices[cubitFace]);
+      }
+
+    return bandIndices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeVectorFace(float[] prev, float[] curr, float[] next)
+    {
+    float ax = prev[0]-curr[0];
+    float ay = prev[1]-curr[1];
+    float az = prev[2]-curr[2];
+
+    float bx = next[0]-curr[0];
+    float by = next[1]-curr[1];
+    float bz = next[2]-curr[2];
+
+    float lena = (float)Math.sqrt(ax*ax + ay*ay + az*az);
+    float lenb = (float)Math.sqrt(bx*bx + by*by + bz*bz);
+
+    ax /= lena;
+    ay /= lena;
+    az /= lena;
+
+    bx /= lenb;
+    by /= lenb;
+    bz /= lenb;
+
+    float cx = ax + bx + ay*bz-az*by;
+    float cy = ay + by + az*bx-ax*bz;
+    float cz = az + bz + ax*by-ay*bx;
+
+    return (cx<0 ? 0:4) + (cy<0 ? 0:2) + (cz<0 ? 0:1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[] computeVector(int index, float[][] vertices, int[][] indices, int[] bandIndices)
+    {
+    int band=0;
+    int len = indices.length;
+    int vector=-1;
+
+    for(int i=0; i<len; i++)
+      {
+      int len2 = indices[i].length;
+
+      for(int j=0; j<len2; j++)
+        {
+        if( indices[i][j]==index )
+          {
+          int prev = j>0 ? j-1 : len2-1;
+          int next = j<len2-1 ? j+1 : 0;
+
+          int prevIndex = indices[i][prev];
+          int currIndex = indices[i][j];
+          int nextIndex = indices[i][next];
+
+          int newVector = computeVectorFace(vertices[prevIndex],vertices[currIndex],vertices[nextIndex]);
+          if( vector!=-1 && vector!=newVector ) return null;
+
+          vector = newVector;
+          band |= bandIndices[i];
+          }
+        }
+      }
+
+    return band==0 ? null : VECTOR[vector];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[][] generateVectors(float[][] vertices, int[][] indices, int[] bandIndices)
+    {
+    int len = vertices.length;
+    float[][] vectors = new float[len][];
+
+    for(int i=0; i<len; i++)
+      {
+      vectors[i] = computeVector(i,vertices,indices,bandIndices);
+      }
+
+    return vectors;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void debug(float[][] vert, int[][] ind)
+    {
+    String vv="VERTICES: ";
+    for (float[] floats : vert)
+      {
+      vv += "\n";
+      int lenV2 = floats.length / 3;
+
+      for (int v2 = 0; v2 < lenV2; v2++)
+        {
+        vv += " {";
+        vv += (floats[3 * v2] + " ");
+        vv += (floats[3 * v2 + 1] + " ");
+        vv += (floats[3 * v2 + 2] + " ");
+        vv += "}";
+        }
+      }
+    android.util.Log.e("D", vv);
+
+    String ii="INDICES: ";
+    for (int[] ints : ind)
+      {
+      ii += "\n";
+      int lenI2 = ints.length;
+
+      for (int i2 = 0; i2 < lenI2; i2++)
+        {
+        ii += (ints[i2] + " ");
+        }
+      }
+    android.util.Log.e("D", ii);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public static FactoryBandagedCuboid getInstance()
+    {
+    if( mThis==null ) mThis = new FactoryBandagedCuboid();
+    return mThis;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void prepare(int numVariants, int x, int y, int z)
+    {
+    if( mVertexArray==null ) mVertexArray = new ArrayList<>();
+    mVertices= new float[numVariants][][];
+    mIndices = new int[numVariants][][];
+    mMove = new float[numVariants][3];
+    mBandIndices = new int[numVariants][];
+
+    mX = x;
+    mY = y;
+    mZ = z;
+
+    dX = mX/2.0f;
+    dY = mY/2.0f;
+    dZ = mZ/2.0f;
+
+    mMax = mX>mY ? Math.max(mX,mZ) : Math.max(mY,mZ);
+
+    mWall   = new int[mMax][mMax];
+    mPoints = new int[mMax+1][mMax+1];
+    mTmp    = new boolean[mX][mY][mZ];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectShape createIrregularShape(int variant, float[] pos)
+    {
+    mVertexArray.clear();
+
+    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++) mTmp[x][y][z] = cubitExists(pos,begX+x,begY+y,begZ+z);
+
+    for(int x=0; x<mX; x++) createRight (x,mVertexArray);
+    for(int x=0; x<mX; x++) createLeft  (x,mVertexArray);
+    for(int y=0; y<mY; y++) createTop   (y,mVertexArray);
+    for(int y=0; y<mY; y++) createBottom(y,mVertexArray);
+    for(int z=0; z<mZ; z++) createFront (z,mVertexArray);
+    for(int z=0; z<mZ; z++) createBack  (z,mVertexArray);
+
+    float[][] verts = getVertices(mVertexArray,pos,variant);
+    int[][] inds    = getIndices(mVertexArray);
+
+    compressVerticesAndIndices(variant,verts,inds);
+    mBandIndices[variant] = generateBandIndices(mVertices[variant], mIndices[variant], mMove[variant]);
+
+    return new ObjectShape(mVertices[variant], mIndices[variant]);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectVertexEffects createVertexEffects(int variant, boolean roundCorners)
+    {
+    float[][] vertVec= generateVectors(mVertices[variant], mIndices[variant], mBandIndices[variant]);
+    int numEffects   = mVertices[variant].length;
+    float S          = 0.04f;
+    float[] region   = {0,0,0,0.15f};
+    String[] names   = new String[numEffects];
+    float[][] regions= new float[numEffects][];
+    boolean[] uses   = new boolean[numEffects];
+    float[][] vars   = new float[numEffects][];
+
+    for(int i=0; i<numEffects; i++)
+      {
+      float[] v = vertVec[i];
+
+      if( v!=null )
+        {
+        names[i]  = FactoryCubit.NAME;
+        regions[i]= region;
+        uses[i]   = roundCorners;
+        vars[i]   = new float[] { 0, S*v[0], S*v[1], S*v[2], 1 };
+        }
+      }
+
+    return new ObjectVertexEffects(names,vars,mVertices[variant],regions,uses);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectFaceShape createIrregularFaceShape(int variant, boolean iconMode)
+    {
+    float height= iconMode ? 0.001f : 0.048f;
+    int angle   = 60;
+    float R     = 0.2f;
+    float S     = 0.5f;
+    int numVerts= 5;
+    int extraI  = 0;
+    int extraV  = 0;
+
+    float[][] bands  = { {  0.001f,angle,R,S,numVerts,extraV,extraI},
+                         {height/1,angle,R,S,numVerts,extraV,extraI},
+                         {height/2,angle,R,S,numVerts,extraV,extraI},
+                         {height/3,angle,R,S,numVerts,extraV,extraI},
+                         {height/4,angle,R,S,numVerts,extraV,extraI},
+                         {height/5,angle,R,S,numVerts,extraV,extraI} };
+
+    return new ObjectFaceShape(bands,mBandIndices[variant],null);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public MeshBase createMesh(float[] pos, int x, int y, int z, boolean iconMode, boolean roundCorners)
+    {
+    prepare(1,x,y,z);
+    ObjectShape shape           = createIrregularShape(0,pos);
+    ObjectFaceShape face        = createIrregularFaceShape(0,iconMode);
+    ObjectVertexEffects effects = createVertexEffects(0,roundCorners);
+    int[][] indices             = shape.getVertIndices();
+    int numComponents           = indices.length;
+
+    FactoryCubit factory = FactoryCubit.getInstance();
+    factory.clear();
+    factory.createNewFaceTransform(shape,null);
+    return factory.createRoundedSolid(shape,face,effects,MESH_NICE,numComponents);
+    }
+  }
diff --git a/src/main/java/org/distorted/objectlib/helpers/FactoryBandagedPyraminx.java b/src/main/java/org/distorted/objectlib/helpers/FactoryBandagedPyraminx.java
new file mode 100644
index 00000000..fa4e6afe
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/helpers/FactoryBandagedPyraminx.java
@@ -0,0 +1,920 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.objectlib.helpers;
+
+import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
+
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.type.Static3D;
+import org.distorted.objectlib.touchcontrol.TouchControlHexahedron;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class FactoryBandagedPyraminx
+  {
+  private static final int WALL_MARKED=0;
+  private static final int WALL_EMPTY =-1;
+
+  private static final int AXIS_XP = 0;
+  private static final int AXIS_XM = 1;
+  private static final int AXIS_YP = 2;
+  private static final int AXIS_YM = 3;
+  private static final int AXIS_ZP = 4;
+  private static final int AXIS_ZM = 5;
+
+  private static final float[][] VECTOR =
+      {
+          {-1.0f,-1.0f,-1.0f},
+          {-1.0f,-1.0f,+1.0f},
+          {-1.0f,+1.0f,-1.0f},
+          {-1.0f,+1.0f,+1.0f},
+          {+1.0f,-1.0f,-1.0f},
+          {+1.0f,-1.0f,+1.0f},
+          {+1.0f,+1.0f,-1.0f},
+          {+1.0f,+1.0f,+1.0f}
+      };
+
+  private static FactoryBandagedPyraminx mThis;
+  private ArrayList<float[]> mVertexArray;
+  private ArrayList<float[]> mTmpArray;
+  private float[][][] mVertices;
+  private int[][][] mIndices;
+  private int[][] mBandIndices;
+  private float[][] mMove;
+
+  private int mX, mY, mZ, mMax;
+  private float dX, dY, dZ;
+  private int[][] mWall;
+  private int[][] mPoints;
+  private boolean[][][] mTmp;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private FactoryBandagedPyraminx()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[][] getVertices(ArrayList<float[]> list, float[] move, int variant)
+    {
+    int numMoves = move.length/3;
+    mMove[variant][0]=0.0f;
+    mMove[variant][1]=0.0f;
+    mMove[variant][2]=0.0f;
+
+    for(int m=0; m<numMoves; m++)
+      {
+      mMove[variant][0] += move[3*m  ];
+      mMove[variant][1] += move[3*m+1];
+      mMove[variant][2] += move[3*m+2];
+      }
+
+    mMove[variant][0]/=numMoves;
+    mMove[variant][1]/=numMoves;
+    mMove[variant][2]/=numMoves;
+
+    int total  = 0;
+    int length = list.size();
+    float[][] vertices = new float[length][];
+
+    for( int i=0; i<length; i++ )
+      {
+      vertices[i] = list.get(i);
+      total += vertices[i].length/3;
+      }
+
+    float[][] verts = new float[total][3];
+    int pointer = 0;
+
+    for(int i=0; i<length; i++)
+      {
+      int len = vertices[i].length/3;
+
+      for(int j=0; j<len; j++)
+        {
+        verts[pointer][0] = vertices[i][3*j  ] - mMove[variant][0];
+        verts[pointer][1] = vertices[i][3*j+1] - mMove[variant][1];
+        verts[pointer][2] = vertices[i][3*j+2] - mMove[variant][2];
+        pointer++;
+        }
+      }
+
+    return verts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[][] getIndices(ArrayList<float[]> list)
+    {
+    int indicesSoFar=0;
+    int length = list.size();
+    int[][] indices = new int[length][];
+
+    for( int i=0; i<length; i++ )
+      {
+      float[] f = list.get(i);
+      int len = f.length/3;
+      int[] ind = new int[len];
+      for(int j=0; j<len; j++) ind[j] = (indicesSoFar++);
+      indices[i] = ind;
+      }
+
+    return indices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void markAllVertices(float[] vertex, float[][] vertices, int[][] indices, int pointer, int variant)
+    {
+    int lenI = indices.length;
+
+    for(int index=0; index<lenI; index++)
+      {
+      int len = indices[index].length;
+
+      for(int i=0; i<len; i++)
+        {
+        if( mIndices[variant][index][i] == -1 )
+          {
+          int ind = indices[index][i];
+          float[] ver = vertices[ind];
+
+          if( vertex[0]==ver[0] && vertex[1]==ver[1] && vertex[2]==ver[2] )
+            {
+            mIndices[variant][index][i] = pointer;
+            }
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// So far the 'vertices/indices' are stored inefficiently, with each vertex stored three times
+// (each one normally is a corner of three faces) or even six times. Compress!
+// Example of six times: the central vertex here:
+//
+// { 1.0f,  0.0f, -1.0f,
+//   1.0f, -1.0f, -1.0f,
+//   1.0f, -1.0f, +0.0f,
+//   0.0f, -1.0f, -1.0f },
+
+  private void compressVerticesAndIndices(int variant, float[][] vertices, int[][] indices)
+    {
+    if( mTmpArray==null ) mTmpArray = new ArrayList<>();
+
+    int lenI = indices.length;
+    int pointer=0;
+
+    mIndices[variant] = new int[lenI][];
+
+    for(int index=0; index<lenI; index++)
+      {
+      int len = indices[index].length;
+      mIndices[variant][index] = new int[len];
+      for(int i=0; i<len; i++) mIndices[variant][index][i] = -1;
+      }
+
+    for(int index=0; index<lenI; index++)
+      {
+      int len = indices[index].length;
+
+      for(int i=0; i<len; i++)
+        {
+        if( mIndices[variant][index][i] == -1 )
+          {
+          int ind = indices[index][i];
+          float[] ver = vertices[ind];
+          mTmpArray.add(ver);
+          markAllVertices(ver,vertices,indices,pointer,variant);
+          pointer++;
+          }
+        }
+      }
+
+    int len = mTmpArray.size();
+    mVertices[variant] = new float[len][];
+
+    for(int i=0; i<len; i++)
+      {
+      mVertices[variant][i] = mTmpArray.remove(0);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cubitExists(float[] pos, float x, float y, float z)
+    {
+    int len = pos.length/3;
+
+    for(int i=0; i<len; i++)
+      if( pos[3*i]==x && pos[3*i+1]==y && pos[3*i+2]==z ) return true;
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createRight(int x, ArrayList<float[]> list)
+    {
+    for(int y=0; y<mMax; y++)
+      for(int z=0; z<mMax; z++)
+        {
+        boolean b = (y<mY && z<mZ) && ( mTmp[x][mY-1-y][mZ-1-z] && (x+1>=mX || !mTmp[x+1][mY-1-y][mZ-1-z]) );
+        mWall[z][y] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_XP,x);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createLeft(int x, ArrayList<float[]> list)
+    {
+    for(int y=0; y<mMax; y++)
+      for(int z=0; z<mMax; z++)
+        {
+        boolean b = (y<mY && z<mZ) && ( mTmp[x][mY-1-y][z] && (x<1 || !mTmp[x-1][mY-1-y][z]) );
+        mWall[z][y] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_XM,x);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createTop(int y, ArrayList<float[]> list)
+    {
+    for(int z=0; z<mMax; z++)
+      for(int x=0; x<mMax; x++)
+        {
+        boolean b = (x<mX && z<mZ) && ( mTmp[x][y][z] && (y+1>=mY || !mTmp[x][y+1][z]) );
+        mWall[x][z] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_YP,y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createBottom(int y, ArrayList<float[]> list)
+    {
+    for(int z=0; z<mMax; z++)
+      for(int x=0; x<mMax; x++)
+        {
+        boolean b = (x<mX && z<mZ) && ( mTmp[x][y][mZ-1-z] && (y<1 || !mTmp[x][y-1][mZ-1-z]) );
+        mWall[x][z] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_YM,y);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createFront(int z, ArrayList<float[]> list)
+    {
+    for(int y=0; y<mMax; y++)
+      for(int x=0; x<mMax; x++)
+        {
+        boolean b = (x<mX && y<mY) && ( mTmp[x][mY-1-y][z] && (z+1>=mZ || !mTmp[x][mY-1-y][z+1]) );
+        mWall[x][y] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_ZP,z);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createBack(int z, ArrayList<float[]> list)
+    {
+    for(int y=0; y<mMax; y++)
+      for(int x=0; x<mMax; x++)
+        {
+        boolean b = (x<mX && y<mY) && ( mTmp[mX-1-x][mY-1-y][z] && (z<1 || !mTmp[mX-1-x][mY-1-y][z-1]) );
+        mWall[x][y] = b ? WALL_MARKED : WALL_EMPTY;
+        }
+
+    createVertices(list,mWall,AXIS_ZM,z);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void markNeighbours(int[][] wall, int x, int y, int section)
+    {
+    wall[x][y] = section;
+
+    if( x  >   0 && wall[x-1][y]==WALL_MARKED ) markNeighbours(wall,x-1,y,section);
+    if( x+1<mMax && wall[x+1][y]==WALL_MARKED ) markNeighbours(wall,x+1,y,section);
+    if( y  >   0 && wall[x][y-1]==WALL_MARKED ) markNeighbours(wall,x,y-1,section);
+    if( y+1<mMax && wall[x][y+1]==WALL_MARKED ) markNeighbours(wall,x,y+1,section);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int markSections(int[][] wall)
+    {
+    int sections = 0;
+
+    for(int x=0; x<mMax; x++)
+      for(int y=0; y<mMax; y++)
+        if( wall[x][y]==WALL_MARKED )
+          {
+          sections++;
+          markNeighbours(wall,x,y,sections);
+          }
+
+    return sections;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return true iff exactly three or exactly one of the four values (x1,x2,x3,x4) are equal to 'value'.
+
+  private boolean threeOrOne(int x1, int x2, int x3, int x4, int value)
+    {
+    if( x1==value ) return x2==value ? (x3==value)^(x4==value) : (x3==value)^(x4!=value);
+    else            return x2==value ? (x3==value)^(x4!=value) : (x3==value)^(x4==value);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean isOddVertical(int x, int y)
+    {
+    int number = 0;
+
+    for(int i=0; i<y; i++)
+      if( mPoints[x][i]==0 ) number++;
+
+    return (number%2)==0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean isOddHorizontal(int x, int y)
+    {
+    int number = 0;
+
+    for(int i=0; i<x; i++)
+      if( mPoints[i][y]==0 ) number++;
+
+    return (number%2)==0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int moveUp(int x, int y)
+    {
+    for(int i=y-1; i>=0; i--)
+      if( mPoints[x][i]==0 ) return i;
+
+    android.util.Log.e("D", "moveUp error!");
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int moveDown(int x, int y)
+    {
+    for(int i=y+1; i<=mMax; i++)
+      if( mPoints[x][i]==0 ) return i;
+
+    android.util.Log.e("D", "moveDown error!");
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int moveLeft(int x, int y)
+    {
+    for(int i=x-1; i>=0; i--)
+      if( mPoints[i][y]==0 ) return i;
+
+    android.util.Log.e("D", "moveLeft error!");
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int moveRight(int x, int y)
+    {
+    for(int i=x+1; i<=mMax; i++)
+      if( mPoints[i][y]==0 ) return i;
+
+    android.util.Log.e("D", "moveRight error!");
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[] buildVertices(int[][] wall, int section, float dx, float dy)
+    {
+    int numPoints = buildPoints(wall,section);
+    int numSections = numPoints/2;
+    float[] vertices = new float[3*numPoints];
+    int x=0,y=0,pointer=0;
+
+    for(int i=0; i<=mMax; i++)
+      for(int j=0; j<=mMax; j++)
+        if( mPoints[i][j]==0 )
+          {
+          x = i;
+          y = j;
+          i = j = mMax+1;
+          }
+
+    for(int s=0; s<numSections; s++)
+      {
+      vertices[6*pointer  ] = x-dx;
+      vertices[6*pointer+1] = dy-y;
+      vertices[6*pointer+2] = 0.0f;
+
+      y = isOddVertical(x,y) ? moveDown(x,y) : moveUp(x,y);
+
+      vertices[6*pointer+3] = x-dx;
+      vertices[6*pointer+4] = dy-y;
+      vertices[6*pointer+5] = 0.0f;
+
+      x = isOddHorizontal(x,y) ? moveRight(x,y) : moveLeft(x,y);
+
+      pointer++;
+      }
+
+    return vertices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int buildPoints(int[][] wall, int section)
+    {
+    int numPoints=0;
+    boolean thereIsCorner;
+
+    for(int x=0; x<=mMax; x++)
+      for(int y=0; y<=mMax; y++)
+        {
+        if( y==0 )
+          {
+               if( x==   0 ) thereIsCorner = (wall[     0][0]==section);
+          else if( x==mMax ) thereIsCorner = (wall[mMax-1][0]==section);
+          else               thereIsCorner = (wall[x-1][0]==section)^(wall[x][0]==section);
+          }
+        else if( y==mMax )
+          {
+               if( x==   0 ) thereIsCorner = (wall[     0][mMax-1]==section);
+          else if( x==mMax ) thereIsCorner = (wall[mMax-1][mMax-1]==section);
+          else               thereIsCorner = (wall[x-1][mMax-1]==section)^(wall[x][mMax-1]==section);
+          }
+        else if( x==0 )
+          {
+          thereIsCorner = (wall[0][y-1]==section)^(wall[0][y]==section);
+          }
+        else if( x==mMax )
+          {
+          thereIsCorner = (wall[mMax-1][y-1]==section)^(wall[mMax-1][y]==section);
+          }
+        else
+          {
+          thereIsCorner = threeOrOne(wall[x-1][y-1],wall[x-1][y],wall[x][y-1],wall[x][y],section);
+          }
+
+        if( thereIsCorner )
+          {
+          mPoints[x][y] = 0;
+          numPoints++;
+          }
+        else
+          {
+          mPoints[x][y] =-1;
+          }
+        }
+
+    return numPoints;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void rotateAndMoveVertices(float[] vertices, int axis, int layer)
+    {
+    int i,len = vertices.length/3;
+
+    switch(axis)
+      {
+      case AXIS_XP: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] =-vertices[3*i  ];
+                      vertices[3*i  ] = layer-(dX-1.0f);
+                      }
+                    break;
+      case AXIS_XM: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] = vertices[3*i  ];
+                      vertices[3*i  ] = layer-dX;
+                      }
+                    break;
+      case AXIS_YP: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] =-vertices[3*i+1];
+                      vertices[3*i+1] = layer-(dY-1.0f);
+                      }
+                    break;
+      case AXIS_YM: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] = vertices[3*i+1];
+                      vertices[3*i+1] = layer-dY;
+                      }
+                    break;
+      case AXIS_ZP: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] = layer-(dZ-1.0f);
+                      }
+                    break;
+      case AXIS_ZM: for(i=0; i<len; i++)
+                      {
+                      vertices[3*i+2] = layer-dZ;
+                      vertices[3*i  ] =-vertices[3*i  ];
+                      }
+                    break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 1. assume the 'wall' is in the XY plane
+// 2. split the wall into individual connected regions and for each such region:
+//   a. build the list of vertices (Z=0)
+//   b. take the axis into consideration and rotate the vertices.
+//   c. take layer into consideration and move the vertices.
+//   d. add the resulting vertices to the list.
+
+  private void createVertices(ArrayList<float[]> list, int[][] wall, int axis, int layer)
+    {
+    int sections = markSections(wall);
+
+    float dx = (axis==AXIS_XP || axis==AXIS_XM) ? dZ : dX;
+    float dy = (axis==AXIS_YP || axis==AXIS_YM) ? dZ : dY;
+
+    for(int i=0; i<sections; i++)
+      {
+      float[] vertices = buildVertices(wall,i+1,dx,dy);
+
+      rotateAndMoveVertices(vertices,axis,layer);
+      list.add(vertices);
+/*
+      int len = vertices.length/3;
+      String w="";
+
+      for(int j=0; j<len; j++)
+        {
+        w += ( "["+vertices[3*j]+" "+vertices[3*j+1]+" "+vertices[3*j+2]+"] ");
+        }
+      android.util.Log.e("D", "1 section: "+i+" axis: "+axis+" layer: "+layer+" vertices after: "+w);
+*/
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static boolean vertInFace(float[] vertex, float[] move, Static3D faceAxis, float dist)
+    {
+    final float MAX_ERROR = 0.01f;
+
+    float x= faceAxis.get0();
+    float y= faceAxis.get1();
+    float z= faceAxis.get2();
+
+    float a = (vertex[0]+move[0])*x + (vertex[1]+move[1])*y + (vertex[2]+move[2])*z;
+    float diff = a - dist;
+
+    return diff>-MAX_ERROR && diff<MAX_ERROR;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// (vertices,indices) define a cubit face, i.e. a connected subset of the NxN grid.
+// Return its 'diameter', i.e. max(width,height)
+
+  private int faceDiameter(float[][] vertices, int[] indices)
+    {
+    float maxX = -dX;
+    float minX =  dX;
+    float maxY = -dY;
+    float minY =  dY;
+    float maxZ = -dZ;
+    float minZ =  dZ;
+
+    for (int index : indices)
+      {
+      float[] v = vertices[index];
+
+      if (v[0] > maxX) maxX = v[0];
+      if (v[0] < minX) minX = v[0];
+      if (v[1] > maxY) maxY = v[1];
+      if (v[1] < minY) minY = v[1];
+      if (v[2] > maxZ) maxZ = v[2];
+      if (v[2] < minZ) minZ = v[2];
+      }
+
+    float diffX = maxX-minX;
+    float diffY = maxY-minY;
+    float diffZ = maxZ-minZ;
+
+    float max = diffX>diffY ? Math.max(diffX,diffZ) : Math.max(diffY,diffZ);
+
+    return (int)max;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return array of:
+// 0 if this is an inner face, 1 if its diameter is 1, 2 if diameter is 2, 3 if 3, etc
+// but only up to 5 (because the number of bands is 6 - see createIrregularFaceShape() )
+
+  private int[] generateBandIndices(float[][] vertices, int[][] indices, float[] move)
+    {
+    int numCubitFaces = indices.length;
+    int[] bandIndices = new int[numCubitFaces];
+
+    for(int cubitFace=0; cubitFace<numCubitFaces; cubitFace++)
+      {
+      bandIndices[cubitFace] = 0xffffffff;
+      int numVertices = indices[cubitFace].length;
+
+      for(int vertex=0; vertex<numVertices; vertex++)
+        {
+        int vertBelongsBitmap = 0x00000000;
+        float[] vert = vertices[ indices[cubitFace][vertex] ];
+
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[0], dX) ) vertBelongsBitmap |= (1   );
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[1], dX) ) vertBelongsBitmap |= (1<<1);
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[2], dY) ) vertBelongsBitmap |= (1<<2);
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[3], dY) ) vertBelongsBitmap |= (1<<3);
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[4], dZ) ) vertBelongsBitmap |= (1<<4);
+        if( vertInFace(vert, move, TouchControlHexahedron.FACE_AXIS[5], dZ) ) vertBelongsBitmap |= (1<<5);
+
+        bandIndices[cubitFace] &= vertBelongsBitmap;
+        }
+
+      if( bandIndices[cubitFace]!=0 ) // outer face
+        {
+        int diameter = faceDiameter(vertices, indices[cubitFace]);
+        bandIndices[cubitFace] = (diameter>=6 ? 5:diameter);
+        }
+
+      //android.util.Log.e("D", "cubit face "+cubitFace+" : "+bandIndices[cubitFace]);
+      }
+
+    return bandIndices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeVectorFace(float[] prev, float[] curr, float[] next)
+    {
+    float ax = prev[0]-curr[0];
+    float ay = prev[1]-curr[1];
+    float az = prev[2]-curr[2];
+
+    float bx = next[0]-curr[0];
+    float by = next[1]-curr[1];
+    float bz = next[2]-curr[2];
+
+    float lena = (float)Math.sqrt(ax*ax + ay*ay + az*az);
+    float lenb = (float)Math.sqrt(bx*bx + by*by + bz*bz);
+
+    ax /= lena;
+    ay /= lena;
+    az /= lena;
+
+    bx /= lenb;
+    by /= lenb;
+    bz /= lenb;
+
+    float cx = ax + bx + ay*bz-az*by;
+    float cy = ay + by + az*bx-ax*bz;
+    float cz = az + bz + ax*by-ay*bx;
+
+    return (cx<0 ? 0:4) + (cy<0 ? 0:2) + (cz<0 ? 0:1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[] computeVector(int index, float[][] vertices, int[][] indices, int[] bandIndices)
+    {
+    int band=0;
+    int len = indices.length;
+    int vector=-1;
+
+    for(int i=0; i<len; i++)
+      {
+      int len2 = indices[i].length;
+
+      for(int j=0; j<len2; j++)
+        {
+        if( indices[i][j]==index )
+          {
+          int prev = j>0 ? j-1 : len2-1;
+          int next = j<len2-1 ? j+1 : 0;
+
+          int prevIndex = indices[i][prev];
+          int currIndex = indices[i][j];
+          int nextIndex = indices[i][next];
+
+          int newVector = computeVectorFace(vertices[prevIndex],vertices[currIndex],vertices[nextIndex]);
+          if( vector!=-1 && vector!=newVector ) return null;
+
+          vector = newVector;
+          band |= bandIndices[i];
+          }
+        }
+      }
+
+    return band==0 ? null : VECTOR[vector];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[][] generateVectors(float[][] vertices, int[][] indices, int[] bandIndices)
+    {
+    int len = vertices.length;
+    float[][] vectors = new float[len][];
+
+    for(int i=0; i<len; i++)
+      {
+      vectors[i] = computeVector(i,vertices,indices,bandIndices);
+      }
+
+    return vectors;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void debug(float[][] vert, int[][] ind)
+    {
+    String vv="VERTICES: ";
+    for (float[] floats : vert)
+      {
+      vv += "\n";
+      int lenV2 = floats.length / 3;
+
+      for (int v2 = 0; v2 < lenV2; v2++)
+        {
+        vv += " {";
+        vv += (floats[3 * v2] + " ");
+        vv += (floats[3 * v2 + 1] + " ");
+        vv += (floats[3 * v2 + 2] + " ");
+        vv += "}";
+        }
+      }
+    android.util.Log.e("D", vv);
+
+    String ii="INDICES: ";
+    for (int[] ints : ind)
+      {
+      ii += "\n";
+      int lenI2 = ints.length;
+
+      for (int i2 = 0; i2 < lenI2; i2++)
+        {
+        ii += (ints[i2] + " ");
+        }
+      }
+    android.util.Log.e("D", ii);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public static FactoryBandagedPyraminx getInstance()
+    {
+    if( mThis==null ) mThis = new FactoryBandagedPyraminx();
+    return mThis;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void prepare(int numVariants, int x)
+    {
+    if( mVertexArray==null ) mVertexArray = new ArrayList<>();
+    mVertices= new float[numVariants][][];
+    mIndices = new int[numVariants][][];
+    mMove = new float[numVariants][3];
+    mBandIndices = new int[numVariants][];
+
+    mX = x;
+
+
+    dX = mX/2.0f;
+    dY = mY/2.0f;
+    dZ = mZ/2.0f;
+
+    mMax = mX>mY ? Math.max(mX,mZ) : Math.max(mY,mZ);
+
+    mWall   = new int[mMax][mMax];
+    mPoints = new int[mMax+1][mMax+1];
+    mTmp    = new boolean[mX][mY][mZ];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectShape createIrregularShape(int variant, float[] pos)
+    {
+    mVertexArray.clear();
+
+    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++) mTmp[x][y][z] = cubitExists(pos,begX+x,begY+y,begZ+z);
+
+    for(int x=0; x<mX; x++) createRight (x,mVertexArray);
+    for(int x=0; x<mX; x++) createLeft  (x,mVertexArray);
+    for(int y=0; y<mY; y++) createTop   (y,mVertexArray);
+    for(int y=0; y<mY; y++) createBottom(y,mVertexArray);
+    for(int z=0; z<mZ; z++) createFront (z,mVertexArray);
+    for(int z=0; z<mZ; z++) createBack  (z,mVertexArray);
+
+    float[][] verts = getVertices(mVertexArray,pos,variant);
+    int[][] inds    = getIndices(mVertexArray);
+
+    compressVerticesAndIndices(variant,verts,inds);
+    mBandIndices[variant] = generateBandIndices(mVertices[variant], mIndices[variant], mMove[variant]);
+
+    return new ObjectShape(mVertices[variant], mIndices[variant]);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectVertexEffects createVertexEffects(int variant, boolean roundCorners)
+    {
+    float[][] vertVec= generateVectors(mVertices[variant], mIndices[variant], mBandIndices[variant]);
+    int numEffects   = mVertices[variant].length;
+    float S          = 0.04f;
+    float[] region   = {0,0,0,0.15f};
+    String[] names   = new String[numEffects];
+    float[][] regions= new float[numEffects][];
+    boolean[] uses   = new boolean[numEffects];
+    float[][] vars   = new float[numEffects][];
+
+    for(int i=0; i<numEffects; i++)
+      {
+      float[] v = vertVec[i];
+
+      if( v!=null )
+        {
+        names[i]  = FactoryCubit.NAME;
+        regions[i]= region;
+        uses[i]   = roundCorners;
+        vars[i]   = new float[] { 0, S*v[0], S*v[1], S*v[2], 1 };
+        }
+      }
+
+    return new ObjectVertexEffects(names,vars,mVertices[variant],regions,uses);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectFaceShape createIrregularFaceShape(int variant, boolean iconMode)
+    {
+    float height= iconMode ? 0.001f : 0.048f;
+    int angle   = 60;
+    float R     = 0.2f;
+    float S     = 0.5f;
+    int numVerts= 5;
+    int extraI  = 0;
+    int extraV  = 0;
+
+    float[][] bands  = { {  0.001f,angle,R,S,numVerts,extraV,extraI},
+                         {height/1,angle,R,S,numVerts,extraV,extraI},
+                         {height/2,angle,R,S,numVerts,extraV,extraI},
+                         {height/3,angle,R,S,numVerts,extraV,extraI},
+                         {height/4,angle,R,S,numVerts,extraV,extraI},
+                         {height/5,angle,R,S,numVerts,extraV,extraI} };
+
+    return new ObjectFaceShape(bands,mBandIndices[variant],null);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public MeshBase createMesh(float[] pos, int x, boolean iconMode, boolean roundCorners)
+    {
+    prepare(1,x);
+    ObjectShape shape           = createIrregularShape(0,pos);
+    ObjectFaceShape face        = createIrregularFaceShape(0,iconMode);
+    ObjectVertexEffects effects = createVertexEffects(0,roundCorners);
+    int[][] indices             = shape.getVertIndices();
+    int numComponents           = indices.length;
+
+    FactoryCubit factory = FactoryCubit.getInstance();
+    factory.clear();
+    factory.createNewFaceTransform(shape,null);
+    return factory.createRoundedSolid(shape,face,effects,MESH_NICE,numComponents);
+    }
+  }
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyBandagedCuboid.java b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedCuboid.java
index d7dd3545..2e1a930a 100644
--- a/src/main/java/org/distorted/objectlib/objects/TwistyBandagedCuboid.java
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedCuboid.java
@@ -16,7 +16,7 @@ import org.distorted.library.main.DistortedLibrary;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 
-import org.distorted.objectlib.helpers.FactoryBandagedCubit;
+import org.distorted.objectlib.helpers.FactoryBandagedCuboid;
 import org.distorted.objectlib.helpers.ObjectFaceShape;
 import org.distorted.objectlib.helpers.ObjectSignature;
 import org.distorted.objectlib.helpers.ObjectVertexEffects;
@@ -459,7 +459,7 @@ public class TwistyBandagedCuboid extends TwistyBandagedAbstract
       }
     else
       {
-      FactoryBandagedCubit factory = FactoryBandagedCubit.getInstance();
+      FactoryBandagedCuboid factory = FactoryBandagedCuboid.getInstance();
       mTmpShapes[variant] = factory.createIrregularShape(variant,positions[cubit]);
       }
     }
@@ -532,7 +532,7 @@ public class TwistyBandagedCuboid extends TwistyBandagedAbstract
       return new ObjectFaceShape(bands,bandIndices,null);
       }
 
-    FactoryBandagedCubit factory = FactoryBandagedCubit.getInstance();
+    FactoryBandagedCuboid factory = FactoryBandagedCuboid.getInstance();
     return factory.createIrregularFaceShape(variant, isInIconMode() );
     }
 
@@ -571,7 +571,7 @@ public class TwistyBandagedCuboid extends TwistyBandagedAbstract
       return new ObjectVertexEffects(names,variables,vertices,regions,uses);
       }
 
-    FactoryBandagedCubit factory = FactoryBandagedCubit.getInstance();
+    FactoryBandagedCuboid factory = FactoryBandagedCuboid.getInstance();
     return factory.createVertexEffects(variant,round);
     }
 
@@ -640,7 +640,7 @@ public class TwistyBandagedCuboid extends TwistyBandagedAbstract
           }
         }
 
-      FactoryBandagedCubit factory = FactoryBandagedCubit.getInstance();
+      FactoryBandagedCuboid factory = FactoryBandagedCuboid.getInstance();
       factory.prepare(mNumVariants,numLayers[0],numLayers[1],numLayers[2]);
       }
 
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyBandagedPyraminx.java b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedPyraminx.java
new file mode 100644
index 00000000..9fe7f45c
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyBandagedPyraminx.java
@@ -0,0 +1,892 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.objectlib.objects;
+
+import static org.distorted.objectlib.touchcontrol.TouchControl.TC_CUBOID;
+
+import org.distorted.library.effect.EffectName;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.objectlib.helpers.FactoryBandagedCuboid;
+import org.distorted.objectlib.helpers.ObjectFaceShape;
+import org.distorted.objectlib.helpers.ObjectShape;
+import org.distorted.objectlib.helpers.ObjectSignature;
+import org.distorted.objectlib.helpers.ObjectVertexEffects;
+import org.distorted.objectlib.main.InitAssets;
+import org.distorted.objectlib.main.InitData;
+import org.distorted.objectlib.main.ObjectType;
+import org.distorted.objectlib.scrambling.ObjectScrambler;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class TwistyBandagedPyraminx extends TwistyBandagedAbstract
+{
+  public static final String OBJECT_NAME = "LOCAL_BANDAGED";
+
+  private static final int CUBIT_111 = 0;
+  private static final int CUBIT_211 = 1;
+  private static final int CUBIT_311 = 2;
+  private static final int CUBIT_221 = 3;
+  private static final int CUBIT_222 = 4;
+  private static final int CUBIT_OTH = 5;
+
+  private static final int[][] mDims = new int[][]
+        {
+         {1,1,1},  // has to be X>=Z>=Y so that all
+         {2,1,1},  // the faces are horizontal
+         {3,1,1},
+         {2,1,2},
+         {2,2,2},
+        };
+
+  // Fused Cube
+  public static final float[][] POS_1 = new float[][]
+        {
+          {-1.0f, -1.0f, +0.0f, -1.0f, -1.0f, +1.0f, -1.0f,  0.0f, +0.0f, -1.0f,  0.0f, +1.0f,
+            0.0f, -1.0f, +0.0f,  0.0f, -1.0f, +1.0f,  0.0f,  0.0f, +0.0f,  0.0f,  0.0f, +1.0f },
+          {-1.0f, +1.0f, +1.0f},
+          {-1.0f, +1.0f, +0.0f},
+          {-1.0f, +1.0f, -1.0f},
+          { 0.0f, +1.0f, +1.0f},
+          { 0.0f, +1.0f, +0.0f},
+          { 0.0f, +1.0f, -1.0f},
+          { 1.0f, +1.0f, +1.0f},
+          { 1.0f, +1.0f, +0.0f},
+          { 1.0f, +1.0f, -1.0f},
+          { 1.0f,  0.0f, +1.0f},
+          { 1.0f,  0.0f, +0.0f},
+          { 1.0f,  0.0f, -1.0f},
+          { 1.0f, -1.0f, +1.0f},
+          { 1.0f, -1.0f, +0.0f},
+          { 1.0f, -1.0f, -1.0f},
+          {-1.0f, -1.0f, -1.0f},
+          {-1.0f,  0.0f, -1.0f},
+          { 0.0f, -1.0f, -1.0f},
+          { 0.0f,  0.0f, -1.0f}
+        };
+
+  // 2-bar cube
+  public static final float[][] POS_2 = new float[][]
+        {
+          { 0.0f, +1.0f,  1.0f, 0.0f, +1.0f,  0.0f, 0.0f, +1.0f, -1.0f},
+          {-1.0f, -1.0f,  0.0f, 0.0f, -1.0f,  0.0f, 1.0f, -1.0f,  0.0f},
+          {-1.0f, +1.0f, +1.0f},
+          {-1.0f, +1.0f,  0.0f},
+          {-1.0f, +1.0f, -1.0f},
+          {-1.0f,  0.0f, +1.0f},
+          {-1.0f,  0.0f,  0.0f},
+          {-1.0f,  0.0f, -1.0f},
+          {-1.0f, -1.0f, +1.0f},
+          {-1.0f, -1.0f, -1.0f},
+          {+1.0f, +1.0f, +1.0f},
+          {+1.0f, +1.0f,  0.0f},
+          {+1.0f, +1.0f, -1.0f},
+          {+1.0f,  0.0f, +1.0f},
+          {+1.0f,  0.0f,  0.0f},
+          {+1.0f,  0.0f, -1.0f},
+          {+1.0f, -1.0f, +1.0f},
+          {+1.0f, -1.0f, -1.0f},
+          { 0.0f,  0.0f, +1.0f},
+          { 0.0f, -1.0f, +1.0f},
+          { 0.0f,  0.0f, -1.0f},
+          { 0.0f, -1.0f, -1.0f}
+        };
+
+  // 3-plate cube
+  public static final float[][] POS_3 = new float[][]
+        {
+          {-1.0f,  1.0f,  1.0f, -1.0f,  0.0f,  1.0f,  0.0f,  1.0f,  1.0f,  0.0f,  0.0f,  1.0f},
+          { 1.0f,  0.0f, -1.0f,  1.0f,  0.0f,  0.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  0.0f},
+          {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  0.0f,  0.0f, -1.0f, -1.0f,  0.0f, -1.0f,  0.0f},
+          { 1.0f,  1.0f,  1.0f},
+          { 1.0f,  0.0f,  1.0f},
+          { 1.0f, -1.0f,  1.0f},
+          {-1.0f, -1.0f,  1.0f},
+          { 0.0f, -1.0f,  1.0f},
+          { 1.0f, -1.0f,  0.0f},
+          { 1.0f, -1.0f, -1.0f},
+          {-1.0f,  1.0f, -1.0f},
+          {-1.0f,  1.0f,  0.0f},
+          { 0.0f,  1.0f, -1.0f},
+          { 0.0f,  1.0f,  0.0f},
+          {-1.0f,  0.0f, -1.0f},
+          {-1.0f,  0.0f,  0.0f},
+          { 0.0f,  0.0f, -1.0f}
+        };
+
+  // BiCube
+  public static final float[][] POS_4 = new float[][]
+        {
+          { 1.0f,  1.0f, -1.0f},
+          {-1.0f, -1.0f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f, -1.0f,  0.0f,  0.0f,  0.0f,  0.0f},
+          {-1.0f,  1.0f, -1.0f,  0.0f,  1.0f, -1.0f},
+          {-1.0f,  0.0f, -1.0f,  0.0f,  0.0f, -1.0f},
+          {-1.0f, -1.0f, -1.0f,  0.0f, -1.0f, -1.0f},
+          {-1.0f,  1.0f,  0.0f, -1.0f,  1.0f,  1.0f},
+          { 0.0f,  1.0f,  0.0f,  0.0f,  1.0f,  1.0f},
+          { 1.0f,  1.0f,  0.0f,  1.0f,  1.0f,  1.0f},
+          {-1.0f, -1.0f,  1.0f, -1.0f,  0.0f,  1.0f},
+          { 0.0f, -1.0f,  1.0f,  0.0f,  0.0f,  1.0f},
+          { 1.0f, -1.0f,  1.0f,  1.0f,  0.0f,  1.0f},
+          { 1.0f, -1.0f,  0.0f,  1.0f,  0.0f,  0.0f},
+          { 1.0f, -1.0f, -1.0f,  1.0f,  0.0f, -1.0f}
+        };
+
+  // AI Cube
+  public static final float[][] POS_5 = new float[][]
+        {
+          {-1.5f,-1.5f, 1.5f, -0.5f,-1.5f, 1.5f, -1.5f,-0.5f, 1.5f, -0.5f,-0.5f, 1.5f,
+           -1.5f,-1.5f, 0.5f, -0.5f,-1.5f, 0.5f, -1.5f,-0.5f, 0.5f, -0.5f,-0.5f, 0.5f, },
+          { 1.5f, 1.5f, 1.5f,  0.5f, 1.5f, 1.5f,  1.5f, 0.5f, 1.5f,  0.5f, 0.5f, 1.5f,
+            1.5f, 1.5f, 0.5f,  0.5f, 1.5f, 0.5f,  1.5f, 0.5f, 0.5f,  0.5f, 0.5f, 0.5f, },
+          {-1.5f, 1.5f,-1.5f, -0.5f, 1.5f,-1.5f, -1.5f, 0.5f,-1.5f, -0.5f, 0.5f,-1.5f,
+           -1.5f, 1.5f,-0.5f, -0.5f, 1.5f,-0.5f, -1.5f, 0.5f,-0.5f, -0.5f, 0.5f,-0.5f, },
+          { 1.5f,-1.5f,-1.5f,  0.5f,-1.5f,-1.5f,  1.5f,-0.5f,-1.5f,  0.5f,-0.5f,-1.5f,
+            1.5f,-1.5f,-0.5f,  0.5f,-1.5f,-0.5f,  1.5f,-0.5f,-0.5f,  0.5f,-0.5f,-0.5f, },
+
+          {-1.5f, 1.5f, 1.5f },
+          {-1.5f, 1.5f, 0.5f },
+          {-1.5f, 0.5f, 1.5f },
+          {-1.5f, 0.5f, 0.5f },
+          {-0.5f, 1.5f, 1.5f },
+          {-0.5f, 1.5f, 0.5f },
+          {-0.5f, 0.5f, 1.5f },
+
+          { 1.5f,-1.5f, 1.5f },
+          { 1.5f,-1.5f, 0.5f },
+          { 1.5f,-0.5f, 1.5f },
+          { 1.5f,-0.5f, 0.5f },
+          { 0.5f,-1.5f, 1.5f },
+          { 0.5f,-1.5f, 0.5f },
+          { 0.5f,-0.5f, 1.5f },
+
+          { 1.5f, 1.5f,-1.5f },
+          { 1.5f, 1.5f,-0.5f },
+          { 1.5f, 0.5f,-1.5f },
+          { 1.5f, 0.5f,-0.5f },
+          { 0.5f, 1.5f,-1.5f },
+          { 0.5f, 1.5f,-0.5f },
+          { 0.5f, 0.5f,-1.5f },
+
+          {-1.5f,-1.5f,-1.5f },
+          {-1.5f,-1.5f,-0.5f },
+          {-1.5f,-0.5f,-1.5f },
+          {-1.5f,-0.5f,-0.5f },
+          {-0.5f,-1.5f,-1.5f },
+          {-0.5f,-1.5f,-0.5f },
+          {-0.5f,-0.5f,-1.5f },
+        };
+
+  // Burr Cube
+  public static final float[][] POS_6 = new float[][]
+        {
+          {-1.5f, 1.5f, 1.5f,-0.5f, 1.5f, 1.5f},
+          { 1.5f, 1.5f, 1.5f, 0.5f, 1.5f, 1.5f},
+          {-0.5f, 0.5f, 1.5f, 0.5f, 0.5f, 1.5f},
+          {-1.5f,-0.5f, 1.5f,-0.5f,-0.5f, 1.5f},
+          { 1.5f,-0.5f, 1.5f, 0.5f,-0.5f, 1.5f},
+          {-0.5f,-1.5f, 1.5f, 0.5f,-1.5f, 1.5f},
+
+          {-1.5f, 1.5f,-1.5f,-0.5f, 1.5f,-1.5f},
+          { 1.5f, 1.5f,-1.5f, 0.5f, 1.5f,-1.5f},
+          {-0.5f, 0.5f,-1.5f, 0.5f, 0.5f,-1.5f},
+          {-1.5f,-0.5f,-1.5f,-0.5f,-0.5f,-1.5f},
+          { 1.5f,-0.5f,-1.5f, 0.5f,-0.5f,-1.5f},
+          {-0.5f,-1.5f,-1.5f, 0.5f,-1.5f,-1.5f},
+
+          { 1.5f, 1.5f, 0.5f, 1.5f, 1.5f,-0.5f},
+          { 1.5f, 0.5f, 1.5f, 1.5f, 0.5f, 0.5f},
+          { 1.5f, 0.5f,-1.5f, 1.5f, 0.5f,-0.5f},
+          { 1.5f,-0.5f, 0.5f, 1.5f,-0.5f,-0.5f},
+          { 1.5f,-1.5f, 1.5f, 1.5f,-1.5f, 0.5f},
+          { 1.5f,-1.5f,-1.5f, 1.5f,-1.5f,-0.5f},
+
+          {-1.5f, 1.5f, 0.5f,-1.5f, 1.5f,-0.5f},
+          {-1.5f, 0.5f, 1.5f,-1.5f, 0.5f, 0.5f},
+          {-1.5f, 0.5f,-1.5f,-1.5f, 0.5f,-0.5f},
+          {-1.5f,-0.5f, 0.5f,-1.5f,-0.5f,-0.5f},
+          {-1.5f,-1.5f, 1.5f,-1.5f,-1.5f, 0.5f},
+          {-1.5f,-1.5f,-1.5f,-1.5f,-1.5f,-0.5f},
+
+          {-0.5f, 1.5f,-0.5f, 0.5f, 1.5f,-0.5f},
+          {-0.5f, 1.5f, 0.5f, 0.5f, 1.5f, 0.5f},
+
+          { 0.5f,-1.5f, 0.5f, 0.5f,-1.5f,-0.5f},
+          {-0.5f,-1.5f, 0.5f,-0.5f,-1.5f,-0.5f},
+        };
+
+  private int[][] mEdges;
+  private int[] mCubitVariantMap;
+  private int[] mTypeVariantMap;
+  private Static4D[] mInitQuats;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public TwistyBandagedPyraminx(int meshState, int iconMode, Static4D quat, Static3D move, float scale, InitData data, InitAssets asset)
+    {
+    super(meshState, iconMode, (data.getNumLayers()[0]+data.getNumLayers()[1]+data.getNumLayers()[2])/3.0f, quat, move, scale, data, asset);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Computing scramble states of many a bandaged cubes takes way too long time and too much space.
+// Return null here and turn to construction of scramble tables just-in-time (scrambleType=2)
+// Unless this is AI Cube :)
+
+  public int[][] getScrambleEdges()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+
+    if( mPosition==POS_5 )
+      {
+      if( mEdges==null )
+        {
+        mEdges = new int[][]
+          {
+            { 36, 1,37, 2,38, 3,39, 4,40, 5,41, 6 },  // 0: beg
+            {  6, 7, 7, 7, 8, 7, 9, 7,10, 7,11, 7 },  // 1: R before
+            {  0, 8, 1, 8, 2, 8, 3, 8, 4, 8, 5, 8 },  // 2: L before
+            { 12, 9,13, 9,14, 9,15, 9,16, 9,17, 9 },  // 3: D before
+            { 18,10,19,10,20,10,21,10,22,10,23,10 },  // 4: U before
+            { 24,11,25,11,26,11,27,11,28,11,29,11 },  // 5: B before
+            { 30,12,31,12,32,12,33,12,34,12,35,12 },  // 6: F before
+            { 42, 3,43, 4,44, 5,45, 6 }, // 7: R after
+            { 46, 3,47, 4,48, 5,49, 6 }, // 8: L after
+            { 50, 1,51, 2,52, 5,53, 6 }, // 9: D after
+            { 54, 1,55, 2,56, 5,57, 6 }, //10: U after
+            { 58, 1,59, 2,60, 3,61, 4 }, //11: B after
+            { 62, 1,63, 2,64, 3,65, 4 }, //12: F after
+          };
+        }
+
+      return mEdges;
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// AI Cube is scrambled using algorithms
+
+  @Override
+  public int[][] getScrambleAlgorithms()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+
+    if( mPosition==POS_5 )
+      {
+      return new int[][]
+          {
+              { 0,0,-1 }, { 0,0, 1 }, { 0,0, 2 }, { 0,1,-1 }, { 0,1, 1 }, { 0,1, 2 },  //  0-5 : L
+              { 0,2,-1 }, { 0,2, 1 }, { 0,2, 2 }, { 0,3,-1 }, { 0,3, 1 }, { 0,3, 2 },  //  6-11: R
+              { 1,0,-1 }, { 1,0, 1 }, { 1,0, 2 }, { 1,1,-1 }, { 1,1, 1 }, { 1,1, 2 },  // 12-17: D
+              { 1,2,-1 }, { 1,2, 1 }, { 1,2, 2 }, { 1,3,-1 }, { 1,3, 1 }, { 1,3, 2 },  // 18-23: U
+              { 2,0,-1 }, { 2,0, 1 }, { 2,0, 2 }, { 2,1,-1 }, { 2,1, 1 }, { 2,1, 2 },  // 24-29: B
+              { 2,2,-1 }, { 2,2, 1 }, { 2,2, 2 }, { 2,3,-1 }, { 2,3, 1 }, { 2,3, 2 },  // 30-35: F
+
+              { 1,3,1, 2,0,2 },  // 36: BEG->R
+              { 1,0,1, 2,0,2 },  // 37: BEG->L
+              { 2,3,1, 0,3,2 },  // 38: BEG->D
+              { 2,0,1, 0,3,2 },  // 39: BEG->U
+              { 0,3,1, 1,3,2 },  // 40: BEG->B
+              { 0,0,1, 1,3,2 },  // 41: BEG->F
+
+              { 1,0,-1, 0,3,2, 2,0,-1 }, // 42: R->D
+              { 1,3, 1, 0,3,2, 2,3, 1 }, // 43: R->U
+              { 2,0, 1, 0,3,2, 1,0, 1 }, // 44: R->B
+              { 2,3,-1, 0,3,2, 1,3,-1 }, // 45: R->F
+
+              { 1,0,-1, 0,0,2, 2,3, 1 }, // 46: L->D
+              { 1,3, 1, 0,0,2, 2,0,-1 }, // 47: L->U
+              { 2,0, 1, 0,0,2, 1,3,-1 }, // 48: L->B
+              { 2,3,-1, 0,0,2, 1,0, 1 }, // 49: L->F
+
+              { 0,3,-1, 1,0,2, 2,0, 1 }, // 50: D->R
+              { 0,0, 1, 1,0,2, 2,3,-1 }, // 51: D->L
+              { 2,0,-1, 1,0,2, 0,3, 1 }, // 52: D->B
+              { 2,3, 1, 1,0,2, 0,0,-1 }, // 53: D->F
+
+              { 0,3,-1, 1,3,2, 2,3,-1 }, // 54: U->R
+              { 0,0, 1, 1,3,2, 2,0, 1 }, // 55: U->L
+              { 2,0,-1, 1,3,2, 0,0,-1 }, // 56: U->B
+              { 2,3, 1, 1,3,2, 0,3, 1 }, // 57: U->F
+
+              { 0,3, 1, 2,0,2, 1,0,-1 }, // 58: B->R
+              { 0,0,-1, 2,0,2, 1,3, 1 }, // 59: B->L
+              { 1,0, 1, 2,0,2, 0,3,-1 }, // 60: B->D
+              { 1,3,-1, 2,0,2, 0,0, 1 }, // 61: B->U
+
+              { 0,3, 1, 2,3,2, 1,3, 1 }, // 62: F->R
+              { 0,0,-1, 2,3,2, 1,0,-1 }, // 63: F->L
+              { 1,0, 1, 2,3,2, 0,0, 1 }, // 64: F->D
+              { 1,3,-1, 2,3,2, 0,3,-1 }, // 65: F->U
+          };
+      }
+    else
+      {
+      return super.getScrambleAlgorithms();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// AI Cube is scrambled using algorithms
+
+  @Override
+  public int getScrambleType()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+
+    return mPosition==POS_5 ? ObjectScrambler.SCRAMBLING_ALGORITHMS : ObjectScrambler.SCRAMBLING_BANDAGED;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getType(float[] pos)
+    {
+    switch(pos.length)
+      {
+      case  3: return CUBIT_111;
+      case  6: return CUBIT_211;
+      case  9: boolean x1 = (pos[0]==pos[3] && pos[0]==pos[6]);
+               boolean y1 = (pos[1]==pos[4] && pos[1]==pos[7]);
+               boolean z1 = (pos[2]==pos[5] && pos[2]==pos[8]);
+               return ( (x1&&y1) || (x1&&z1) || (y1&&z1) ) ? CUBIT_311 : CUBIT_OTH;
+      case 12: float x = (pos[0]+pos[3]+pos[6]+pos[ 9])/4;
+               float y = (pos[1]+pos[4]+pos[7]+pos[10])/4;
+               float z = (pos[2]+pos[5]+pos[8]+pos[11])/4;
+               float d1 = (pos[0]-x)*(pos[0]-x) + (pos[ 1]-y)*(pos[ 1]-y) + (pos[ 2]-z)*(pos[ 2]-z);
+               float d2 = (pos[3]-x)*(pos[3]-x) + (pos[ 4]-y)*(pos[ 4]-y) + (pos[ 5]-z)*(pos[ 5]-z);
+               float d3 = (pos[6]-x)*(pos[6]-x) + (pos[ 7]-y)*(pos[ 7]-y) + (pos[ 8]-z)*(pos[ 8]-z);
+               float d4 = (pos[9]-x)*(pos[9]-x) + (pos[10]-y)*(pos[10]-y) + (pos[11]-z)*(pos[11]-z);
+               return ( d1==0.5f && d2==0.5f && d3==0.5f && d4==0.5f ) ? CUBIT_221 : CUBIT_OTH;
+      case 24: float x3 = pos[0];
+               float y3 = pos[1];
+               float z3 = pos[2];
+               float x4=-10,y4=-10,z4=-10;
+               int i;
+
+               for(i=0; i<8; i++)
+                 {
+                 if( pos[3*i]!=x3 && pos[3*i+1]!=y3 && pos[3*i+2]!=z3 )
+                   {
+                   x4 = pos[3*i  ];
+                   y4 = pos[3*i+1];
+                   z4 = pos[3*i+2];
+                   break;
+                   }
+                 }
+               if( i==9 ) return CUBIT_OTH;
+
+               float dX = x4-x3;
+               float dY = y4-y3;
+               float dZ = z4-z3;
+
+               if( (dX==1.0f || dX==-1.0f) && (dY==1.0f || dY==-1.0f) && (dZ==1.0f || dZ==-1.0f) )
+                 {
+                 for(i=0; i<8; i++)
+                   {
+                   if( (pos[3*i  ]!=x3 && pos[3*i  ]!=x4) ||
+                       (pos[3*i+1]!=y3 && pos[3*i+1]!=y4) ||
+                       (pos[3*i+2]!=z3 && pos[3*i+2]!=z4)  ) return CUBIT_OTH;
+                   }
+
+                 return CUBIT_222;
+                 }
+
+      default: return CUBIT_OTH;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getQuatIndex(int cubit)
+    {
+    float[][] positions = getPositions();
+    int len = positions.length;
+
+    if( cubit>=0 && cubit<len )
+      {
+      float[] pos = positions[cubit];
+      int type = getType(pos);
+
+      switch(type)
+        {
+        case CUBIT_222:
+        case CUBIT_111: return 0;
+        case CUBIT_211:
+        case CUBIT_311: return (pos[1]==pos[4]) ? (pos[0]==pos[3] ? 2 : 0) : 3;
+        case CUBIT_221: if( pos[0]==pos[3] && pos[0]==pos[6] ) return 3;
+                        if( pos[1]==pos[4] && pos[1]==pos[7] ) return 0;
+                        if( pos[2]==pos[5] && pos[2]==pos[8] ) return 1;
+        }
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] getDim(int variant)
+    {
+    int type,numTypes = mDims.length;
+    for(type=0; type<numTypes; type++) if( mTypeVariantMap[type]==variant ) break;
+    return type<numTypes ? mDims[type] : null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void produceTmpShape(int variant)
+    {
+    float[][] positions = getPositions();
+    int cubit,numCubits = positions.length;
+
+    for(cubit=0; cubit<numCubits; cubit++)
+      {
+      if( mCubitVariantMap[cubit]==variant ) break;
+      }
+
+    if( cubit>=numCubits )
+      {
+      android.util.Log.e("D", "unknown variant: "+variant);
+      }
+    else
+      {
+      FactoryBandagedCuboid factory = FactoryBandagedCuboid.getInstance();
+      mTmpShapes[variant] = factory.createIrregularShape(variant,positions[cubit]);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public int getTouchControlType()
+    {
+    return TC_CUBOID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectShape getObjectShape(int variant)
+    {
+    if( getDim(variant)!=null )
+      {
+      int[][] indices =
+        {
+          {2,3,1,0},
+          {7,6,4,5},
+          {4,0,1,5},
+          {7,3,2,6},
+          {6,2,0,4},
+          {3,7,5,1},
+        };
+
+      return new ObjectShape( getVertices(variant), indices);
+      }
+
+    if( mTmpShapes==null ) mTmpShapes = new ObjectShape[mNumVariants];
+    if( mTmpShapes[variant]==null ) produceTmpShape(variant);
+    return mTmpShapes[variant];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectFaceShape getObjectFaceShape(int variant)
+    {
+    int[] numLayers = getNumLayers();
+    int size = (numLayers[0]+numLayers[1]+numLayers[2])/3;
+    int[] dim = getDim(variant);
+
+    if( dim!=null )
+      {
+      int X = dim[0];
+      int Y = dim[1];
+      int Z = dim[2];
+
+      float height     = isInIconMode() ? 0.001f : size<=5 ? 0.048f : 0.020f;
+      int[] bandIndices= { 0,0,1,1,2,2 };
+
+      int maxXY = Math.max(X,Y);
+      int maxXZ = Math.max(X,Z);
+      int maxYZ = Math.max(Y,Z);
+
+      int angle = 45;
+      float R = 0.25f;
+      float S = 0.50f;
+      float N = size<=4 ? 5 : size<=5 ? 4 : 3;
+
+      float[][] bands =
+        {
+          {height/maxYZ,angle,R,S,N,0,0},
+          {height/maxXZ,angle,R,S,N,0,0},
+          {height/maxXY,angle,R,S,N,0,0}
+        };
+
+      return new ObjectFaceShape(bands,bandIndices,null);
+      }
+
+    FactoryBandagedCuboid factory = FactoryBandagedCuboid.getInstance();
+    return factory.createIrregularFaceShape(variant, isInIconMode() );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectVertexEffects getVertexEffects(int variant)
+    {
+    int[] numLayers = getNumLayers();
+    int size = (numLayers[0]+numLayers[1]+numLayers[2])/3;
+    boolean round = (DistortedLibrary.fastCompilationTF() && size<=5 && !isInIconMode());
+
+    if( getDim(variant)!=null )
+      {
+      float[][] vertices = getVertices(variant);
+      float strength = -0.04f;
+
+      float[][] variables =
+        {
+          { 0, strength*vertices[0][0], strength*vertices[0][1], strength*vertices[0][2], 1  },
+          { 0, strength*vertices[1][0], strength*vertices[1][1], strength*vertices[1][2], 1  },
+          { 0, strength*vertices[2][0], strength*vertices[2][1], strength*vertices[2][2], 1  },
+          { 0, strength*vertices[3][0], strength*vertices[3][1], strength*vertices[3][2], 1  },
+          { 0, strength*vertices[4][0], strength*vertices[4][1], strength*vertices[4][2], 1  },
+          { 0, strength*vertices[5][0], strength*vertices[5][1], strength*vertices[5][2], 1  },
+          { 0, strength*vertices[6][0], strength*vertices[6][1], strength*vertices[6][2], 1  },
+          { 0, strength*vertices[7][0], strength*vertices[7][1], strength*vertices[7][2], 1  },
+        };
+
+      String name = EffectName.DEFORM.name();
+      float[] reg = {0,0,0,0.15f};
+
+      String[] names = {name,name,name,name,name,name,name,name};
+      float[][] regions = {reg,reg,reg,reg,reg,reg,reg,reg};
+      boolean[] uses = {round,round,round,round,round,round,round,round};
+
+      return new ObjectVertexEffects(names,variables,vertices,regions,uses);
+      }
+
+    FactoryBandagedCuboid factory = FactoryBandagedCuboid.getInstance();
+    return factory.createVertexEffects(variant,round);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public Static4D getCubitQuats(int cubit, int[] numLayers)
+    {
+    if( mInitQuats ==null )
+      {
+      mInitQuats = new Static4D[]
+        {
+        new Static4D(  0.0f,   0.0f,   0.0f,   1.0f),  // NULL
+        new Static4D( SQ2/2,   0.0f,   0.0f, -SQ2/2),  // X
+        new Static4D(  0.0f,  SQ2/2,   0.0f, -SQ2/2),  // Y
+        new Static4D(  0.0f,   0.0f,  SQ2/2, -SQ2/2),  // Z
+        new Static4D( -0.5f,  +0.5f,  -0.5f,  +0.5f),  // ZX
+        new Static4D( +0.5f,  +0.5f,  +0.5f,  -0.5f),  // YX
+        };
+      }
+
+    return mInitQuats[getQuatIndex(cubit)];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumCubitVariants(int[] numLayers)
+    {
+    if( mNumVariants==0 )
+      {
+      float[][] positions = getPositions();
+      boolean C111=false;
+      boolean C211=false;
+      boolean C311=false;
+      boolean C221=false;
+      boolean C222=false;
+
+      int numCubits = positions.length;
+      mCubitVariantMap = new int[numCubits];
+
+      int numTypes = mDims.length;
+      mTypeVariantMap = new int[numTypes];
+      for(int i=0; i<numTypes; i++) mTypeVariantMap[i] = -1;
+
+      for (int cubit=0; cubit<numCubits; cubit++)
+        {
+        int type = getType(positions[cubit]);
+
+        switch (type)
+          {
+          case CUBIT_111: if (!C111) { C111 = true; mTypeVariantMap[CUBIT_111]=mNumVariants++; }
+                          mCubitVariantMap[cubit]=mTypeVariantMap[CUBIT_111];
+                          break;
+          case CUBIT_211: if (!C211) { C211 = true; mTypeVariantMap[CUBIT_211]=mNumVariants++; }
+                          mCubitVariantMap[cubit]=mTypeVariantMap[CUBIT_211];
+                          break;
+          case CUBIT_311: if (!C311) { C311 = true; mTypeVariantMap[CUBIT_311]=mNumVariants++; }
+                          mCubitVariantMap[cubit]=mTypeVariantMap[CUBIT_311];
+                          break;
+          case CUBIT_221: if (!C221) { C221 = true; mTypeVariantMap[CUBIT_221]=mNumVariants++; }
+                          mCubitVariantMap[cubit]=mTypeVariantMap[CUBIT_221];
+                          break;
+          case CUBIT_222: if (!C222) { C222 = true; mTypeVariantMap[CUBIT_222]=mNumVariants++; }
+                          mCubitVariantMap[cubit]=mTypeVariantMap[CUBIT_222];
+                          break;
+          default       : mCubitVariantMap[cubit] = mNumVariants++;
+          }
+        }
+
+      FactoryBandagedCuboid factory = FactoryBandagedCuboid.getInstance();
+      factory.prepare(mNumVariants,numLayers[0],numLayers[1],numLayers[2]);
+      }
+
+    return mNumVariants;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getCubitVariant(int cubit, int[] numLayers)
+    {
+    return mCubitVariantMap[cubit];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[][] getVertices(int variant)
+    {
+    int[] dim = getDim(variant);
+
+    if( dim!=null )
+      {
+      int X = dim[0];
+      int Y = dim[1];
+      int Z = dim[2];
+
+      return new float[][]
+        {
+          { 0.5f*X, 0.5f*Y, 0.5f*Z},
+          { 0.5f*X, 0.5f*Y,-0.5f*Z},
+          { 0.5f*X,-0.5f*Y, 0.5f*Z},
+          { 0.5f*X,-0.5f*Y,-0.5f*Z},
+          {-0.5f*X, 0.5f*Y, 0.5f*Z},
+          {-0.5f*X, 0.5f*Y,-0.5f*Z},
+          {-0.5f*X,-0.5f*Y, 0.5f*Z},
+          {-0.5f*X,-0.5f*Y,-0.5f*Z}
+        };
+      }
+
+    if( mTmpShapes==null ) mTmpShapes = new ObjectShape[mNumVariants];
+    if( mTmpShapes[variant]==null ) produceTmpShape(variant);
+    return mTmpShapes[variant].getVertices();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public float[] getDist3D(int[] numLayers)
+    {
+    float x = numLayers[0];
+    float y = numLayers[1];
+    float z = numLayers[2];
+    float a = (x+y+z)/1.5f;
+
+    return new float[] {x/a,x/a,y/a,y/a,z/a,z/a};
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float getStickerRadius()
+    {
+    return 0.10f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float getStickerStroke()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+    boolean icon = isInIconMode();
+
+    if( mPosition==POS_5 ) return icon ? 0.30f : 0.11f;
+    if( mPosition==POS_6 ) return icon ? 0.30f : 0.10f;
+
+    return icon ? 0.16f : 0.08f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getShortName()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+
+    if( mPosition==POS_1 ) return ObjectType.BAN1_3.name();
+    if( mPosition==POS_2 ) return ObjectType.BAN2_3.name();
+    if( mPosition==POS_3 ) return ObjectType.BAN3_3.name();
+    if( mPosition==POS_4 ) return ObjectType.BAN4_3.name();
+    if( mPosition==POS_5 ) return ObjectType.BAN5_4.name();
+    if( mPosition==POS_6 ) return ObjectType.BAN6_4.name();
+
+    if( mSignature==null ) mSignature = getSignature();
+    int[] numLayers = getNumLayers();
+    int number = 100*numLayers[0]+10*numLayers[1]+numLayers[2];
+
+    return number+"_"+mSignature.getString();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectSignature getSignature()
+    {
+    if( mSignature==null )
+      {
+      int[] numLayers = getNumLayers();
+      mSignature = new ObjectSignature(numLayers[0],numLayers[1],numLayers[2],mPosition);
+      }
+    return mSignature;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getObjectName()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+
+    if( mPosition==POS_1 ) return "Fused Cube";
+    if( mPosition==POS_2 ) return "2Bar Cube";
+    if( mPosition==POS_3 ) return "Bandaged Cube C";
+    if( mPosition==POS_4 ) return "BiCube";
+    if( mPosition==POS_5 ) return "AI Cube";
+    if( mPosition==POS_6 ) return "Burr Cube";
+
+    return OBJECT_NAME;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getInventor()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+
+    if( mPosition==POS_1 ) return "Ting Huang";
+    if( mPosition==POS_2 ) return "Unknown";
+    if( mPosition==POS_3 ) return "Andreas Nortmann";
+    if( mPosition==POS_4 ) return "Uwe Meffert";
+    if( mPosition==POS_5 ) return "David Adams";
+    if( mPosition==POS_6 ) return "Unknown";
+
+    return "??";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getYearOfInvention()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+
+    if( mPosition==POS_1 ) return 2011;
+    if( mPosition==POS_2 ) return 0;
+    if( mPosition==POS_3 ) return 2005;
+    if( mPosition==POS_4 ) return 1999;
+    if( mPosition==POS_5 ) return 2004;
+    if( mPosition==POS_6 ) return 2002;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getComplexity()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+
+    if( mPosition==POS_1 ) return 1;
+    if( mPosition==POS_2 ) return 2;
+    if( mPosition==POS_3 ) return 2;
+    if( mPosition==POS_4 ) return 3;
+    if( mPosition==POS_5 ) return 4;
+    if( mPosition==POS_6 ) return 3;
+
+    return 4;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String[][] getTutorials()
+    {
+    if( mPosition==null ) mPosition = getInitData().getPos();
+
+    if( mPosition==POS_1 )
+      {
+      return new String[][]{
+                            {"gb","F_iJk_IvpVo","Bandaged Cube","CanChrisSolve"},
+                            {"es","_lTgw5aEFOg","Tutorial 3x3 Fuse Cube","QBAndo"},
+                            {"ru","raYDwFEXIq4","Как собрать Fused Cube","Алексей Ярыгин"},
+                            {"fr","9Cfi4rhKzIw","Tutoriel: résolution du Fused Cube","Skieur Cubb"},
+                            {"pl","0PcUoGxQa6s","Bandaged 3x3 v.A cube","MrUK"},
+                            {"kr","1RePOLrzJNE","밴디지 타입 A 해법","듀나메스 큐브 해법연구소"},
+                            {"vn","vg4J0U0n1oA","Tutorial N.1 - Bandaged VA","Duy Thích Rubik"},
+                           };
+      }
+    if( mPosition==POS_2 )
+      {
+      return new String[][]{
+                            {"ru","lS_EK0PMWI8","Как собрать 2-bar Cube","Алексей Ярыгин"},
+                            {"pl","tX8ubTLh6p8","Bandaged 3x3 (Two bar)","MrUK"},
+                            {"kr","NE6XuC1r8xw","밴디지 큐브","Denzel Washington"},
+                           };
+      }
+    if( mPosition==POS_3 )
+      {
+      return new String[][]{
+                            {"gb","7UiCVGygUT4","Bandage Cube C Tutorial","PolyakB"},
+                            {"ru","gXenRA92Wdc","Как собрать Bandaged 3x3 Type C","YG Cuber"},
+                            {"pl","sKfdFLm79Zs","Bandaged 3x3 v.C cube","MrUK"},
+                            {"kr","BcCFgeFy6Ec","밴디지 타입 C 해법","듀나메스 큐브 해법연구소"},
+                            {"vn","9674LLkPSog","Tutorial N.2 - Bandaged VC","Duy Thích Rubik"},
+                           };
+      }
+    if( mPosition==POS_4 )
+      {
+      return new String[][]{
+                            {"gb","AnpdIKICBpM","Trying to Solve a Bandaged Cube","RedKB"},
+                            {"es","cUyo5fycrvI","Tutorial Bandaged Cube en español","Rafa Garcia Benacazon"},
+                            {"ru","-MTzeEJptsg","Как собрать bandaged Cube B","стратегия знаний"},
+                            {"fr","3rsfIJ3roT0","Tutoriel: résolution du Bicube","Skieur Cubb"},
+                            {"de","sqWVRwkXX9w","Bandaged Cube - Tutorial","GerCubing"},
+                            {"pl","XcHzTvVR6Po","Bandaged 3x3 v.B cube","MrUK"},
+                            {"kr","1gsoijF_5q0","BiCube Tutorial (해법)","듀나메스 큐브 해법연구소"},
+                            {"vn","ZCJDaF4jEbc","Tutorial N.3 - BiCube","Duy Thích Rubik"},
+                           };
+      }
+    if( mPosition==POS_5 )
+      {
+      return new String[][]{
+                            {"gb","b62HPjlUmYQ","4x4x4 AI Bandaged Cube 1/2","Superantoniovivaldi"},
+                            {"gb","DQeZ0iDqt4s","4x4x4 AI Bandaged Cube 2/2","Superantoniovivaldi"},
+                            {"es","bZRV8aXyIY4","Tutorial AI Cube 4x4","QBAndo"},
+                            {"ru","2NIEHu6jE9o","Как собрать AI Cube","Rodion Strizhakov"},
+                            {"fr","iP9sSg1q0K0","Résolution de l'AI Cube","asthalis"},
+                            {"pl","y6MNyoHgY8A","All Bandage 4x4 cube TUTORIAL","MrUK"},
+                            {"br","yGnQ4ObiPRY","Como Resolver o 4x4 Bandaged 1/4","Rafael Cinoto"},
+                            {"br","XJUtQrwrCmA","Como Resolver o 4x4 Bandaged 2/4","Rafael Cinoto"},
+                            {"br","Wrdec2WNBSY","Como Resolver o 4x4 Bandaged 3/4","Rafael Cinoto"},
+                            {"br","mWvYD53fqHI","Como Resolver o 4x4 Bandaged 4/4","Rafael Cinoto"},
+                            {"kr","MvaFtrU4O_k","AI 밴디지 큐브 해법","듀나메스 큐브 해법연구소"},
+                            {"vn","aAP1E567ADc","Tutorial N.78 - AI Cube","Duy Thích Rubik"},
+                           };
+      }
+    if( mPosition==POS_6 )
+      {
+      return new String[][]{
+                            {"gb","_FnNzFo0ITM","Burr Cube Tutorial 1/2","Superantoniovivaldi"},
+                            {"gb","NOf-UcNh4sI","Burr Cube Tutorial 2/2","Superantoniovivaldi"},
+                            {"es","xR2TLKQmym8","Cómo Resuelvo Wall Cube","Robert Cubes"},
+                            {"vn","cbDUtkIJjfk","Tutorial N.96 - Burr Cube","Duy Thích Rubik"},
+                           };
+      }
+    return null;
+    }
+}
