commit 5d7bb479c6d7c0727a58837a0325a86f4815a632
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri May 26 18:35:38 2023 +0200

    Tins Cube.

diff --git a/src/main/java/org/distorted/objectlib/main/ObjectSignatures.java b/src/main/java/org/distorted/objectlib/main/ObjectSignatures.java
index 9199db4c..ec59f500 100644
--- a/src/main/java/org/distorted/objectlib/main/ObjectSignatures.java
+++ b/src/main/java/org/distorted/objectlib/main/ObjectSignatures.java
@@ -86,4 +86,5 @@ public class ObjectSignatures
   public static final int ICOS_2 = ObjectType.ICOS_2.ordinal();
   public static final int ICOS_3 = ObjectType.ICOS_3.ordinal();
   public static final int COIN_3 = ObjectType.COIN_3.ordinal();
+  public static final int TINS_5 = ObjectType.TINS_5.ordinal();
   }
diff --git a/src/main/java/org/distorted/objectlib/main/ObjectType.java b/src/main/java/org/distorted/objectlib/main/ObjectType.java
index d4372955..e560ef35 100644
--- a/src/main/java/org/distorted/objectlib/main/ObjectType.java
+++ b/src/main/java/org/distorted/objectlib/main/ObjectType.java
@@ -22,6 +22,9 @@ import org.distorted.objectlib.objects.*;
 
 public enum ObjectType
   {
+  TINS_5 ( TwistyTins.class           , 28, R.drawable.coin_3, true,    60, new InitData(new int[] {5,5,5,5})),
+
+
   CUBE_2 ( TwistyCuboid.class         , 12, R.drawable.cube_2, true,     0, new InitData(new int[] {2,2,2})),
   CUBE_3 ( TwistyCuboid.class         , 17, R.drawable.cube_3, true,     0, new InitData(new int[] {3,3,3})),
   CUBE_4 ( TwistyCuboid.class         , 24, R.drawable.cube_4, true,    50, new InitData(new int[] {4,4,4})),
diff --git a/src/main/java/org/distorted/objectlib/objects/TwistyTins.java b/src/main/java/org/distorted/objectlib/objects/TwistyTins.java
new file mode 100644
index 00000000..b0c7a75d
--- /dev/null
+++ b/src/main/java/org/distorted/objectlib/objects/TwistyTins.java
@@ -0,0 +1,515 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.objects;
+
+import static org.distorted.objectlib.touchcontrol.TouchControl.TC_HEXAHEDRON;
+import static org.distorted.objectlib.touchcontrol.TouchControl.TYPE_SPLIT_CORNER;
+
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.objectlib.helpers.FactoryCubit;
+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.ObjectSignatures;
+import org.distorted.objectlib.main.ObjectType;
+import org.distorted.objectlib.scrambling.ScrambleEdgeGenerator;
+import org.distorted.objectlib.shape.ShapeHexahedron;
+import org.distorted.objectlib.touchcontrol.TouchControlHexahedron;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class TwistyTins extends ShapeHexahedron
+{
+  static final Static3D[] ROT_AXIS = new Static3D[]
+         {
+           new Static3D( SQ3/3, SQ3/3, SQ3/3),
+           new Static3D( SQ3/3, SQ3/3,-SQ3/3),
+           new Static3D( SQ3/3,-SQ3/3, SQ3/3),
+           new Static3D( SQ3/3,-SQ3/3,-SQ3/3)
+         };
+
+  private int[][] mEdges;
+  private int[][] mBasicAngle;
+  private float[][] mCuts;
+  private float[][] mPosition;
+  private int[] mQuatIndex;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public TwistyTins(int meshState, int iconMode, Static4D quat, Static3D move, float scale, InitData data, InitAssets asset)
+    {
+    super(meshState, iconMode, 4, quat, move, scale, data, asset);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] getScrambleEdges()
+    {
+    if( mEdges==null ) mEdges = ScrambleEdgeGenerator.getScrambleEdgesSingle(mBasicAngle);
+    return mEdges;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[][] getCuts(int[] numLayers)
+    {
+    if( mCuts==null )
+      {
+      float C1 = -4*SQ3/3;
+      float C2 = -2*SQ3/3;
+      float C3 =  2*SQ3/3;
+      float C4 =    SQ3+0.01f;
+
+      float[] cut1 = new float[] { C1, C2, C3, C4 };
+      float[] cut2 = new float[] {-C4,-C3,-C2,-C1 };
+
+      mCuts = new float[][] { cut1,cut2,cut2,cut1 };
+      }
+
+    return mCuts;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean[][] getLayerRotatable(int[] numLayers)
+    {
+    boolean[] tmp = new boolean[] {true,true,false,true,true};
+    return new boolean[][] { tmp,tmp,tmp,tmp };
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getTouchControlType()
+    {
+    return TC_HEXAHEDRON;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getTouchControlSplit()
+    {
+    return TYPE_SPLIT_CORNER;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][][] getEnabled()
+    {
+    return new int[][][]
+      {
+          {{0,1},{3,1},{2,3},{0,2}},
+          {{2,3},{3,1},{0,1},{0,2}},
+          {{1,2},{0,1},{0,3},{2,3}},
+          {{1,2},{2,3},{0,3},{0,1}},
+          {{0,3},{0,2},{1,2},{1,3}},
+          {{1,2},{0,2},{0,3},{1,3}},
+      };
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[] getDist3D(int[] numLayers)
+    {
+    return TouchControlHexahedron.D3D;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public Static3D[] getFaceAxis()
+    {
+    return TouchControlHexahedron.FACE_AXIS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// corner cubes, corner 'dino' , edges, centers.
+
+  public float[][] getCubitPositions(int[] numLayers)
+    {
+    if( mPosition==null )
+      {
+      float C1 = 1.5f;
+      float C2 = 1.0f;
+      float C3 = 2.0f;
+      float C4 = 0.5f;
+      float C5 = 0.25f;
+      float C6 = 0.75f;
+
+      mPosition = new float[][]
+         {
+             { C1, C1, C1},
+             {-C1, C1,-C1},
+             { C1,-C1,-C1},
+             {-C1,-C1, C1},
+
+             {-C2, C3, C3},
+             {-C3, C2, C3},
+             {-C3, C3, C2},
+             { C2, C3,-C3},
+             { C3, C2,-C3},
+             { C3, C3,-C2},
+             { C2,-C3, C3},
+             { C3,-C2, C3},
+             { C3,-C3, C2},
+             {-C2,-C3,-C3},
+             {-C3,-C2,-C3},
+             {-C3,-C3,-C2},
+
+             { C4, C3, C3},
+             { C3, C4, C3},
+             {-C4,-C3, C3},
+             {-C3,-C4, C3},
+             { C3, C3, C4},
+             { C3,-C3,-C4},
+             {-C3,-C3, C4},
+             {-C3, C3,-C4},
+             {-C4, C3,-C3},
+             { C3,-C4,-C3},
+             { C4,-C3,-C3},
+             {-C3, C4,-C3},
+
+             {-C5, C6, C3}, // F
+             { C6,-C5, C3},
+             { C5,-C6, C3},
+             {-C6, C5, C3},
+             { C5, C6,-C3}, // B
+             {-C6,-C5,-C3},
+             {-C5,-C6,-C3},
+             { C6, C5,-C3},
+             { C3, C6,-C5}, // R
+             { C3, C5,-C6},
+             { C3,-C6, C5},
+             { C3,-C5, C6},
+             {-C3, C6, C5}, // L
+             {-C3, C5, C6},
+             {-C3,-C6,-C5},
+             {-C3,-C5,-C6},
+             { C5, C3,-C6}, // T
+             { C6, C3,-C5},
+             {-C5, C3, C6},
+             {-C6, C3, C5},
+             { C5,-C3, C6}, // D
+             { C6,-C3, C5},
+             {-C5,-C3,-C6},
+             {-C6,-C3,-C5},
+         };
+      }
+
+    return mPosition;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public Static4D getCubitQuats(int cubit, int[] numLayers)
+    {
+    if( mQuatIndex==null ) mQuatIndex = new int[] { 0, 3, 4, 6,
+                                                    0, 8, 7,11, 5, 1,10, 2, 4, 9, 3, 6,
+                                                    0, 2,10, 8, 1, 4, 6, 7,11, 5, 9, 3,
+                                                    0, 0, 0, 0, 9, 9, 9, 9, 2, 2, 2, 2, 3, 3, 3, 3, 1, 1, 1, 1, 4, 4, 4, 4
+                                                  };
+    return mObjectQuats[mQuatIndex[cubit]];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[][] getVertices(int variant)
+    {
+    if( variant==0 )
+      {
+      return new float[][]
+          {
+             { 0.0f, 0.0f, 0.0f },
+             {-0.5f, 0.5f, 0.5f },
+             {-0.5f,-0.5f, 0.5f },
+             { 0.5f, 0.5f, 0.5f },
+             { 0.5f,-0.5f, 0.5f },
+             { 0.5f, 0.5f,-0.5f },
+             { 0.5f,-0.5f,-0.5f },
+             {-0.5f, 0.5f,-0.5f },
+          };
+      }
+    else if( variant==1 )
+      {
+      return new float[][]
+          {
+             {-1.0f, 0.0f, 0.0f},
+             { 1.0f, 0.0f, 0.0f},
+             { 0.0f,-1.0f, 0.0f},
+             { 0.0f, 0.0f,-1.0f}
+          };
+      }
+    else if( variant==2 )
+      {
+      return new float[][]
+          {
+             {-0.5f, 0.0f, 0.0f},
+             { 0.5f, 0.0f, 0.0f},
+             { 0.5f,-1.0f, 0.0f},
+             { 0.0f,-1.5f, 0.0f},
+             {-1.0f,-0.5f, 0.0f},
+             {-1.0f, 0.0f,-0.5f},
+             { 0.0f, 0.0f,-1.5f},
+             { 0.5f, 0.0f,-1.0f}
+          };
+      }
+    else
+      {
+      return new float[][]
+          {
+             {-0.25f, 0.75f, 0.0f},
+             { 0.75f,-0.25f, 0.0f},
+             { 0.25f,-0.75f, 0.0f},
+             {-0.75f, 0.25f, 0.0f},
+             { 0.00f, 0.00f,-1.0f},
+          };
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectShape getObjectShape(int variant)
+    {
+    if( variant==0 )
+      {
+      int[][] indices =
+          {
+             { 2,4,3,1 },
+             { 1,3,5,7 },
+             { 4,6,5,3 },
+             { 0,4,2 },
+             { 0,7,5 },
+             { 0,6,4 },
+             { 0,1,7 },
+             { 0,2,1 },
+             { 0,5,6 }
+          };
+
+      return new ObjectShape(getVertices(variant), indices);
+      }
+    else if( variant==1 )
+      {
+      int[][] indices =
+          {
+             {2,1,0},
+             {3,0,1},
+             {2,3,1},
+             {3,2,0}
+          };
+
+      return new ObjectShape(getVertices(variant), indices);
+      }
+    else if( variant==2 )
+      {
+      int[][] indices =
+          {
+             {4,3,2,1,0},
+             {0,1,7,6,5},
+             {1,2,7},
+             {2,3,6,7},
+             {3,4,5,6},
+             {0,4,5}
+          };
+
+      return new ObjectShape(getVertices(variant), indices);
+      }
+    else
+      {
+      int[][] indices =
+          {
+             {3,2,1,0},
+             {4,1,2},
+             {4,0,1},
+             {4,3,0},
+             {4,2,3}
+          };
+
+      return new ObjectShape(getVertices(variant), indices);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectFaceShape getObjectFaceShape(int variant)
+    {
+    if( variant==0 )
+      {
+      float h1 = isInIconMode() ? 0.001f : 0.06f;
+      float h2 = isInIconMode() ? 0.001f : 0.01f;
+      float[][] bands = { {h1,35,0.5f,0.7f,5,1,0}, {h2,35,0.2f,0.4f,5,1,0} };
+      int[] indices   = { 0,0,0,1,1,1,1,1,1 };
+      return new ObjectFaceShape(bands,indices,null);
+      }
+    else if( variant==1 )
+      {
+      float h1 = isInIconMode() ? 0.001f : 0.035f;
+      float h2 = isInIconMode() ? 0.001f : 0.010f;
+      float[][] bands  = { {h1,30,0.16f,0.8f,5,0,0}, {h2,30,0.16f,0.2f,5,0,0} };
+      int[] bandIndices= { 0,0,1,1 };
+      return new ObjectFaceShape(bands,bandIndices,null);
+      }
+    else if( variant==2 )
+      {
+      float h1 = isInIconMode() ? 0.001f : 0.035f;
+      float h2 = isInIconMode() ? 0.001f : 0.010f;
+      float[][] bands  = { {h1,30,0.16f,0.8f,5,1,0}, {h2,30,0.16f,0.2f,5,0,0} };
+      int[] bandIndices= { 0,0,1,1,1,1 };
+      return new ObjectFaceShape(bands,bandIndices,null);
+      }
+    else
+      {
+      float h1 = isInIconMode() ? 0.001f : 0.035f;
+      float h2 = isInIconMode() ? 0.001f : 0.010f;
+      float[][] bands  = { {h1,30,0.16f,0.8f,4,0,0}, {h2,30,0.16f,0.2f,3,0,0} };
+      int[] bandIndices= { 0,1,1,1,1 };
+      return new ObjectFaceShape(bands,bandIndices,null);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectVertexEffects getVertexEffects(int variant)
+    {
+    if( variant==0 )
+      {
+      float[][] corners = { {0.06f,0.12f} };
+      int[] indices     = { -1,0,-1,0,0,0,-1,-1 };
+      float[][] centers = { { 0.0f, 0.0f, 0.0f} };
+      return FactoryCubit.generateVertexEffect(getVertices(variant),corners,indices,centers,indices);
+      }
+    else if( variant==1 )
+      {
+      float[][] corners   = { {0.07f,0.40f}, {0.05f,0.30f} };
+      int[] cornerIndices = { 0,0,1,1 };
+      float[][] centers   = { {0.0f, -0.75f, -0.75f} };
+      int[] centerIndices = { 0,0,0,0 };
+      return FactoryCubit.generateVertexEffect(getVertices(variant),corners,cornerIndices,centers,centerIndices);
+      }
+    else if( variant==2 )
+      {
+      float[][] corners   = { {0.07f,0.20f}, {0.05f,0.15f} };
+      int[] cornerIndices = { 0,0,1,1,1,1,1,1 };
+      float[][] centers   = { {0.0f, -0.5f, -0.5f} };
+      int[] centerIndices = { 0,0,0,0,0,0,0,0 };
+      return FactoryCubit.generateVertexEffect(getVertices(variant),corners,cornerIndices,centers,centerIndices);
+      }
+    else
+      {
+      return null;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumCubitVariants(int[] numLayers)
+    {
+    return 4;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getCubitVariant(int cubit, int[] numLayers)
+    {
+    return cubit<4 ? 0: (cubit<16 ? 1: (cubit<28 ? 2:3));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float getStickerRadius()
+    {
+    return 0.09f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float getStickerStroke()
+    {
+    return isInIconMode() ? 0.20f : 0.09f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public float[][] getStickerAngles()
+    {
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public Static3D[] getRotationAxis()
+    {
+    return ROT_AXIS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] getBasicAngles()
+    {
+    if( mBasicAngle ==null )
+      {
+      int[] tmp = {3,3,3,3,3};
+      mBasicAngle = new int[][] { tmp,tmp,tmp,tmp };
+      }
+
+    return mBasicAngle;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getShortName()
+    {
+    return ObjectType.TINS_5.name();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectSignature getSignature()
+    {
+    return new ObjectSignature(ObjectSignatures.TINS_5);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getObjectName()
+    {
+    return "Tins Cube";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getInventor()
+    {
+    return "조현준";
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getYearOfInvention()
+    {
+    return 2021;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getComplexity()
+    {
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String[][] getTutorials()
+    {
+    return null;
+    }
+}
