1
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
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
|
import org.distorted.library.main.DistortedEffects;
|
14
|
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
|
// ghost angle
|
41
|
private final int mGhostAngle;
|
42
|
private final Static3D[] mAxis;
|
43
|
private final int[] mNumLayers;
|
44
|
private int mSolvedBitmap;
|
45
|
private int mGhostEffectBitmap;
|
46
|
private VertexEffectRotate mGhostEffect1;
|
47
|
private boolean mCanGhost;
|
48
|
private Static3D mEffectAx;
|
49
|
private int mAxNum;
|
50
|
private int mGhostRot;
|
51
|
|
52
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
53
|
|
54
|
TwistyLayerRotations(TwistyObject parent, Static3D[] axis, int[] numLayers, int ghostAngle, DistortedEffects effect)
|
55
|
{
|
56
|
mParent = parent;
|
57
|
mGhostAngle = ghostAngle;
|
58
|
mAxis = axis;
|
59
|
mNumLayers = numLayers;
|
60
|
|
61
|
int maxLayers = -1;
|
62
|
for(int numLayer : numLayers) if(maxLayers<numLayer) maxLayers = numLayer;
|
63
|
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
|
|
74
|
effect.apply(mRotateEffect);
|
75
|
|
76
|
if( mGhostAngle!=0 )
|
77
|
{
|
78
|
mAxNum = 0;
|
79
|
int nL = mNumLayers[mAxNum];
|
80
|
mEffectAx = mAxis[mAxNum];
|
81
|
int numRightRows = nL - nL/2;
|
82
|
mSolvedBitmap = ((1<<numRightRows)-1)<<(nL/2);
|
83
|
mGhostEffectBitmap = mSolvedBitmap;
|
84
|
mParent.enableGhostAxis(mAxNum,false);
|
85
|
Static1D ghostAngle1= new Static1D(mGhostAngle);
|
86
|
mGhostEffect1 = new VertexEffectRotate(ghostAngle1, mEffectAx, CENTER);
|
87
|
mGhostEffect1.setMeshAssociation(mGhostEffectBitmap,-1);
|
88
|
effect.apply(mGhostEffect1);
|
89
|
}
|
90
|
}
|
91
|
|
92
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
93
|
// GHOST
|
94
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
95
|
|
96
|
private int computeGhostAngle(int row, int basicAngle, float angle, float speed)
|
97
|
{
|
98
|
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
|
|
107
|
int ret= computeNoGhostAngle(basicAngle,angle,speed);
|
108
|
|
109
|
android.util.Log.e("D", "computeGhostAngle: "+ret+" ghostRot="+mGhostRot+" angle>0: "+(angle>0)+" ghosted="+ghosted);
|
110
|
|
111
|
return ret;
|
112
|
}
|
113
|
|
114
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
115
|
|
116
|
private boolean rowIsGhosted(int row)
|
117
|
{
|
118
|
return ((mGhostEffectBitmap>>row)&0x1)==1;
|
119
|
}
|
120
|
|
121
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
122
|
|
123
|
private int computeNearest(int ghostAngle, int basicAngle, float angle)
|
124
|
{
|
125
|
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
|
}
|
129
|
|
130
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
131
|
|
132
|
private void changeRow(int row)
|
133
|
{
|
134
|
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
|
mGhostEffectBitmap ^= (1<<row);
|
138
|
if( mGhostEffectBitmap==0 || mGhostEffectBitmap==((1<<nL)-1) ) mParent.enableGhostAxis(mAxNum,true);
|
139
|
mGhostEffect1.setMeshAssociation( mGhostEffectBitmap, -1);
|
140
|
}
|
141
|
|
142
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
143
|
// END GHOST
|
144
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
145
|
|
146
|
private int computeNoGhostAngle(int basicAngle, float angle, float speed)
|
147
|
{
|
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
|
if( mGhostAngle==0 || axisIndex!=0 /*|| !mCanGhost*/ )
|
160
|
{
|
161
|
return computeNoGhostAngle(basicAngle,angle,speed);
|
162
|
}
|
163
|
else
|
164
|
{
|
165
|
return computeGhostAngle(row,basicAngle,angle,speed);
|
166
|
}
|
167
|
}
|
168
|
|
169
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
170
|
|
171
|
private float getAngle()
|
172
|
{
|
173
|
int pointNum = mRotationAngle.getNumPoints();
|
174
|
return pointNum>=1 ? mRotationAngle.getPoint(pointNum-1).get0() : 0;
|
175
|
}
|
176
|
|
177
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
178
|
// API
|
179
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
180
|
// the rotation has fully finished (everything including the last mini 'come back'). Delete it.
|
181
|
|
182
|
synchronized float removeRotation()
|
183
|
{
|
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
|
// we have just lifted the finger; create this last little 'come back'
|
196
|
|
197
|
long finishRotation(ObjectPreRender pre, int axisIndex, int row, int basicAngle, float finishAngle, float avgSpeed)
|
198
|
{
|
199
|
mGhostRot = 0;
|
200
|
int nearestAngleInDegrees = computeNearestAngle(axisIndex,row,basicAngle,finishAngle,avgSpeed);
|
201
|
|
202
|
mRotationState = STATE_FINISH;
|
203
|
float angle = getAngle() + mGhostRot;
|
204
|
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
|
mRotateEffect.notifyWhenFinished(pre);
|
214
|
|
215
|
return mRotateEffect.getID();
|
216
|
}
|
217
|
|
218
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
219
|
// rotation has begun and we have just moved the finger a bit
|
220
|
|
221
|
void continueRotation(float angleInDegrees)
|
222
|
{
|
223
|
mRotationAngleStatic.set0(angleInDegrees);
|
224
|
}
|
225
|
|
226
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
227
|
// this is 'programmatic' rotation - i.e. for example the one induced by the 'backMove' button.
|
228
|
|
229
|
synchronized long addRotation(EffectListener listener, int axisIndex, int rowBitmap, int angle, long durationMillis )
|
230
|
{
|
231
|
int mult = 1;
|
232
|
|
233
|
if( mRotationState==STATE_ROTATE ) return 0;
|
234
|
if( mRotationState==STATE_FINISH ) { removeRotation(); mult = -1; }
|
235
|
|
236
|
mRotationState = STATE_ROTATE;
|
237
|
mCurrentRotAxis= axisIndex;
|
238
|
mRotRowBitmap = mParent.computeBandagedBitmap(rowBitmap,axisIndex);
|
239
|
mRotationAngleStatic.set0(0.0f);
|
240
|
mRotationAxis.set(mAxis[axisIndex]);
|
241
|
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
|
// 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
|
|
255
|
synchronized boolean beginRotation(int axisIndex, int row )
|
256
|
{
|
257
|
if( mRotationState==STATE_ROTATE ) return false;
|
258
|
if( mRotationState==STATE_FINISH ) removeRotation();
|
259
|
|
260
|
mRotationState = STATE_ROTATE;
|
261
|
mCurrentRotAxis= axisIndex;
|
262
|
mRotRowBitmap = mParent.computeBandagedBitmap( (1<<row),axisIndex );
|
263
|
mRotationAngleStatic.set0(0.0f);
|
264
|
mRotationAxis.set( mAxis[axisIndex] );
|
265
|
mRotationAngle.add(mRotationAngleStatic);
|
266
|
mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axisIndex*mMaxNumLayers) , -1);
|
267
|
|
268
|
return true;
|
269
|
}
|
270
|
|
271
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
272
|
|
273
|
boolean isSolved(boolean solved)
|
274
|
{
|
275
|
mCanGhost = solved;
|
276
|
return (solved && (mGhostAngle==0 || mGhostEffectBitmap==mSolvedBitmap) );
|
277
|
}
|
278
|
}
|