Project

General

Profile

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

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

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.main.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 a1 = vertices[0][0] - vertices[1][0];
53
      float a2 = vertices[0][1] - vertices[1][1];
54
      float a3 = vertices[0][2] - vertices[1][2];
55
      float b1 = vertices[1][0] - vertices[2][0];
56
      float b2 = vertices[1][1] - vertices[2][1];
57
      float b3 = vertices[1][2] - vertices[2][2];
58

    
59
      float vx = a2*b3-a3*b2;
60
      float vy = a3*b1-a1*b3;
61
      float vz = a1*b2-a2*b1;
62

    
63
      float len = (float)Math.sqrt(vx*vx+vy*vy+vz*vz);
64

    
65
      vx/=len;
66
      vy/=len;
67
      vz/=len;
68

    
69
      distance = vx*vertices[0][0] + vy*vertices[0][1] + vz*vertices[0][2];
70

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

    
78
    //////////////////////////////////////////////////////////
79

    
80
    public float[] getNormal()
81
      {
82
      return normal;
83
      }
84
    }
85

    
86
  private final float[] mTouch;
87
  private final Static4D mTmpAxis;
88
  private int mNumCubits;
89
  private int[] mNumFaces;
90
  private boolean mPreparationDone;
91

    
92
  final float[] mCamera, mPoint;
93
  final Static3D[] mRotAxis;
94
  final TwistyObject mObject;
95
  int mTouchedCubit, mTouchedFace, mNumAxis;
96
  FaceInfo[][] mInfos;
97
  float[][] mQuats;
98

    
99
///////////////////////////////////////////////////////////////////////////////////////////////////
100

    
101
  public TouchControlShapeChanging(TwistyObject object)
102
    {
103
    super( object!=null ? object.getObjectRatio() : 1.0f );
104

    
105
    mPoint = new float[3];
106
    mCamera= new float[3];
107
    mTouch = new float[3];
108
    mObject= object;
109
    mPreparationDone = false;
110
    mTmpAxis = new Static4D(0,0,0,0);
111

    
112
    if( object!=null )
113
      {
114
      mRotAxis = object.getRotationAxis() ;
115
      mNumAxis = mRotAxis.length;
116
      }
117
    else
118
      {
119
      mRotAxis = null;
120
      mNumAxis = 0;
121
      }
122
    }
123

    
124
///////////////////////////////////////////////////////////////////////////////////////////////////
125

    
126
  private FaceInfo[] computeInfos(float[][] vertices, int[][] indices, float[] position, Static4D quat, float size)
127
    {
128
    int numFaces = indices.length;
129

    
130
    int len = position.length/3;
131
    float avgX = 0.0f;
132
    float avgY = 0.0f;
133
    float avgZ = 0.0f;
134

    
135
    for(int i=0; i<len; i++)
136
      {
137
      avgX += position[3*i  ];
138
      avgY += position[3*i+1];
139
      avgZ += position[3*i+2];
140
      }
141

    
142
    avgX /= len;
143
    avgY /= len;
144
    avgZ /= len;
145

    
146
    FaceInfo[] infos = new FaceInfo[numFaces];
147
    Static4D tmp;
148

    
149
    for(int i=0; i<numFaces; i++)
150
      {
151
      int numVerts = indices[i].length;
152
      float[][] verts = new float[numVerts][4];
153

    
154
      for(int j=0; j<numVerts; j++)
155
        {
156
        int index = indices[i][j];
157
        float x = vertices[index][0];
158
        float y = vertices[index][1];
159
        float z = vertices[index][2];
160
        float w = 1.0f;
161

    
162
        tmp = QuatHelper.rotateVectorByQuat(x,y,z,w,quat);
163

    
164
        verts[j][0] = tmp.get0() + avgX;
165
        verts[j][1] = tmp.get1() + avgY;
166
        verts[j][2] = tmp.get2() + avgZ;
167
        verts[j][3] = 1.0f;
168
        }
169

    
170
      infos[i] = new FaceInfo(verts,size);
171
      }
172

    
173
    return infos;
174
    }
175

    
176
///////////////////////////////////////////////////////////////////////////////////////////////////
177
// software implementation of DistortedLibrary.mainVertexShader.degree() function.
178
// (limited to regions centered at [0,0,0])
179

    
180
  private float computeVertexDegree(float radius, float[] vert)
181
    {
182
    float x = vert[0];
183
    float y = vert[1];
184
    float z = vert[2];
185

    
186
    float len = (float)Math.sqrt(x*x + y*y + z*z);
187
    return len>radius ? 0.0f : 1.0f-len/radius;
188
    }
189

    
190
///////////////////////////////////////////////////////////////////////////////////////////////////
191
// software implementation of DistortedLibrary.VertexEffectSink
192

    
193
  private float[] adjustVert(float pillow, float radius, float[] vert)
194
    {
195
    float[] output = new float[3];
196
    float deg = computeVertexDegree(radius,vert);
197
    float t = 1.0f - deg*(1.0f-pillow)/pillow;
198
    output[0] = t*vert[0];
199
    output[1] = t*vert[1];
200
    output[2] = t*vert[2];
201

    
202
    return output;
203
    }
204

    
205
///////////////////////////////////////////////////////////////////////////////////////////////////
206

    
207
  private float[][] adjustVerticesForPillow(float pillow, float radius, float[][] verts)
208
    {
209
    int num = verts.length;
210
    float[][] output = new float[num][3];
211
    for(int i=0; i<num; i++) output[i] = adjustVert(pillow,radius,verts[i]);
212
    return output;
213
    }
214

    
215
///////////////////////////////////////////////////////////////////////////////////////////////////
216

    
217
  private void prepare()
218
    {
219
    float ratio = mObject.getObjectRatio();
220
    setObjectRatio(ratio);
221
    int[] numLayers = mObject.getNumLayers();
222
    float[][] positions = mObject.getCubitPositions(numLayers);
223
    float size = mObject.getSize();
224
    mNumCubits = positions.length;
225
    mNumFaces  = new int[mNumCubits];
226

    
227
    mInfos = new FaceInfo[mNumCubits][];
228
    float pillow = mObject.getPillowCoeff();
229
    float radius = mObject.getCircumscribedRadius();
230

    
231
    for(int i=0; i<mNumCubits; i++)
232
      {
233
      int variant = mObject.getCubitVariant(i,numLayers);
234
      ObjectShape shape = mObject.getObjectShape(variant);
235
      Static4D quat = mObject.getCubitQuats(i,numLayers);
236
      float[][] vertices = shape.getVertices();
237
      int[][] indices = shape.getVertIndices();
238
      if( pillow!=1.0f ) vertices = adjustVerticesForPillow(pillow,radius,vertices);
239

    
240
      mInfos[i] = computeInfos(vertices,indices,positions[i],quat,size);
241
      mNumFaces[i] = indices.length;
242
      }
243

    
244
    Static4D[] quats = mObject.getQuats();
245
    int numQuats = quats.length;
246

    
247
    mQuats = new float[numQuats][4];
248

    
249
    for(int i=0; i<numQuats; i++)
250
      {
251
      Static4D q = quats[i];
252
      mQuats[i][0] = q.get0();
253
      mQuats[i][1] = q.get1();
254
      mQuats[i][2] = q.get2();
255
      mQuats[i][3] = q.get3();
256
      }
257

    
258
    mPreparationDone = true;
259
    }
260

    
261
///////////////////////////////////////////////////////////////////////////////////////////////////
262
// points A, B, C are co-linear. Return true iff B is between A and C on this line.
263
// Compute D1 = A-B, D2=C-B. Then D1 and D2 are parallel vectors.
264
// They disagree in direction iff |D1+D2|<|D1-D2|
265

    
266
  private boolean isBetween(float ax, float ay, float az,
267
                            float bx, float by, float bz,
268
                            float cx, float cy, float cz)
269
    {
270
    float d1x = ax-bx;
271
    float d1y = ay-by;
272
    float d1z = az-bz;
273

    
274
    float d2x = cx-bx;
275
    float d2y = cy-by;
276
    float d2z = cz-bz;
277

    
278
    float sx = d1x+d2x;
279
    float sy = d1y+d2y;
280
    float sz = d1z+d2z;
281

    
282
    float dx = d1x-d2x;
283
    float dy = d1y-d2y;
284
    float dz = d1z-d2z;
285

    
286
    return sx*sx+sy*sy+sz*sz < dx*dx+dy*dy+dz*dz;
287
    }
288

    
289
///////////////////////////////////////////////////////////////////////////////////////////////////
290
// General algorithm: shoot a half-line from the 'point' and count how many
291
// sides of the polygon it intersects with. The point is inside iff this number
292
// is odd. Note that this works also in case of concave polygons.
293
//
294
// Arbitrarily take point P on the plane ( we have decided on P=(vert[0]+vert[1])/2 )
295
// as the other point defining the half-line.
296
// 'point' and 'P' define a line L1 in 3D. Then for each side the pair of its vertices
297
// defines a line L2. If L1||L2 return false. Otherwise, the lines are skew so it's
298
// possible to compute points C1 and C2 on lines L1 and L2 which are closest to the
299
// other line and check if
300
//
301
// a) C1 and P are on the same side of 'point'
302
//    (which happens iff 'point' is not in between of C1 and P)
303
// b) C2 is between the two vertices.
304
//
305
// Both a) and b) together mean that the half-line intersects with side defined by (p2,d2)
306
//
307
// C1 and C2 can be computed in the following way:
308
// Let n = d1 x d2 - then vector n is perpendicular to both d1 and d2 --> (c1-c2) is
309
// parallel to n.
310
// There exist real numbers A,B,C such that
311
// c1 = p1 + A*d1
312
// c2 = p2 + B*d2 and
313
// c2 = c1 + C*n so that
314
// p1 + A*d1 + C*n = p2 + B*d2  --> (p1-p2) + A*d1 = B*d2 - C*n (*)
315
// Let n2 = n x d2. Let's multiply both sides of (*) by n2. Then
316
// (p1-p2)*n2 + A*(d1*n2) = 0 (0 because d1*n2 = n*n2 = 0)
317
// and from that A = [(p1-p2)*n2]/[d1*n2]
318
// Similarly     B = [(p2-p1)*n1]/[d2*n1]  where n1 = n x d1.
319

    
320
  private boolean isInside(float[] point, float[][] vertices)
321
    {
322
    float e1x = (vertices[0][0]+vertices[1][0])/2;
323
    float e1y = (vertices[0][1]+vertices[1][1])/2;
324
    float e1z = (vertices[0][2]+vertices[1][2])/2;
325

    
326
    float d1x = e1x - point[0];
327
    float d1y = e1y - point[1];
328
    float d1z = e1z - point[2];
329

    
330
    float ax = vertices[0][0] - vertices[1][0];
331
    float ay = vertices[0][1] - vertices[1][1];
332
    float az = vertices[0][2] - vertices[1][2];
333

    
334
    float normX = d1y*az - d1z*ay;
335
    float normY = d1z*ax - d1x*az;
336
    float normZ = d1x*ay - d1y*ax;
337

    
338
    float n1x = d1y*normZ - d1z*normY;
339
    float n1y = d1z*normX - d1x*normZ;
340
    float n1z = d1x*normY - d1y*normX;
341

    
342
    float p1x = point[0];
343
    float p1y = point[1];
344
    float p1z = point[2];
345

    
346
    int len = vertices.length;
347
    int numCrossings = 0;
348

    
349
    for(int side=0; side<len; side++)
350
      {
351
      float p2x = vertices[side][0];
352
      float p2y = vertices[side][1];
353
      float p2z = vertices[side][2];
354

    
355
      int next = side==len-1 ? 0 : side+1;
356

    
357
      float e2x = vertices[next][0];
358
      float e2y = vertices[next][1];
359
      float e2z = vertices[next][2];
360

    
361
      float d2x = e2x-p2x;
362
      float d2y = e2y-p2y;
363
      float d2z = e2z-p2z;
364

    
365
      float nx = d2y*d1z - d2z*d1y;
366
      float ny = d2z*d1x - d2x*d1z;
367
      float nz = d2x*d1y - d2y*d1x;
368

    
369
      float n2x = d2y*nz - d2z*ny;
370
      float n2y = d2z*nx - d2x*nz;
371
      float n2z = d2x*ny - d2y*nx;
372

    
373
      float dpx = p1x-p2x;
374
      float dpy = p1y-p2y;
375
      float dpz = p1z-p2z;
376

    
377
      float A1 =-dpx*n2x-dpy*n2y-dpz*n2z;
378
      float B1 = d1x*n2x+d1y*n2y+d1z*n2z;
379

    
380
      float A2 = dpx*n1x+dpy*n1y+dpz*n1z;
381
      float B2 = d2x*n1x+d2y*n1y+d2z*n1z;
382

    
383
      if( B1==0 || B2==0 ) continue;
384

    
385
      float C1 = A1/B1;
386
      float C2 = A2/B2;
387

    
388
      float c1x = p1x + C1*d1x;
389
      float c1y = p1y + C1*d1y;
390
      float c1z = p1z + C1*d1z;
391

    
392
      float c2x = p2x + C2*d2x;
393
      float c2y = p2y + C2*d2y;
394
      float c2z = p2z + C2*d2z;
395

    
396
      if( !isBetween(c1x,c1y,c1z, p1x,p1y,p1z, e1x,e1y,e1z ) &&
397
           isBetween(p2x,p2y,p2z, c2x,c2y,c2z, e2x,e2y,e2z )  )
398
        {
399
        numCrossings++;
400
        }
401
      }
402

    
403
    return (numCrossings%2)==1;
404
    }
405

    
406
///////////////////////////////////////////////////////////////////////////////////////////////////
407

    
408
  private void rotateVertices(float[][] points, float[][] rotated, float[] quat)
409
    {
410
    int numPoints = points.length;
411

    
412
    for(int i=0; i<numPoints; i++)
413
      {
414
      QuatHelper.rotateVectorByQuat(rotated[i],points[i],quat);
415
      }
416
    }
417

    
418
///////////////////////////////////////////////////////////////////////////////////////////////////
419
// given precomputed mCamera and mPoint, respectively camera and touch point positions in ScreenSpace,
420
// a normalVec (nx,ny,nz) and distance (which together define a plane) compute point 'output[]' which:
421
// 1) lies on this plane
422
// 2) is co-linear with mCamera and mPoint
423
//
424
// output = camera + alpha*(point-camera), where alpha = [dist-normalVec*camera] / [normalVec*(point-camera)]
425

    
426
  void castTouchPointOntoFace(float nx, float ny, float nz, float distance, float[] output)
427
    {
428
    float d0 = mPoint[0]-mCamera[0];
429
    float d1 = mPoint[1]-mCamera[1];
430
    float d2 = mPoint[2]-mCamera[2];
431

    
432
    float denom = nx*d0 + ny*d1 + nz*d2;
433

    
434
    if( denom != 0.0f )
435
      {
436
      float axisCam = nx*mCamera[0] + ny*mCamera[1] + nz*mCamera[2];
437
      float alpha = (distance-axisCam)/denom;
438

    
439
      output[0] = mCamera[0] + d0*alpha;
440
      output[1] = mCamera[1] + d1*alpha;
441
      output[2] = mCamera[2] + d2*alpha;
442
      }
443
    }
444

    
445
///////////////////////////////////////////////////////////////////////////////////////////////////
446

    
447
  private boolean cubitFaceIsVisible(float nx, float ny, float nz, float distance)
448
    {
449
    return mCamera[0]*nx + mCamera[1]*ny + mCamera[2]*nz > distance;
450
    }
451

    
452
///////////////////////////////////////////////////////////////////////////////////////////////////
453
// FaceInfo defines a 3D plane (by means of a unit normal vector 'vector' and distance from the origin
454
// 'distance') and a list of points on the plane ('vertices').
455
//
456
// 0) rotate the face normal vector by quat
457
// 1) see if the face is visible. If not, return NOT_TOUCHED
458
// 2) else, cast the line passing through mPoint and mCamera onto this plane
459
// 3) if Z of this point is further from us than the already computed closestSoFar, return NOT_TOUCHED
460
// 4) else, rotate 'vertices' by quat and see if the casted point lies inside the polygon defined by them
461
// 5) if yes, return the distance form this point to the camera; otherwise, return NOT_TOUCHED
462

    
463
  private float cubitFaceTouched(FaceInfo info, float[] quat, float closestSoFar)
464
    {
465
    QuatHelper.rotateVectorByQuat(mTmp,info.normal,quat);
466
    float nx = mTmp[0];
467
    float ny = mTmp[1];
468
    float nz = mTmp[2];
469

    
470
    if( cubitFaceIsVisible(nx,ny,nz,info.distance) )
471
      {
472
      castTouchPointOntoFace(nx,ny,nz,info.distance,mTouch);
473

    
474
      float dx = mTouch[0]-mCamera[0];
475
      float dy = mTouch[1]-mCamera[1];
476
      float dz = mTouch[2]-mCamera[2];
477
      float dist = dx*dx + dy*dy + dz*dz;
478

    
479
      if( dist<closestSoFar )
480
        {
481
        rotateVertices(info.vertices,info.rotated,quat);
482
        if( isInside(mTouch,info.rotated) ) return dist;
483
        }
484
      }
485

    
486
    return NOT_TOUCHED;
487
    }
488

    
489
///////////////////////////////////////////////////////////////////////////////////////////////////
490

    
491
  int computeRow(int cubit, int rotIndex)
492
    {
493
    int row = mObject.getCubitRotRow(cubit,rotIndex);
494

    
495
    for(int index=0; index<32; index++)
496
      {
497
      if( (row&1)==1 ) return index;
498
      row>>=1;
499
      }
500

    
501
    return 0;
502
    }
503

    
504
///////////////////////////////////////////////////////////////////////////////////////////////////
505
// PUBLIC API
506
///////////////////////////////////////////////////////////////////////////////////////////////////
507

    
508
  public boolean objectTouched(Static4D rotatedTouchPoint, Static4D rotatedCamera)
509
    {
510
    if( !mPreparationDone ) prepare();
511

    
512
    mPoint[0]  = rotatedTouchPoint.get0()/mObjectRatio;
513
    mPoint[1]  = rotatedTouchPoint.get1()/mObjectRatio;
514
    mPoint[2]  = rotatedTouchPoint.get2()/mObjectRatio;
515

    
516
    mCamera[0] = rotatedCamera.get0()/mObjectRatio;
517
    mCamera[1] = rotatedCamera.get1()/mObjectRatio;
518
    mCamera[2] = rotatedCamera.get2()/mObjectRatio;
519

    
520
    float closestSoFar = NOT_TOUCHED;
521
    mTouchedCubit = -1;
522
    mTouchedFace  = -1;
523
    int numQuats = mQuats.length;
524

    
525
    for(int cubit=0; cubit<mNumCubits; cubit++)
526
      {
527
      int quatIndex = mObject.getCubitQuatIndex(cubit);
528

    
529
      if( quatIndex<numQuats )
530
        {
531
        float[] quat = mQuats[quatIndex];
532

    
533
        for(int face=0; face<mNumFaces[cubit]; face++)
534
          {
535
          float dist = cubitFaceTouched(mInfos[cubit][face],quat,closestSoFar);
536

    
537
          if( dist!=NOT_TOUCHED )
538
            {
539
            mTouchedCubit= cubit;
540
            mTouchedFace = face;
541
            closestSoFar = dist;
542
            }
543
          }
544
        }
545
      }
546
/*
547
    if( closestSoFar!=NOT_TOUCHED )
548
      {
549
      android.util.Log.e("D", "cubit="+mTouchedCubit+" face="+mTouchedFace+" result: "+closestSoFar);
550
      }
551
*/
552
    return closestSoFar!=NOT_TOUCHED;
553
    }
554

    
555
///////////////////////////////////////////////////////////////////////////////////////////////////
556
// really implemented in derived classes; here present only because we need to be able to
557
// instantiate an object of this class for MODE_REPLACE.
558

    
559
  public void newRotation(int[] output, Static4D rotatedTouchPoint, Static4D quat)
560
    {
561

    
562
    }
563

    
564
///////////////////////////////////////////////////////////////////////////////////////////////////
565

    
566
  public void getCastedRotAxis(float[] output, Static4D quat, int axisIndex)
567
    {
568
    Static3D rotAxis = mRotAxis[axisIndex];
569
    float rx = rotAxis.get0();
570
    float ry = rotAxis.get1();
571
    float rz = rotAxis.get2();
572

    
573
    mTmpAxis.set(rx,ry,rz,0);
574
    Static4D result = QuatHelper.rotateVectorByQuat(mTmpAxis, quat);
575

    
576
    float cx =result.get0();
577
    float cy =result.get1();
578

    
579
    float len = (float)Math.sqrt(cx*cx+cy*cy);
580

    
581
    if( len!=0 )
582
      {
583
      output[0] = cx/len;
584
      output[1] = cy/len;
585
      }
586
    else
587
      {
588
      output[0] = 1;
589
      output[1] = 0;
590
      }
591
    }
592

    
593
///////////////////////////////////////////////////////////////////////////////////////////////////
594

    
595
  public void prepareAgain()
596
    {
597
    mPreparationDone = false;
598
    }
599

    
600
///////////////////////////////////////////////////////////////////////////////////////////////////
601

    
602
  public int getTouchedCubitFace()
603
    {
604
    return mTouchedFace;
605
    }
606

    
607
///////////////////////////////////////////////////////////////////////////////////////////////////
608

    
609
  public int getTouchedCubit()
610
    {
611
    return mTouchedCubit;
612
    }
613

    
614
///////////////////////////////////////////////////////////////////////////////////////////////////
615

    
616
  public float returnRotationFactor(int[] numLayers, int row)
617
    {
618
    return 1.0f;
619
    }
620
  }
(9-9/13)