1 |
e3169794
|
leszek
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
2 |
|
|
// Copyright 2023 Leszek Koltunski //
|
3 |
|
|
// //
|
4 |
|
|
// This file is part of Magic Cube. //
|
5 |
|
|
// //
|
6 |
|
|
// Magic Cube is proprietary software licensed under an EULA which you should have received //
|
7 |
|
|
// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html //
|
8 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
9 |
|
|
|
10 |
|
|
package org.distorted.objectlib.main;
|
11 |
|
|
|
12 |
|
|
import org.distorted.library.effect.VertexEffectRotate;
|
13 |
4a014840
|
leszek
|
import org.distorted.library.main.DistortedEffects;
|
14 |
e3169794
|
leszek
|
import org.distorted.library.message.EffectListener;
|
15 |
|
|
import org.distorted.library.type.Dynamic1D;
|
16 |
|
|
import org.distorted.library.type.Static1D;
|
17 |
|
|
import org.distorted.library.type.Static3D;
|
18 |
|
|
|
19 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
20 |
|
|
|
21 |
|
|
public class TwistyLayerRotations
|
22 |
|
|
{
|
23 |
|
|
private static final int STATE_NOTHING = 0;
|
24 |
|
|
private static final int STATE_ROTATE = 1;
|
25 |
|
|
private static final int STATE_FINISH = 2;
|
26 |
|
|
private static final int POST_ROTATION_MILLISEC = 500;
|
27 |
|
|
private static final Static3D CENTER = new Static3D(0,0,0);
|
28 |
|
|
|
29 |
|
|
private int mRotationState;
|
30 |
|
|
private int mRotRowBitmap;
|
31 |
|
|
private int mCurrentRotAxis;
|
32 |
|
|
|
33 |
|
|
private final int mMaxNumLayers;
|
34 |
|
|
private final Static3D mRotationAxis;
|
35 |
|
|
private final VertexEffectRotate mRotateEffect;
|
36 |
|
|
private final Dynamic1D mRotationAngle;
|
37 |
|
|
private final Static1D mRotationAngleStatic, mRotationAngleMiddle, mRotationAngleFinal;
|
38 |
|
|
private final TwistyObject mParent;
|
39 |
|
|
|
40 |
4a014840
|
leszek
|
// ghost angle
|
41 |
8c70541c
|
leszek
|
private final int mGhostAngle;
|
42 |
a0ef8a1d
|
leszek
|
private final Static3D[] mAxis;
|
43 |
93dc5a55
|
leszek
|
private final int[] mNumLayers;
|
44 |
63bfcdd1
|
leszek
|
private int mSolvedBitmap;
|
45 |
93dc5a55
|
leszek
|
private int mGhostEffectBitmap;
|
46 |
4a014840
|
leszek
|
private VertexEffectRotate mGhostEffect1;
|
47 |
5df97ecb
|
leszek
|
private boolean mCanGhost;
|
48 |
93dc5a55
|
leszek
|
private Static3D mEffectAx;
|
49 |
|
|
private int mAxNum;
|
50 |
63bfcdd1
|
leszek
|
private int mGhostRot;
|
51 |
4a014840
|
leszek
|
|
52 |
e3169794
|
leszek
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
53 |
|
|
|
54 |
a0ef8a1d
|
leszek
|
TwistyLayerRotations(TwistyObject parent, Static3D[] axis, int[] numLayers, int ghostAngle, DistortedEffects effect)
|
55 |
e3169794
|
leszek
|
{
|
56 |
|
|
mParent = parent;
|
57 |
8c70541c
|
leszek
|
mGhostAngle = ghostAngle;
|
58 |
a0ef8a1d
|
leszek
|
mAxis = axis;
|
59 |
93dc5a55
|
leszek
|
mNumLayers = numLayers;
|
60 |
4a014840
|
leszek
|
|
61 |
|
|
int maxLayers = -1;
|
62 |
|
|
for(int numLayer : numLayers) if(maxLayers<numLayer) maxLayers = numLayer;
|
63 |
e3169794
|
leszek
|
mMaxNumLayers = maxLayers;
|
64 |
|
|
mRotationState = STATE_NOTHING;
|
65 |
|
|
|
66 |
|
|
mRotationAngleStatic = new Static1D(0);
|
67 |
|
|
mRotationAngleMiddle = new Static1D(0);
|
68 |
|
|
mRotationAngleFinal = new Static1D(0);
|
69 |
|
|
|
70 |
|
|
mRotationAngle= new Dynamic1D();
|
71 |
|
|
mRotationAxis = new Static3D(1,0,0);
|
72 |
|
|
mRotateEffect = new VertexEffectRotate(mRotationAngle, mRotationAxis, CENTER);
|
73 |
4a014840
|
leszek
|
|
74 |
5df97ecb
|
leszek
|
effect.apply(mRotateEffect);
|
75 |
|
|
|
76 |
4a014840
|
leszek
|
if( mGhostAngle!=0 )
|
77 |
|
|
{
|
78 |
93dc5a55
|
leszek
|
mAxNum = 0;
|
79 |
|
|
int nL = mNumLayers[mAxNum];
|
80 |
|
|
mEffectAx = mAxis[mAxNum];
|
81 |
|
|
int numRightRows = nL - nL/2;
|
82 |
63bfcdd1
|
leszek
|
mSolvedBitmap = ((1<<numRightRows)-1)<<(nL/2);
|
83 |
|
|
mGhostEffectBitmap = mSolvedBitmap;
|
84 |
93dc5a55
|
leszek
|
mParent.enableGhostAxis(mAxNum,false);
|
85 |
|
|
Static1D ghostAngle1= new Static1D(mGhostAngle);
|
86 |
|
|
mGhostEffect1 = new VertexEffectRotate(ghostAngle1, mEffectAx, CENTER);
|
87 |
|
|
mGhostEffect1.setMeshAssociation(mGhostEffectBitmap,-1);
|
88 |
4a014840
|
leszek
|
effect.apply(mGhostEffect1);
|
89 |
|
|
}
|
90 |
e3169794
|
leszek
|
}
|
91 |
|
|
|
92 |
4a014840
|
leszek
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
93 |
|
|
// GHOST
|
94 |
5df97ecb
|
leszek
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
95 |
|
|
|
96 |
|
|
private int computeGhostAngle(int row, int basicAngle, float angle, float speed)
|
97 |
|
|
{
|
98 |
63bfcdd1
|
leszek
|
boolean ghosted = rowIsGhosted(row);
|
99 |
|
|
int basic = 360/basicAngle;
|
100 |
|
|
int nearest = computeNearest( ghosted ? basic-mGhostAngle : mGhostAngle, basic, angle );
|
101 |
|
|
if( (nearest%basic)!=0 )
|
102 |
|
|
{
|
103 |
|
|
changeRow(row);
|
104 |
|
|
mGhostRot = (ghosted ? 1:-1)*mGhostAngle;
|
105 |
|
|
}
|
106 |
4a014840
|
leszek
|
|
107 |
63bfcdd1
|
leszek
|
int ret= computeNoGhostAngle(basicAngle,angle,speed);
|
108 |
4a014840
|
leszek
|
|
109 |
63bfcdd1
|
leszek
|
android.util.Log.e("D", "computeGhostAngle: "+ret+" ghostRot="+mGhostRot+" angle>0: "+(angle>0)+" ghosted="+ghosted);
|
110 |
|
|
|
111 |
|
|
return ret;
|
112 |
4a014840
|
leszek
|
}
|
113 |
|
|
|
114 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
115 |
|
|
|
116 |
93dc5a55
|
leszek
|
private boolean rowIsGhosted(int row)
|
117 |
4a014840
|
leszek
|
{
|
118 |
93dc5a55
|
leszek
|
return ((mGhostEffectBitmap>>row)&0x1)==1;
|
119 |
4a014840
|
leszek
|
}
|
120 |
|
|
|
121 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
122 |
|
|
|
123 |
63bfcdd1
|
leszek
|
private int computeNearest(int ghostAngle, int basicAngle, float angle)
|
124 |
4a014840
|
leszek
|
{
|
125 |
63bfcdd1
|
leszek
|
float A = (2*angle-ghostAngle)/basicAngle;
|
126 |
|
|
int k = A>=0 ? (int)A : (int)A-1;
|
127 |
|
|
return (k%2)==0 ? (k/2)*basicAngle+ghostAngle : ((k+1)/2)*basicAngle;
|
128 |
4a014840
|
leszek
|
}
|
129 |
|
|
|
130 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
131 |
|
|
|
132 |
93dc5a55
|
leszek
|
private void changeRow(int row)
|
133 |
4a014840
|
leszek
|
{
|
134 |
63bfcdd1
|
leszek
|
android.util.Log.e("D", "changing row "+row);
|
135 |
|
|
int nL = mNumLayers[mAxNum];
|
136 |
|
|
if( mGhostEffectBitmap==0 || mGhostEffectBitmap==((1<<nL)-1) ) mParent.enableGhostAxis(mAxNum,false);
|
137 |
93dc5a55
|
leszek
|
mGhostEffectBitmap ^= (1<<row);
|
138 |
63bfcdd1
|
leszek
|
if( mGhostEffectBitmap==0 || mGhostEffectBitmap==((1<<nL)-1) ) mParent.enableGhostAxis(mAxNum,true);
|
139 |
93dc5a55
|
leszek
|
mGhostEffect1.setMeshAssociation( mGhostEffectBitmap, -1);
|
140 |
4a014840
|
leszek
|
}
|
141 |
|
|
|
142 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
143 |
|
|
// END GHOST
|
144 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
145 |
|
|
|
146 |
5df97ecb
|
leszek
|
private int computeNoGhostAngle(int basicAngle, float angle, float speed)
|
147 |
4a014840
|
leszek
|
{
|
148 |
|
|
int basicDeg = 360/basicAngle;
|
149 |
|
|
int numRot = (int)(angle/basicDeg + 0.5f);
|
150 |
|
|
if( angle< -(basicDeg*0.5) ) numRot-=1;
|
151 |
|
|
if( numRot==0 && speed>1.1f ) numRot = (angle>0 ? 1:-1);
|
152 |
|
|
return basicDeg*numRot;
|
153 |
|
|
}
|
154 |
|
|
|
155 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
156 |
|
|
|
157 |
|
|
private int computeNearestAngle(int axisIndex, int row, int basicAngle, float angle, float speed)
|
158 |
|
|
{
|
159 |
63bfcdd1
|
leszek
|
if( mGhostAngle==0 || axisIndex!=0 /*|| !mCanGhost*/ )
|
160 |
8c70541c
|
leszek
|
{
|
161 |
5df97ecb
|
leszek
|
return computeNoGhostAngle(basicAngle,angle,speed);
|
162 |
8c70541c
|
leszek
|
}
|
163 |
|
|
else
|
164 |
|
|
{
|
165 |
5df97ecb
|
leszek
|
return computeGhostAngle(row,basicAngle,angle,speed);
|
166 |
8c70541c
|
leszek
|
}
|
167 |
e3169794
|
leszek
|
}
|
168 |
|
|
|
169 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
170 |
|
|
|
171 |
4a014840
|
leszek
|
private float getAngle()
|
172 |
e3169794
|
leszek
|
{
|
173 |
4a014840
|
leszek
|
int pointNum = mRotationAngle.getNumPoints();
|
174 |
|
|
return pointNum>=1 ? mRotationAngle.getPoint(pointNum-1).get0() : 0;
|
175 |
e3169794
|
leszek
|
}
|
176 |
|
|
|
177 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
178 |
|
|
// API
|
179 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
180 |
4a014840
|
leszek
|
// the rotation has fully finished (everything including the last mini 'come back'). Delete it.
|
181 |
e3169794
|
leszek
|
|
182 |
4a014840
|
leszek
|
synchronized float removeRotation()
|
183 |
e3169794
|
leszek
|
{
|
184 |
|
|
float angle = getAngle();
|
185 |
|
|
|
186 |
|
|
mRotationAngleStatic.set0(0);
|
187 |
|
|
mRotationAngle.removeAll();
|
188 |
|
|
mParent.rotateAllCubits(mCurrentRotAxis,mRotRowBitmap, (int)angle );
|
189 |
|
|
mRotationState = STATE_NOTHING;
|
190 |
|
|
|
191 |
|
|
return angle;
|
192 |
|
|
}
|
193 |
|
|
|
194 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
195 |
5df97ecb
|
leszek
|
// we have just lifted the finger; create this last little 'come back'
|
196 |
e3169794
|
leszek
|
|
197 |
a0ef8a1d
|
leszek
|
long finishRotation(ObjectPreRender pre, int axisIndex, int row, int basicAngle, float finishAngle, float avgSpeed)
|
198 |
e3169794
|
leszek
|
{
|
199 |
63bfcdd1
|
leszek
|
mGhostRot = 0;
|
200 |
4a014840
|
leszek
|
int nearestAngleInDegrees = computeNearestAngle(axisIndex,row,basicAngle,finishAngle,avgSpeed);
|
201 |
e3169794
|
leszek
|
|
202 |
|
|
mRotationState = STATE_FINISH;
|
203 |
63bfcdd1
|
leszek
|
float angle = getAngle() + mGhostRot;
|
204 |
e3169794
|
leszek
|
mRotationAngleStatic.set0(angle);
|
205 |
|
|
mRotationAngleFinal.set0(nearestAngleInDegrees);
|
206 |
|
|
mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-angle)*0.2f );
|
207 |
|
|
mRotationAngle.setDuration(POST_ROTATION_MILLISEC);
|
208 |
|
|
mRotationAngle.resetToBeginning();
|
209 |
|
|
mRotationAngle.removeAll();
|
210 |
|
|
mRotationAngle.add(mRotationAngleStatic);
|
211 |
|
|
mRotationAngle.add(mRotationAngleMiddle);
|
212 |
|
|
mRotationAngle.add(mRotationAngleFinal);
|
213 |
a0ef8a1d
|
leszek
|
mRotateEffect.notifyWhenFinished(pre);
|
214 |
|
|
|
215 |
e3169794
|
leszek
|
return mRotateEffect.getID();
|
216 |
|
|
}
|
217 |
|
|
|
218 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
219 |
4a014840
|
leszek
|
// rotation has begun and we have just moved the finger a bit
|
220 |
e3169794
|
leszek
|
|
221 |
|
|
void continueRotation(float angleInDegrees)
|
222 |
|
|
{
|
223 |
|
|
mRotationAngleStatic.set0(angleInDegrees);
|
224 |
|
|
}
|
225 |
|
|
|
226 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
227 |
4a014840
|
leszek
|
// this is 'programmatic' rotation - i.e. for example the one induced by the 'backMove' button.
|
228 |
e3169794
|
leszek
|
|
229 |
63bfcdd1
|
leszek
|
synchronized long addRotation(EffectListener listener, int axisIndex, int rowBitmap, int angle, long durationMillis )
|
230 |
e3169794
|
leszek
|
{
|
231 |
|
|
int mult = 1;
|
232 |
|
|
|
233 |
|
|
if( mRotationState==STATE_ROTATE ) return 0;
|
234 |
4a014840
|
leszek
|
if( mRotationState==STATE_FINISH ) { removeRotation(); mult = -1; }
|
235 |
e3169794
|
leszek
|
|
236 |
|
|
mRotationState = STATE_ROTATE;
|
237 |
4a014840
|
leszek
|
mCurrentRotAxis= axisIndex;
|
238 |
|
|
mRotRowBitmap = mParent.computeBandagedBitmap(rowBitmap,axisIndex);
|
239 |
e3169794
|
leszek
|
mRotationAngleStatic.set0(0.0f);
|
240 |
63bfcdd1
|
leszek
|
mRotationAxis.set(mAxis[axisIndex]);
|
241 |
e3169794
|
leszek
|
mRotationAngle.setDuration(durationMillis);
|
242 |
|
|
mRotationAngle.resetToBeginning();
|
243 |
|
|
mRotationAngle.add(new Static1D(0));
|
244 |
|
|
mRotationAngle.add(new Static1D(angle));
|
245 |
|
|
mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axisIndex*mMaxNumLayers) , -1);
|
246 |
|
|
mRotateEffect.notifyWhenFinished(listener);
|
247 |
|
|
|
248 |
|
|
return mult*mRotateEffect.getID();
|
249 |
|
|
}
|
250 |
|
|
|
251 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
252 |
4a014840
|
leszek
|
// i.e. we just touched the screen, moved it a little bit and the TouchControl has figured out
|
253 |
|
|
// along which axis we are going to rotate.
|
254 |
e3169794
|
leszek
|
|
255 |
63bfcdd1
|
leszek
|
synchronized boolean beginRotation(int axisIndex, int row )
|
256 |
e3169794
|
leszek
|
{
|
257 |
|
|
if( mRotationState==STATE_ROTATE ) return false;
|
258 |
4a014840
|
leszek
|
if( mRotationState==STATE_FINISH ) removeRotation();
|
259 |
e3169794
|
leszek
|
|
260 |
|
|
mRotationState = STATE_ROTATE;
|
261 |
4a014840
|
leszek
|
mCurrentRotAxis= axisIndex;
|
262 |
|
|
mRotRowBitmap = mParent.computeBandagedBitmap( (1<<row),axisIndex );
|
263 |
e3169794
|
leszek
|
mRotationAngleStatic.set0(0.0f);
|
264 |
63bfcdd1
|
leszek
|
mRotationAxis.set( mAxis[axisIndex] );
|
265 |
e3169794
|
leszek
|
mRotationAngle.add(mRotationAngleStatic);
|
266 |
|
|
mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axisIndex*mMaxNumLayers) , -1);
|
267 |
|
|
|
268 |
|
|
return true;
|
269 |
|
|
}
|
270 |
5df97ecb
|
leszek
|
|
271 |
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
272 |
|
|
|
273 |
|
|
boolean isSolved(boolean solved)
|
274 |
|
|
{
|
275 |
|
|
mCanGhost = solved;
|
276 |
63bfcdd1
|
leszek
|
return (solved && (mGhostAngle==0 || mGhostEffectBitmap==mSolvedBitmap) );
|
277 |
5df97ecb
|
leszek
|
}
|
278 |
e3169794
|
leszek
|
}
|