Project

General

Profile

Download (25.4 KB) Statistics
| Branch: | Revision:

distorted-objectlib / src / main / java / org / distorted / objectlib / touchcontrol / TouchControlShapeChanging.java @ b28f909c

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2021 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.touchcontrol;
11

    
12
import org.distorted.library.helpers.QuatHelper;
13
import org.distorted.library.type.Static3D;
14
import org.distorted.library.type.Static4D;
15
import org.distorted.objectlib.helpers.ObjectShape;
16
import org.distorted.objectlib.main.TwistyObject;
17

    
18
///////////////////////////////////////////////////////////////////////////////////////////////////
19

    
20
public class TouchControlShapeChanging extends TouchControl
21
  {
22
  private static final float NOT_TOUCHED = 1000000.0f;
23
  static final float[] mTmp = new float[4];
24

    
25
  static class FaceInfo
26
    {
27
    private final float[] normal;      // vector normal to the surface of the face, pointing outside.
28
    private final float distance;      // distance from (0,0,0) to the surface of the face
29
    private final float[][] vertices;  // vertices of the face. Already rotated by the initQuat and
30
                                       // moved by 'position' (arithmetic average of all positions)
31
    private final float[][] rotated;   // temp array to store vertices times rotation quaternion.
32

    
33
    //////////////////////////////////////////////////////////
34

    
35
    FaceInfo(float[][] verts, float size)
36
      {
37
      int numV = verts.length;
38

    
39
      vertices = new float[numV][];
40
      rotated  = new float[numV][];
41

    
42
      for(int i=0; i<numV; i++)
43
        {
44
        int len = verts[i].length;
45
        vertices[i]= new float[len];
46
        rotated[i] = new float[len];
47

    
48
        for(int j=0; j<len; j++) vertices[i][j] = verts[i][j]/size;
49
        }
50

    
51
      // assuming the first three vertices are linearly independent
52
      float[] v0 = vertices[0];
53
      float[] v1 = vertices[1];
54
      float[] v2 = vertices[2];
55

    
56
      float a1 = v0[0] - v1[0];
57
      float a2 = v0[1] - v1[1];
58
      float a3 = v0[2] - v1[2];
59
      float b1 = v1[0] - v2[0];
60
      float b2 = v1[1] - v2[1];
61
      float b3 = v1[2] - v2[2];
62

    
63
      float vx = a2*b3-a3*b2;
64
      float vy = a3*b1-a1*b3;
65
      float vz = a1*b2-a2*b1;
66

    
67
      float len = (float)Math.sqrt(vx*vx+vy*vy+vz*vz);
68

    
69
      vx/=len;
70
      vy/=len;
71
      vz/=len;
72

    
73
      normal = new float[4];
74
      normal[0] = vx;
75
      normal[1] = vy;
76
      normal[2] = vz;
77
      normal[3] = 0.0f;
78

    
79
      if( totalAngle(vertices,normal)<0 )
80
        {
81
        normal[0] *= -1;
82
        normal[1] *= -1;
83
        normal[2] *= -1;
84
        }
85

    
86
      distance = normal[0]*v0[0] + normal[1]*v0[1] + normal[2]*v0[2];
87
      }
88

    
89
    //////////////////////////////////////////////////////////
90
    // this returns the total angle we get rotated about when
91
    // we travel from the first vert to the last.
92
    // It's always should be +2PI or -2PI, depending on if the
93
    // verts are CW or CCW (when looking at the plane formed by
94
    // the vertices from the direction the 'normal' vector
95
    // points towards)
96
    //
97
    // The point: this way we can detect if the 'normal' vector
98
    // is correct, i.e. if it points in the right direction.
99
    // Sometimes it does not, when the first three vertices
100
    // (from which the vector is computed) are 'concave'.
101

    
102
    private float totalAngle(float[][] vert, float[] normal)
103
      {
104
      float ret = 0;
105
      int num = vert.length;
106

    
107
      for(int c=0; c<num; c++)
108
        {
109
        int n = (c==num-1 ? 0 : c+1);
110
        int m = (n==num-1 ? 0 : n+1);
111
        ret += angle( vert[c],vert[n],vert[m], normal);
112
        }
113

    
114
      return ret;
115
      }
116

    
117
    //////////////////////////////////////////////////////////
118

    
119
    private float angle(float[] v0, float[] v1, float[] v2, float[] normal)
120
      {
121
      float px = v1[0] - v0[0];
122
      float py = v1[1] - v0[1];
123
      float pz = v1[2] - v0[2];
124

    
125
      float rx = v2[0] - v1[0];
126
      float ry = v2[1] - v1[1];
127
      float rz = v2[2] - v1[2];
128

    
129
      float l1 = (float)Math.sqrt(px*px + py*py + pz*pz);
130
      float l2 = (float)Math.sqrt(rx*rx + ry*ry + rz*rz);
131

    
132
      px /= l1;
133
      py /= l1;
134
      pz /= l1;
135

    
136
      rx /= l2;
137
      ry /= l2;
138
      rz /= l2;
139

    
140
      float s = px*rx + py*ry + pz*rz;
141

    
142
      if( s> 1 ) s= 1;
143
      if( s<-1 ) s=-1;
144

    
145
      float a = (float) Math.acos(s);
146
      float[] cross = crossProduct(px,py,pz,rx,ry,rz);
147

    
148
      float ax = cross[0] + normal[0];
149
      float ay = cross[1] + normal[1];
150
      float az = cross[2] + normal[2];
151

    
152
      float bx = cross[0] - normal[0];
153
      float by = cross[1] - normal[1];
154
      float bz = cross[2] - normal[2];
155

    
156
      float f1 = ax*ax + ay*ay + az*az;
157
      float f2 = bx*bx + by*by + bz*bz;
158

    
159
      return f1>f2 ? a : -a;
160
      }
161

    
162
    //////////////////////////////////////////////////////////
163

    
164
    private float[] crossProduct(float a1, float a2, float a3, float b1, float b2, float b3)
165
      {
166
      float[] ret = new float[3];
167

    
168
      ret[0] = a2*b3 - a3*b2;
169
      ret[1] = a3*b1 - a1*b3;
170
      ret[2] = a1*b2 - a2*b1;
171

    
172
      return ret;
173
      }
174

    
175
    //////////////////////////////////////////////////////////
176
    public float[] getNormal()
177
      {
178
      return normal;
179
      }
180
    }
181
  ////////////////////////////////////////////////////////////
182
  // end FaceInfo
183

    
184
  private final float[] mTouch, mLastT;
185
  private final Static4D mTmpAxis;
186
  private int mNumCubits;
187
  private int[] mNumFaces;
188
  private boolean mPreparationDone;
189
  private boolean[][] mRotatable;
190
  private float[][] mCuts;
191

    
192
  final float[] mCamera, mPoint;
193
  final Static3D[] mRotAxis;
194
  final TwistyObject mObject;
195
  int mTouchedCubit, mTouchedCubitFace, mNumAxis;
196
  FaceInfo[][] mInfos;
197
  float[][] mQuats;
198

    
199
///////////////////////////////////////////////////////////////////////////////////////////////////
200

    
201
  public TouchControlShapeChanging(TwistyObject object)
202
    {
203
    super(object);
204

    
205
    mPoint = new float[3];
206
    mCamera= new float[3];
207
    mTouch = new float[3];
208
    mLastT = new float[3];
209
    mObject= object;
210
    mPreparationDone = false;
211
    mTmpAxis = new Static4D(0,0,0,0);
212
    mGhostAxisEnabled = -1;
213

    
214
    if( object!=null )
215
      {
216
      int[] numL  = object.getNumLayers();
217
      mRotAxis    = object.getRotationAxis() ;
218
      mNumAxis    = mRotAxis.length;
219
      mRotatable  = object.getLayerRotatable(numL);
220
      mCuts       = object.getCuts(numL);
221
      float size  = object.getSize();
222
      computeBorders(mCuts,mRotatable,size);
223
      }
224
    else
225
      {
226
      mRotAxis = null;
227
      mNumAxis = 0;
228
      }
229
    }
230

    
231
///////////////////////////////////////////////////////////////////////////////////////////////////
232
// mesh multigon
233

    
234
  private FaceInfo[] computeInfos(float[][] vertices, int[][][] indices, float[] position, Static4D quat, float size)
235
    {
236
    int len = position.length/3;
237
    float avgX = 0.0f;
238
    float avgY = 0.0f;
239
    float avgZ = 0.0f;
240

    
241
    for(int i=0; i<len; i++)
242
      {
243
      avgX += position[3*i  ];
244
      avgY += position[3*i+1];
245
      avgZ += position[3*i+2];
246
      }
247

    
248
    avgX /= len;
249
    avgY /= len;
250
    avgZ /= len;
251

    
252
    int numFaces = indices.length;
253
    FaceInfo[] infos = new FaceInfo[numFaces];
254
    Static4D tmp;
255

    
256
    for(int f=0; f<numFaces; f++)
257
      {
258
      int[][] inds = indices[f];
259
      int numSegments = inds.length;
260
      int numVerts = 0;
261
      for(int[] ind : inds) numVerts += ind.length;
262

    
263
      float[][] verts = new float[numVerts][4];
264
      int pointer = 0;
265

    
266
      for(int s=0; s<numSegments; s++)
267
        {
268
        int numV = inds[s].length;
269

    
270
        for(int v=0; v<numV; v++)
271
          {
272
          int index = indices[f][s][v];
273
          float x = vertices[index][0];
274
          float y = vertices[index][1];
275
          float z = vertices[index][2];
276
          float w = 1.0f;
277

    
278
          tmp = QuatHelper.rotateVectorByQuat(x,y,z,w,quat);
279

    
280
          verts[pointer][0] = tmp.get0() + avgX;
281
          verts[pointer][1] = tmp.get1() + avgY;
282
          verts[pointer][2] = tmp.get2() + avgZ;
283
          verts[pointer][3] = 1.0f;
284
          pointer++;
285
          }
286
        }
287

    
288
      infos[f] = new FaceInfo(verts,size);
289
      }
290

    
291
    return infos;
292
    }
293

    
294
///////////////////////////////////////////////////////////////////////////////////////////////////
295
// mesh polygon
296

    
297
  private FaceInfo[] computeInfos(float[][] vertices, int[][] indices, float[] position, Static4D quat, float size)
298
    {
299
    int len = position.length/3;
300
    float avgX = 0.0f;
301
    float avgY = 0.0f;
302
    float avgZ = 0.0f;
303

    
304
    for(int i=0; i<len; i++)
305
      {
306
      avgX += position[3*i  ];
307
      avgY += position[3*i+1];
308
      avgZ += position[3*i+2];
309
      }
310

    
311
    avgX /= len;
312
    avgY /= len;
313
    avgZ /= len;
314

    
315
    int numFaces = indices.length;
316
    FaceInfo[] infos = new FaceInfo[numFaces];
317
    Static4D tmp;
318

    
319
    for(int f=0; f<numFaces; f++)
320
      {
321
      int numVerts = indices[f].length;
322
      float[][] verts = new float[numVerts][4];
323

    
324
      for(int v=0; v<numVerts; v++)
325
        {
326
        int index = indices[f][v];
327
        float x = vertices[index][0];
328
        float y = vertices[index][1];
329
        float z = vertices[index][2];
330
        float w = 1.0f;
331

    
332
        tmp = QuatHelper.rotateVectorByQuat(x,y,z,w,quat);
333

    
334
        verts[v][0] = tmp.get0() + avgX;
335
        verts[v][1] = tmp.get1() + avgY;
336
        verts[v][2] = tmp.get2() + avgZ;
337
        verts[v][3] = 1.0f;
338
        }
339

    
340
      infos[f] = new FaceInfo(verts,size);
341
      }
342

    
343
    return infos;
344
    }
345

    
346
///////////////////////////////////////////////////////////////////////////////////////////////////
347
// software implementation of DistortedLibrary.mainVertexShader.degree() function.
348
// (limited to regions centered at [0,0,0])
349

    
350
  private float computeVertexDegree(float radius, float[] vert)
351
    {
352
    float x = vert[0];
353
    float y = vert[1];
354
    float z = vert[2];
355

    
356
    float len = (float)Math.sqrt(x*x + y*y + z*z);
357
    return len>radius ? 0.0f : 1.0f-len/radius;
358
    }
359

    
360
///////////////////////////////////////////////////////////////////////////////////////////////////
361
// software implementation of DistortedLibrary.VertexEffectSink
362

    
363
  private float[] adjustVert(float pillow, float radius, float[] vert)
364
    {
365
    float[] output = new float[3];
366
    float deg = computeVertexDegree(radius,vert);
367
    float t = 1.0f - deg*(1.0f-pillow)/pillow;
368
    output[0] = t*vert[0];
369
    output[1] = t*vert[1];
370
    output[2] = t*vert[2];
371

    
372
    return output;
373
    }
374

    
375
///////////////////////////////////////////////////////////////////////////////////////////////////
376

    
377
  private float[][] adjustVerticesForPillow(float pillow, float radius, float[][] verts)
378
    {
379
    int num = verts.length;
380
    float[][] output = new float[num][3];
381
    for(int i=0; i<num; i++) output[i] = adjustVert(pillow,radius,verts[i]);
382
    return output;
383
    }
384

    
385
///////////////////////////////////////////////////////////////////////////////////////////////////
386

    
387
  private void prepare()
388
    {
389
    int[] numLayers = mObject.getNumLayers();
390
    float[][] positions = mObject.getCubitPositions(numLayers);
391
    float size = mObject.getSize();
392
    mNumCubits = positions.length;
393
    mNumFaces  = new int[mNumCubits];
394

    
395
    mInfos = new FaceInfo[mNumCubits][];
396
    float pillow = mObject.getPillowCoeff();
397
    float radius = mObject.getCircumscribedRadius();
398

    
399
    for(int i=0; i<mNumCubits; i++)
400
      {
401
      int variant = mObject.getCubitVariant(i,numLayers);
402
      ObjectShape shape = mObject.getObjectShape(variant);
403
      Static4D quat = mObject.getCubitQuats(i,numLayers);
404
      float[][] vertices = shape.getVertices();
405
      if( pillow!=1.0f ) vertices = adjustVerticesForPillow(pillow,radius,vertices);
406
      mNumFaces[i] =shape.getNumFaces();
407

    
408
      int[][] indices = shape.getVertIndices();
409

    
410
      if( indices!=null )
411
        {
412
        mInfos[i] = computeInfos(vertices, indices, positions[i], quat, size);
413
        }
414
      else
415
        {
416
        int[][][] ind = shape.getMultigonIndices();
417
        mInfos[i] = computeInfos(vertices, ind, positions[i], quat, size);
418
        }
419
      }
420

    
421
    Static4D[] quats = mObject.getQuats();
422
    int numQuats = quats.length;
423

    
424
    mQuats = new float[numQuats][4];
425

    
426
    for(int i=0; i<numQuats; i++)
427
      {
428
      Static4D q = quats[i];
429
      mQuats[i][0] = q.get0();
430
      mQuats[i][1] = q.get1();
431
      mQuats[i][2] = q.get2();
432
      mQuats[i][3] = q.get3();
433
      }
434

    
435
    mPreparationDone = true;
436
    }
437

    
438
///////////////////////////////////////////////////////////////////////////////////////////////////
439
// points A, B, C are co-linear. Return true iff B is between A and C on this line.
440
// Compute D1 = A-B, D2=C-B. Then D1 and D2 are parallel vectors.
441
// They disagree in direction iff |D1+D2|<|D1-D2|
442

    
443
  private boolean isBetween(float ax, float ay, float az,
444
                            float bx, float by, float bz,
445
                            float cx, float cy, float cz)
446
    {
447
    float d1x = ax-bx;
448
    float d1y = ay-by;
449
    float d1z = az-bz;
450

    
451
    float d2x = cx-bx;
452
    float d2y = cy-by;
453
    float d2z = cz-bz;
454

    
455
    float sx = d1x+d2x;
456
    float sy = d1y+d2y;
457
    float sz = d1z+d2z;
458

    
459
    float dx = d1x-d2x;
460
    float dy = d1y-d2y;
461
    float dz = d1z-d2z;
462

    
463
    return sx*sx+sy*sy+sz*sz < dx*dx+dy*dy+dz*dz;
464
    }
465

    
466
///////////////////////////////////////////////////////////////////////////////////////////////////
467
// General algorithm: shoot a half-line from the 'point' and count how many
468
// sides of the polygon it intersects with. The point is inside iff this number
469
// is odd. Note that this works also in case of concave polygons.
470
//
471
// Arbitrarily take point P on the plane ( we have decided on P=(vert[0]+vert[1])/2 )
472
// as the other point defining the half-line.
473
// 'point' and 'P' define a line L1 in 3D. Then for each side the pair of its vertices
474
// defines a line L2. If L1||L2 return false. Otherwise, the lines are skew so it's
475
// possible to compute points C1 and C2 on lines L1 and L2 which are closest to the
476
// other line and check if
477
//
478
// a) C1 and P are on the same side of 'point'
479
//    (which happens iff 'point' is not in between of C1 and P)
480
// b) C2 is between the two vertices.
481
//
482
// Both a) and b) together mean that the half-line intersects with side defined by (p2,d2)
483
//
484
// C1 and C2 can be computed in the following way:
485
// Let n = d1 x d2 - then vector n is perpendicular to both d1 and d2 --> (c1-c2) is
486
// parallel to n.
487
// There exist real numbers A,B,C such that
488
// c1 = p1 + A*d1
489
// c2 = p2 + B*d2 and
490
// c2 = c1 + C*n so that
491
// p1 + A*d1 + C*n = p2 + B*d2  --> (p1-p2) + A*d1 = B*d2 - C*n (*)
492
// Let n2 = n x d2. Let's multiply both sides of (*) by n2. Then
493
// (p1-p2)*n2 + A*(d1*n2) = 0 (0 because d1*n2 = n*n2 = 0)
494
// and from that A = [(p1-p2)*n2]/[d1*n2]
495
// Similarly     B = [(p2-p1)*n1]/[d2*n1]  where n1 = n x d1.
496

    
497
  private boolean isInside(float[] point, float[][] vertices)
498
    {
499
    float e1x = (vertices[0][0]+vertices[1][0])/2;
500
    float e1y = (vertices[0][1]+vertices[1][1])/2;
501
    float e1z = (vertices[0][2]+vertices[1][2])/2;
502

    
503
    float d1x = e1x - point[0];
504
    float d1y = e1y - point[1];
505
    float d1z = e1z - point[2];
506

    
507
    float ax = vertices[0][0] - vertices[1][0];
508
    float ay = vertices[0][1] - vertices[1][1];
509
    float az = vertices[0][2] - vertices[1][2];
510

    
511
    float normX = d1y*az - d1z*ay;
512
    float normY = d1z*ax - d1x*az;
513
    float normZ = d1x*ay - d1y*ax;
514

    
515
    float n1x = d1y*normZ - d1z*normY;
516
    float n1y = d1z*normX - d1x*normZ;
517
    float n1z = d1x*normY - d1y*normX;
518

    
519
    float p1x = point[0];
520
    float p1y = point[1];
521
    float p1z = point[2];
522

    
523
    int len = vertices.length;
524
    int numCrossings = 0;
525

    
526
    for(int side=0; side<len; side++)
527
      {
528
      float p2x = vertices[side][0];
529
      float p2y = vertices[side][1];
530
      float p2z = vertices[side][2];
531

    
532
      int next = side==len-1 ? 0 : side+1;
533

    
534
      float e2x = vertices[next][0];
535
      float e2y = vertices[next][1];
536
      float e2z = vertices[next][2];
537

    
538
      float d2x = e2x-p2x;
539
      float d2y = e2y-p2y;
540
      float d2z = e2z-p2z;
541

    
542
      float nx = d2y*d1z - d2z*d1y;
543
      float ny = d2z*d1x - d2x*d1z;
544
      float nz = d2x*d1y - d2y*d1x;
545

    
546
      float n2x = d2y*nz - d2z*ny;
547
      float n2y = d2z*nx - d2x*nz;
548
      float n2z = d2x*ny - d2y*nx;
549

    
550
      float dpx = p1x-p2x;
551
      float dpy = p1y-p2y;
552
      float dpz = p1z-p2z;
553

    
554
      float A1 =-dpx*n2x-dpy*n2y-dpz*n2z;
555
      float B1 = d1x*n2x+d1y*n2y+d1z*n2z;
556

    
557
      float A2 = dpx*n1x+dpy*n1y+dpz*n1z;
558
      float B2 = d2x*n1x+d2y*n1y+d2z*n1z;
559

    
560
      if( B1==0 || B2==0 ) continue;
561

    
562
      float C1 = A1/B1;
563
      float C2 = A2/B2;
564

    
565
      float c1x = p1x + C1*d1x;
566
      float c1y = p1y + C1*d1y;
567
      float c1z = p1z + C1*d1z;
568

    
569
      float c2x = p2x + C2*d2x;
570
      float c2y = p2y + C2*d2y;
571
      float c2z = p2z + C2*d2z;
572

    
573
      if( !isBetween(c1x,c1y,c1z, p1x,p1y,p1z, e1x,e1y,e1z ) &&
574
           isBetween(p2x,p2y,p2z, c2x,c2y,c2z, e2x,e2y,e2z )  )
575
        {
576
        numCrossings++;
577
        }
578
      }
579

    
580
    return (numCrossings%2)==1;
581
    }
582

    
583
///////////////////////////////////////////////////////////////////////////////////////////////////
584

    
585
  private void rotateVertices(float[][] points, float[][] rotated, float[] quat)
586
    {
587
    int numPoints = points.length;
588

    
589
    for(int i=0; i<numPoints; i++)
590
      {
591
      QuatHelper.rotateVectorByQuat(rotated[i],points[i],quat);
592
      }
593
    }
594

    
595
///////////////////////////////////////////////////////////////////////////////////////////////////
596
// given precomputed mCamera and mPoint, respectively camera and touch point positions in ScreenSpace,
597
// a normalVec (nx,ny,nz) and distance (which together define a plane) compute point 'output[]' which:
598
// 1) lies on this plane
599
// 2) is co-linear with mCamera and mPoint
600
//
601
// output = camera + alpha*(point-camera), where alpha = [dist-normalVec*camera] / [normalVec*(point-camera)]
602

    
603
  void castTouchPointOntoFace(float nx, float ny, float nz, float distance, float[] output)
604
    {
605
    float d0 = mPoint[0]-mCamera[0];
606
    float d1 = mPoint[1]-mCamera[1];
607
    float d2 = mPoint[2]-mCamera[2];
608

    
609
    float denom = nx*d0 + ny*d1 + nz*d2;
610

    
611
    if( denom != 0.0f )
612
      {
613
      float axisCam = nx*mCamera[0] + ny*mCamera[1] + nz*mCamera[2];
614
      float alpha = (distance-axisCam)/denom;
615

    
616
      output[0] = mCamera[0] + d0*alpha;
617
      output[1] = mCamera[1] + d1*alpha;
618
      output[2] = mCamera[2] + d2*alpha;
619
      }
620
    }
621

    
622
///////////////////////////////////////////////////////////////////////////////////////////////////
623

    
624
  private boolean cubitFaceIsVisible(float nx, float ny, float nz, float distance)
625
    {
626
    return mCamera[0]*nx + mCamera[1]*ny + mCamera[2]*nz > distance;
627
    }
628

    
629
///////////////////////////////////////////////////////////////////////////////////////////////////
630
// FaceInfo defines a 3D plane (by means of a unit normal vector 'vector' and distance from the origin
631
// 'distance') and a list of points on the plane ('vertices').
632
//
633
// 0) rotate the face normal vector by quat
634
// 1) see if the face is visible. If not, return NOT_TOUCHED
635
// 2) else, cast the line passing through mPoint and mCamera onto this plane
636
// 3) if Z of this point is further from us than the already computed closestSoFar, return NOT_TOUCHED
637
// 4) else, rotate 'vertices' by quat and see if the casted point lies inside the polygon defined by them
638
// 5) if yes, return the distance from this point to the camera; otherwise, return NOT_TOUCHED
639

    
640
  private float cubitFaceTouched(FaceInfo info, float[] quat, float closestSoFar)
641
    {
642
    QuatHelper.rotateVectorByQuat(mTmp,info.normal,quat);
643
    float nx = mTmp[0];
644
    float ny = mTmp[1];
645
    float nz = mTmp[2];
646

    
647
    if( cubitFaceIsVisible(nx,ny,nz,info.distance) )
648
      {
649
      castTouchPointOntoFace(nx,ny,nz,info.distance,mTouch);
650

    
651
      float dx = mTouch[0]-mCamera[0];
652
      float dy = mTouch[1]-mCamera[1];
653
      float dz = mTouch[2]-mCamera[2];
654
      float dist = dx*dx + dy*dy + dz*dz;
655

    
656
      if( dist<closestSoFar )
657
        {
658
        rotateVertices(info.vertices,info.rotated,quat);
659
        if( isInside(mTouch,info.rotated) ) return dist;
660
        }
661
      }
662

    
663
    return NOT_TOUCHED;
664
    }
665

    
666
///////////////////////////////////////////////////////////////////////////////////////////////////
667
// This is in order to support non-rotatable rows.
668
// If mTouchedCubit ( as computed by objectTouched() ) turns out to be not rotatable with respect to
669
// the rotAxis, we need to figure out which of the neighbouring 'rotatable' cubits is the closest.
670
//
671
// Note that we cannot do it in objectTouched(), because then we do not yet know which axis we are
672
// going to be rotating along.
673

    
674
  private float distanceToRow(int rotIndex, int row)
675
    {
676
    float closestSoFar = NOT_TOUCHED;
677
    int numQuats = mQuats.length;
678
    int bmp = (1<<row);
679

    
680
    for(int cubit=0; cubit<mNumCubits; cubit++)
681
      {
682
      int rowBitmap = mObject.getCubitRotRow(cubit,rotIndex);
683

    
684
      if( (rowBitmap&bmp) != 0 )
685
        {
686
        int quatIndex = mObject.getCubitQuatIndex(cubit);
687

    
688
        if( quatIndex<numQuats )
689
          {
690
          float[] quat = mQuats[quatIndex];
691

    
692
          for(int face=0; face<mNumFaces[cubit]; face++)
693
            {
694
            float dist = cubitFaceTouched(mInfos[cubit][face],quat,closestSoFar);
695
            if( dist!=NOT_TOUCHED ) closestSoFar = dist;
696
            }
697
          }
698
        }
699
      }
700

    
701
    return closestSoFar;
702
    }
703

    
704
///////////////////////////////////////////////////////////////////////////////////////////////////
705

    
706
  private int notRotatable(int axis)
707
    {
708
    Static3D a = mRotAxis[axis];
709
    float x = mLastT[0]*a.get0() + mLastT[1]*a.get1() + mLastT[2]*a.get2();
710
    x *= mObject.getSize();
711
    float[] cuts = mCuts[axis];
712

    
713
    if( cuts==null ) return -1;
714
    int l = cuts.length;
715

    
716
    if( l>0 && x<=cuts[0]   ) return (l>=2 && x>=(3*cuts[  0]-cuts[  1])/2) ?   1 : -1;
717
    if( l>0 && x>=cuts[l-1] ) return (l>=2 && x<=(3*cuts[l-2]-cuts[l-1])/2) ? l-1 : -1;
718

    
719
    for(int i=1; i<l; i++)
720
      if( x<=cuts[i] )
721
        return x<((cuts[i-1]+cuts[i])/2) ? i-1 : i+1;
722

    
723
    return -1;
724
    }
725

    
726
///////////////////////////////////////////////////////////////////////////////////////////////////
727

    
728
  int computeRow(int cubit, int axis)
729
    {
730
    int row = mObject.getCubitRotRow(cubit,axis);
731

    
732
    for(int r=0; r<32; r++)
733
      {
734
      if( (row&1)==1 )
735
        return mRotatable[axis][r] ? r : notRotatable(axis);
736

    
737
      row>>=1;
738
      }
739

    
740
    return -1;
741
    }
742

    
743
///////////////////////////////////////////////////////////////////////////////////////////////////
744
// PUBLIC API
745
///////////////////////////////////////////////////////////////////////////////////////////////////
746

    
747
  public boolean objectTouched(Static4D rotatedTouchPoint, Static4D rotatedCamera)
748
    {
749
    if( !mPreparationDone ) prepare();
750

    
751
    mPoint[0]  = rotatedTouchPoint.get0()/mObjectRatio;
752
    mPoint[1]  = rotatedTouchPoint.get1()/mObjectRatio;
753
    mPoint[2]  = rotatedTouchPoint.get2()/mObjectRatio;
754

    
755
    mCamera[0] = rotatedCamera.get0()/mObjectRatio;
756
    mCamera[1] = rotatedCamera.get1()/mObjectRatio;
757
    mCamera[2] = rotatedCamera.get2()/mObjectRatio;
758

    
759
    float closestSoFar = NOT_TOUCHED;
760
    mTouchedCubit = -1;
761
    mTouchedCubitFace = -1;
762
    int numQuats = mQuats.length;
763

    
764
    for(int cubit=0; cubit<mNumCubits; cubit++)
765
      {
766
      int quatIndex = mObject.getCubitQuatIndex(cubit);
767

    
768
      if( quatIndex<numQuats )
769
        {
770
        float[] quat = mQuats[quatIndex];
771

    
772
        for(int face=0; face<mNumFaces[cubit]; face++)
773
          {
774
          float dist = cubitFaceTouched(mInfos[cubit][face],quat,closestSoFar);
775

    
776
          if( dist!=NOT_TOUCHED )
777
            {
778
            mTouchedCubit= cubit;
779
            mTouchedCubitFace = face;
780
            closestSoFar = dist;
781
            mLastT[0] = mTouch[0];
782
            mLastT[1] = mTouch[1];
783
            mLastT[2] = mTouch[2];
784
            }
785
          }
786
        }
787
      }
788

    
789
    return closestSoFar!=NOT_TOUCHED;
790
    }
791

    
792
///////////////////////////////////////////////////////////////////////////////////////////////////
793
// really implemented in derived classes; here present only because we need to be able to
794
// instantiate an object of this class for MODE_REPLACE.
795

    
796
  public void newRotation(int[] output, Static4D rotatedTouchPoint, Static4D quat)
797
    {
798

    
799
    }
800

    
801
///////////////////////////////////////////////////////////////////////////////////////////////////
802

    
803
  public void getCastedRotAxis(float[] output, Static4D quat, int axisIndex)
804
    {
805
    Static3D rotAxis = mRotAxis[axisIndex];
806
    float rx = rotAxis.get0();
807
    float ry = rotAxis.get1();
808
    float rz = rotAxis.get2();
809

    
810
    mTmpAxis.set(rx,ry,rz,0);
811
    Static4D result = QuatHelper.rotateVectorByQuat(mTmpAxis, quat);
812

    
813
    float cx =result.get0();
814
    float cy =result.get1();
815

    
816
    float len = (float)Math.sqrt(cx*cx+cy*cy);
817

    
818
    if( len!=0 )
819
      {
820
      output[0] = cx/len;
821
      output[1] = cy/len;
822
      }
823
    else
824
      {
825
      output[0] = 1;
826
      output[1] = 0;
827
      }
828
    }
829

    
830
///////////////////////////////////////////////////////////////////////////////////////////////////
831

    
832
  public boolean axisAndFaceAgree(int axisIndex)
833
    {
834
    return false;
835
    }
836

    
837
///////////////////////////////////////////////////////////////////////////////////////////////////
838

    
839
  public float[] getTouchedPuzzleCenter()
840
    {
841
    return null;
842
    }
843

    
844
///////////////////////////////////////////////////////////////////////////////////////////////////
845

    
846
  public int getTouchedCubitFace()
847
    {
848
    return mTouchedCubitFace;
849
    }
850

    
851
///////////////////////////////////////////////////////////////////////////////////////////////////
852

    
853
  public int getTouchedCubit()
854
    {
855
    return mTouchedCubit;
856
    }
857
  }
(9-9/13)