Project

General

Profile

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

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

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2021 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Magic Cube.                                                              //
5
//                                                                                               //
6
// Magic Cube is free software: you can redistribute it and/or modify                            //
7
// it under the terms of the GNU General Public License as published by                          //
8
// the Free Software Foundation, either version 2 of the License, or                             //
9
// (at your option) any later version.                                                           //
10
//                                                                                               //
11
// Magic Cube is distributed in the hope that it will be useful,                                 //
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
14
// GNU General Public License for more details.                                                  //
15
//                                                                                               //
16
// You should have received a copy of the GNU General Public License                             //
17
// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
18
///////////////////////////////////////////////////////////////////////////////////////////////////
19

    
20
package org.distorted.objectlib.touchcontrol;
21

    
22
import org.distorted.library.main.QuatHelper;
23
import org.distorted.library.type.Static4D;
24
import org.distorted.objectlib.helpers.ObjectShape;
25
import org.distorted.objectlib.main.TwistyObject;
26

    
27
///////////////////////////////////////////////////////////////////////////////////////////////////
28

    
29
public class TouchControlShapeChanging extends TouchControl
30
  {
31
  private static final float NOT_TOUCHED = 1000000.0f;
32
  private static final float[] mTmp = new float[4];
33

    
34
  private static class FaceInfo
35
    {
36
    private final float[] vector;
37
    private final float distance;
38
    private final float[][] vertices;
39
    private final float[][] rotated;
40

    
41
    FaceInfo(float[][] verts, float size)
42
      {
43
      int numV = verts.length;
44

    
45
      vertices = new float[numV][];
46
      rotated  = new float[numV][];
47

    
48
      for(int i=0; i<numV; i++)
49
        {
50
        int len = verts[i].length;
51
        vertices[i]= new float[len];
52
        rotated[i] = new float[len];
53

    
54
        for(int j=0; j<len; j++) vertices[i][j] = verts[i][j]/size;
55
        }
56

    
57
      // assuming the first three vertices are linearly independent
58
      float a1 = vertices[0][0] - vertices[1][0];
59
      float a2 = vertices[0][1] - vertices[1][1];
60
      float a3 = vertices[0][2] - vertices[1][2];
61
      float b1 = vertices[1][0] - vertices[2][0];
62
      float b2 = vertices[1][1] - vertices[2][1];
63
      float b3 = vertices[1][2] - vertices[2][2];
64

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

    
69
      float len = (float)Math.sqrt(vx*vx+vy*vy+vz*vz);
70

    
71
      vx/=len;
72
      vy/=len;
73
      vz/=len;
74

    
75
      float dist = vx*vertices[0][0] + vy*vertices[0][1] + vz*vertices[0][2];
76

    
77
      if( dist<0 )
78
        {
79
        dist = -dist;
80
        vx = -vx;
81
        vy = -vy;
82
        vz = -vz;
83
        }
84

    
85
      vector = new float[4];
86
      vector[0] = vx;
87
      vector[1] = vy;
88
      vector[2] = vz;
89
      vector[3] = 0.0f;
90

    
91
      distance = dist;
92
      }
93
    }
94

    
95
  private final float[] mPoint, mCamera, mTouch;
96
  private final TwistyObject mObject;
97

    
98
  private float[][] mQuats;
99
  private int mNumCubits;
100
  private int[] mNumFaces;
101
  private boolean mPreparationDone;
102
  private FaceInfo[][] mInfos;
103
  private int mTouchedCubit;
104
  private int mTouchedFace;
105

    
106
///////////////////////////////////////////////////////////////////////////////////////////////////
107

    
108
  public TouchControlShapeChanging(TwistyObject object)
109
    {
110
    super(object.getObjectRatio());
111

    
112
    mPoint = new float[3];
113
    mCamera= new float[3];
114
    mTouch = new float[3];
115
    mObject= object;
116
    mPreparationDone = false;
117
    }
118

    
119
///////////////////////////////////////////////////////////////////////////////////////////////////
120

    
121
  private FaceInfo[] computeInfos(float[][] vertices, int[][] indices, float[] position, Static4D quat, float size)
122
    {
123
    int numFaces = indices.length;
124

    
125
    int len = position.length/3;
126
    float avgX = 0.0f;
127
    float avgY = 0.0f;
128
    float avgZ = 0.0f;
129

    
130
    for(int i=0; i<len; i++)
131
      {
132
      avgX += position[3*i  ];
133
      avgY += position[3*i+1];
134
      avgZ += position[3*i+2];
135
      }
136

    
137
    avgX /= len;
138
    avgY /= len;
139
    avgZ /= len;
140

    
141
    FaceInfo[] infos = new FaceInfo[numFaces];
142
    Static4D tmp;
143

    
144
    for(int i=0; i<numFaces; i++)
145
      {
146
      int numVerts = indices[i].length;
147
      float[][] verts = new float[numVerts][4];
148

    
149
      for(int j=0; j<numVerts; j++)
150
        {
151
        int index = indices[i][j];
152
        float x = vertices[index][0];
153
        float y = vertices[index][1];
154
        float z = vertices[index][2];
155
        float w = 1.0f;
156

    
157
        tmp = QuatHelper.rotateVectorByQuat(x,y,z,w,quat);
158

    
159
        verts[j][0] = tmp.get0() + avgX;
160
        verts[j][1] = tmp.get1() + avgY;
161
        verts[j][2] = tmp.get2() + avgZ;
162
        verts[j][3] = 1.0f;
163
        }
164

    
165
      infos[i] = new FaceInfo(verts,size);
166
      }
167

    
168
    return infos;
169
    }
170

    
171
///////////////////////////////////////////////////////////////////////////////////////////////////
172

    
173
  private void prepare()
174
    {
175
    int[] numLayers = mObject.getNumLayers();
176
    float[][] positions = mObject.getCubitPositions(numLayers);
177
    float size = mObject.getSize();
178
    mNumCubits = positions.length;
179
    mNumFaces  = new int[mNumCubits];
180

    
181
    mInfos = new FaceInfo[mNumCubits][];
182

    
183
    for(int i=0; i<mNumCubits; i++)
184
      {
185
      int variant = mObject.getCubitVariant(i,numLayers);
186
      ObjectShape shape = mObject.getObjectShape(variant);
187
      Static4D quat = mObject.getQuat(i,numLayers);
188
      float[][] vertices = shape.getVertices();
189
      int[][] indices = shape.getVertIndices();
190
      mInfos[i] = computeInfos(vertices,indices,positions[i],quat,size);
191
      mNumFaces[i] = indices.length;
192
      }
193

    
194
    Static4D[] quats = mObject.getQuats();
195
    int numQuats = quats.length;
196

    
197
    mQuats = new float[numQuats][4];
198

    
199
    for(int i=0; i<numQuats; i++)
200
      {
201
      Static4D q = quats[i];
202
      mQuats[i][0] = q.get0();
203
      mQuats[i][1] = q.get1();
204
      mQuats[i][2] = q.get2();
205
      mQuats[i][3] = q.get3();
206
      }
207

    
208
    mPreparationDone = true;
209
    }
210

    
211
///////////////////////////////////////////////////////////////////////////////////////////////////
212
// points A, B, C are co-linear. Return true iff B is between A and C on this line.
213
// Compute D1 = A-B, D2=C-B. Then D1 and D2 are parallel vectors.
214
// They disagree in direction iff |D1+D2|<|D1-D2|
215

    
216
  private boolean isBetween(float ax, float ay, float az,
217
                            float bx, float by, float bz,
218
                            float cx, float cy, float cz)
219
    {
220
    float d1x = ax-bx;
221
    float d1y = ay-by;
222
    float d1z = az-bz;
223

    
224
    float d2x = cx-bx;
225
    float d2y = cy-by;
226
    float d2z = cz-bz;
227

    
228
    float sx = d1x+d2x;
229
    float sy = d1y+d2y;
230
    float sz = d1z+d2z;
231

    
232
    float dx = d1x-d2x;
233
    float dy = d1y-d2y;
234
    float dz = d1z-d2z;
235

    
236
    return sx*sx+sy*sy+sz*sz < dx*dx+dy*dy+dz*dz;
237
    }
238

    
239
///////////////////////////////////////////////////////////////////////////////////////////////////
240
// General algorithm: shoot a half-line from the 'point' and count how many
241
// sides of the polygon it intersects with. The point is inside iff this number
242
// is odd. Note that this works also in case of concave polygons.
243
//
244
// Arbitrarily take point P on the plane ( we have decided on P=(vert[0]+vert[1])/2 )
245
// as the other point defining the half-line.
246
// 'point' and 'P' define a line L1 in 3D. Then for each side the pair of its vertices
247
// defines a line L2. If L1||L2 return false. Otherwise, the lines are skew so it's
248
// possible to compute points C1 and C2 on lines L1 and L2 which are closest to the
249
// other line and check if
250
//
251
// a) C1 and P are on the same side of 'point'
252
//    (which happens iff 'point' is not in between of C1 and P)
253
// b) C2 is between the two vertices.
254
//
255
// Both a) and b) together mean that the half-line intersects with side defined by (p2,d2)
256
//
257
// C1 and C2 can be computed in the following way:
258
// Let n = d1 x d2 - then vector n is perpendicular to both d1 and d2 --> (c1-c2) is
259
// parallel to n.
260
// There exist real numbers A,B,C such that
261
// c1 = p1 + A*d1
262
// c2 = p2 + B*d2 and
263
// c2 = c1 + C*n so that
264
// p1 + A*d1 + C*n = p2 + B*d2  --> (p1-p2) + A*d1 = B*d2 - C*n (*)
265
// Let n2 = n x d2. Let's multiply both sides of (*) by n2. Then
266
// (p1-p2)*n2 + A*(d1*n2) = 0 (0 because d1*n2 = n*n2 = 0)
267
// and from that A = [(p1-p2)*n2]/[d1*n2]
268
// Similarly     B = [(p2-p1)*n1]/[d2*n1]  where n1 = n x d1.
269

    
270
  private boolean isInside(float[] point, float[][] vertices)
271
    {
272
    float e1x = (vertices[0][0]+vertices[1][0])/2;
273
    float e1y = (vertices[0][1]+vertices[1][1])/2;
274
    float e1z = (vertices[0][2]+vertices[1][2])/2;
275

    
276
    float d1x = e1x - point[0];
277
    float d1y = e1y - point[1];
278
    float d1z = e1z - point[2];
279

    
280
    float ax = vertices[0][0] - vertices[1][0];
281
    float ay = vertices[0][1] - vertices[1][1];
282
    float az = vertices[0][2] - vertices[1][2];
283

    
284
    float normX = d1y*az - d1z*ay;
285
    float normY = d1z*ax - d1x*az;
286
    float normZ = d1x*ay - d1y*ax;
287

    
288
    float n1x = d1y*normZ - d1z*normY;
289
    float n1y = d1z*normX - d1x*normZ;
290
    float n1z = d1x*normY - d1y*normX;
291

    
292
    float p1x = point[0];
293
    float p1y = point[1];
294
    float p1z = point[2];
295

    
296
    int len = vertices.length;
297
    int numCrossings = 0;
298

    
299
    for(int side=0; side<len; side++)
300
      {
301
      float p2x = vertices[side][0];
302
      float p2y = vertices[side][1];
303
      float p2z = vertices[side][2];
304

    
305
      int next = side==len-1 ? 0 : side+1;
306

    
307
      float e2x = vertices[next][0];
308
      float e2y = vertices[next][1];
309
      float e2z = vertices[next][2];
310

    
311
      float d2x = e2x-p2x;
312
      float d2y = e2y-p2y;
313
      float d2z = e2z-p2z;
314

    
315
      float nx = d2y*d1z - d2z*d1y;
316
      float ny = d2z*d1x - d2x*d1z;
317
      float nz = d2x*d1y - d2y*d1x;
318

    
319
      float n2x = d2y*nz - d2z*ny;
320
      float n2y = d2z*nx - d2x*nz;
321
      float n2z = d2x*ny - d2y*nx;
322

    
323
      float dpx = p1x-p2x;
324
      float dpy = p1y-p2y;
325
      float dpz = p1z-p2z;
326

    
327
      float A1 =-dpx*n2x-dpy*n2y-dpz*n2z;
328
      float B1 = d1x*n2x+d1y*n2y+d1z*n2z;
329

    
330
      float A2 = dpx*n1x+dpy*n1y+dpz*n1z;
331
      float B2 = d2x*n1x+d2y*n1y+d2z*n1z;
332

    
333
      if( B1==0 || B2==0 ) continue;
334

    
335
      float C1 = A1/B1;
336
      float C2 = A2/B2;
337

    
338
      float c1x = p1x + C1*d1x;
339
      float c1y = p1y + C1*d1y;
340
      float c1z = p1z + C1*d1z;
341

    
342
      float c2x = p2x + C2*d2x;
343
      float c2y = p2y + C2*d2y;
344
      float c2z = p2z + C2*d2z;
345

    
346
      if( !isBetween(c1x,c1y,c1z, p1x,p1y,p1z, e1x,e1y,e1z ) &&
347
           isBetween(p2x,p2y,p2z, c2x,c2y,c2z, e2x,e2y,e2z )  )
348
        {
349
        numCrossings++;
350
        }
351
      }
352

    
353
    return (numCrossings%2)==1;
354
    }
355

    
356
///////////////////////////////////////////////////////////////////////////////////////////////////
357

    
358
  private void rotateVertices(float[][] points, float[][] rotated, float[] quat)
359
    {
360
    int numPoints = points.length;
361

    
362
    for(int i=0; i<numPoints; i++)
363
      {
364
      QuatHelper.rotateVectorByQuat(rotated[i],points[i],quat);
365
      }
366
    }
367

    
368
///////////////////////////////////////////////////////////////////////////////////////////////////
369
// given precomputed mCamera and mPoint, respectively camera and touch point positions in ScreenSpace,
370
// a normalVec (nx,ny,nz) and distance (which together define a plane) compute point 'output[]' which:
371
// 1) lies on this plane
372
// 2) is co-linear with mCamera and mPoint
373
//
374
// output = camera + alpha*(point-camera), where alpha = [dist-normalVec*camera] / [normalVec*(point-camera)]
375

    
376
  private void castTouchPointOntoFace(float nx, float ny, float nz, float distance, float[] output)
377
    {
378
    float d0 = mPoint[0]-mCamera[0];
379
    float d1 = mPoint[1]-mCamera[1];
380
    float d2 = mPoint[2]-mCamera[2];
381

    
382
    float denom = nx*d0 + ny*d1 + nz*d2;
383

    
384
    if( denom != 0.0f )
385
      {
386
      float axisCam = nx*mCamera[0] + ny*mCamera[1] + nz*mCamera[2];
387
      float alpha = (distance-axisCam)/denom;
388

    
389
      output[0] = mCamera[0] + d0*alpha;
390
      output[1] = mCamera[1] + d1*alpha;
391
      output[2] = mCamera[2] + d2*alpha;
392
      }
393
    }
394

    
395
///////////////////////////////////////////////////////////////////////////////////////////////////
396

    
397
  private boolean faceIsVisible(float nx, float ny, float nz, float distance)
398
    {
399
    return mCamera[0]*nx + mCamera[1]*ny + mCamera[2]*nz > distance;
400
    }
401

    
402
///////////////////////////////////////////////////////////////////////////////////////////////////
403
// FaceInfo defines a 3D plane (by means of a unit normal vector 'vector' and distance from the origin
404
// 'distance') and a list of points on the plane ('vertices').
405
//
406
// 0) rotate the face normal vector by quat
407
// 1) see if the face is visible. If not, return NOT_TOUCHED
408
// 2) else, cast the line passing through mPoint and mCamera onto this plane
409
// 3) if Z of this point is further from us than the already computed closestSoFar, return NOT_TOUCHED
410
// 4) else, rotate 'vertices' by quat and see if the casted point lies inside the polygon defined by them
411
// 5) if yes, return its Z; otherwise, return NOT_TOUCHED
412

    
413
  private float cubitFaceTouched(FaceInfo info, float[] quat, float closestSoFar)
414
    {
415
    QuatHelper.rotateVectorByQuat(mTmp,info.vector,quat);
416
    float nx = mTmp[0];
417
    float ny = mTmp[1];
418
    float nz = mTmp[2];
419

    
420
    if( faceIsVisible(nx,ny,nz,info.distance) )
421
      {
422
      castTouchPointOntoFace(nx,ny,nz,info.distance,mTouch);
423

    
424
      float dx = mTouch[0]-mCamera[0];
425
      float dy = mTouch[1]-mCamera[1];
426
      float dz = mTouch[2]-mCamera[2];
427
      float dist = dx*dx + dy*dy + dz*dz;
428

    
429
      if( dist<closestSoFar )
430
        {
431
        rotateVertices(info.vertices,info.rotated,quat);
432
        if( isInside(mTouch,info.rotated) ) return dist;
433
        }
434
      }
435

    
436
    return NOT_TOUCHED;
437
    }
438

    
439
///////////////////////////////////////////////////////////////////////////////////////////////////
440
// PUBLIC API
441
///////////////////////////////////////////////////////////////////////////////////////////////////
442

    
443
  public boolean objectTouched(Static4D rotatedTouchPoint, Static4D rotatedCamera)
444
    {
445
    if( !mPreparationDone ) prepare();
446

    
447
    mPoint[0]  = rotatedTouchPoint.get0()/mObjectRatio;
448
    mPoint[1]  = rotatedTouchPoint.get1()/mObjectRatio;
449
    mPoint[2]  = rotatedTouchPoint.get2()/mObjectRatio;
450

    
451
    mCamera[0] = rotatedCamera.get0()/mObjectRatio;
452
    mCamera[1] = rotatedCamera.get1()/mObjectRatio;
453
    mCamera[2] = rotatedCamera.get2()/mObjectRatio;
454

    
455
    float closestSoFar = NOT_TOUCHED;
456
    mTouchedCubit = -1;
457
    mTouchedFace  = -1;
458

    
459
    for(int cubit=0; cubit<mNumCubits; cubit++)
460
      {
461
      int quatIndex = mObject.getCubitQuatIndex(cubit);
462
      float[] quat = mQuats[quatIndex];
463

    
464
      for(int face=0; face<mNumFaces[cubit]; face++)
465
        {
466
        float dist = cubitFaceTouched(mInfos[cubit][face],quat,closestSoFar);
467

    
468
        if( dist!=NOT_TOUCHED )
469
          {
470
          mTouchedCubit= cubit;
471
          mTouchedFace = face;
472
          closestSoFar = dist;
473
          }
474
        }
475
      }
476
/*
477
    if( closestSoFar!=NOT_TOUCHED )
478
      {
479
      android.util.Log.e("D", "cubit="+mTouchedCubit+" face="+mTouchedFace+" result: "+closestSoFar);
480
      }
481
*/
482
    return closestSoFar!=NOT_TOUCHED;
483
    }
484

    
485
///////////////////////////////////////////////////////////////////////////////////////////////////
486
// TODO
487

    
488
  public void newRotation(int[] output, Static4D rotatedTouchPoint)
489
    {
490
    if( !mPreparationDone ) prepare();
491

    
492
    int rotIndex = 0;
493
    int row = 0;
494

    
495
    output[0] = rotIndex;
496
    output[1] = row;
497
    }
498

    
499
///////////////////////////////////////////////////////////////////////////////////////////////////
500
// TODO
501

    
502
  public void getCastedRotAxis(float[] output, Static4D quat, int rotIndex)
503
    {
504
    output[0] = 1.0f;
505
    output[1] = 0.0f;
506
    }
507

    
508
///////////////////////////////////////////////////////////////////////////////////////////////////
509

    
510
  public int getTouchedCubitFace()
511
    {
512
    return mTouchedFace;
513
    }
514

    
515
///////////////////////////////////////////////////////////////////////////////////////////////////
516

    
517
  public int getTouchedCubit()
518
    {
519
    return mTouchedCubit;
520
    }
521

    
522
///////////////////////////////////////////////////////////////////////////////////////////////////
523

    
524
  public float returnRotationFactor(int[] numLayers, int row)
525
    {
526
    return 1.0f;
527
    }
528
  }
(6-6/8)