Project

General

Profile

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

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

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)
42
      {
43
      vertices = verts;
44
      vector = new float[4];
45

    
46
      int numV = vertices.length;
47
      rotated = new float[numV][];
48
      for(int i=0; i<numV; i++) rotated[i] = new float[vertices[i].length];
49

    
50
      // assuming the first three vertices are linearly independent
51
      float a1 = vertices[0][0] - vertices[1][0];
52
      float a2 = vertices[0][1] - vertices[1][1];
53
      float a3 = vertices[0][2] - vertices[1][2];
54
      float b1 = vertices[1][0] - vertices[2][0];
55
      float b2 = vertices[1][1] - vertices[2][1];
56
      float b3 = vertices[1][2] - vertices[2][2];
57

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

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

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

    
68
      float dist = vx*vertices[0][0] + vy*vertices[0][1] + vz*vertices[0][2];
69

    
70
      if( dist<0 )
71
        {
72
        dist = -dist;
73
        vx = -vx;
74
        vy = -vy;
75
        vz = -vz;
76
        }
77

    
78
      vector[0] = vx;
79
      vector[1] = vy;
80
      vector[2] = vz;
81
      vector[3] = 0.0f;
82
      distance  = dist;
83
      }
84
    }
85

    
86
  private final float[] mPoint, mCamera, mTouch;
87
  private final TwistyObject mObject;
88

    
89
  private float[][] mQuats;
90
  private int mNumCubits;
91
  private int[] mNumFaces;
92
  private boolean mPreparationDone;
93
  private FaceInfo[][] mInfos;
94
  private int mTouchedCubit;
95
  private int mTouchedFace;
96

    
97
///////////////////////////////////////////////////////////////////////////////////////////////////
98

    
99
  public TouchControlShapeChanging(TwistyObject object)
100
    {
101
    mPoint = new float[3];
102
    mCamera= new float[3];
103
    mTouch = new float[3];
104
    mObject= object;
105
    mPreparationDone = false;
106
    }
107

    
108
///////////////////////////////////////////////////////////////////////////////////////////////////
109

    
110
  private FaceInfo[] computeInfos(float[][] vertices, int[][] indices, float[] position, Static4D quat)
111
    {
112
    int numFaces = indices.length;
113

    
114
    int len = position.length/3;
115
    float avgX = 0.0f;
116
    float avgY = 0.0f;
117
    float avgZ = 0.0f;
118

    
119
    for(int i=0; i<len; i++)
120
      {
121
      avgX += position[3*i  ];
122
      avgY += position[3*i+1];
123
      avgZ += position[3*i+2];
124
      }
125

    
126
    avgX /= len;
127
    avgY /= len;
128
    avgZ /= len;
129

    
130
    FaceInfo[] infos = new FaceInfo[numFaces];
131
    Static4D tmp;
132

    
133
    for(int i=0; i<numFaces; i++)
134
      {
135
      int numVerts = indices[i].length;
136
      float[][] verts = new float[numVerts][4];
137

    
138
      for(int j=0; j<numVerts; j++)
139
        {
140
        int index = indices[i][j];
141
        float x = vertices[index][0];
142
        float y = vertices[index][1];
143
        float z = vertices[index][2];
144
        float w = 1.0f;
145

    
146
        tmp = QuatHelper.rotateVectorByQuat(x,y,z,w,quat);
147

    
148
        verts[j][0] = tmp.get0() + avgX;
149
        verts[j][1] = tmp.get1() + avgY;
150
        verts[j][2] = tmp.get2() + avgZ;
151
        verts[j][3] = 1.0f;
152
        }
153

    
154
      infos[i] = new FaceInfo(verts);
155
      }
156

    
157
    return infos;
158
    }
159

    
160
///////////////////////////////////////////////////////////////////////////////////////////////////
161

    
162
  private void prepare()
163
    {
164
    int[] numLayers = mObject.getNumLayers();
165
    float[][] positions = mObject.getCubitPositions(numLayers);
166
    mNumCubits = positions.length;
167
    mNumFaces  = new int[mNumCubits];
168

    
169
    mInfos = new FaceInfo[mNumCubits][];
170

    
171
    for(int i=0; i<mNumCubits; i++)
172
      {
173
      int variant = mObject.getCubitVariant(i,numLayers);
174
      ObjectShape shape = mObject.getObjectShape(variant);
175
      Static4D quat = mObject.getQuat(i,numLayers);
176
      float[][] vertices = shape.getVertices();
177
      int[][] indices = shape.getVertIndices();
178
      mInfos[i] = computeInfos(vertices,indices,positions[i],quat);
179
      mNumFaces[i] = indices.length;
180
      }
181

    
182
    Static4D[] quats = mObject.getQuats();
183
    int numQuats = quats.length;
184

    
185
    mQuats = new float[numQuats][4];
186

    
187
    for(int i=0; i<numQuats; i++)
188
      {
189
      Static4D q = quats[i];
190
      mQuats[i][0] = q.get0();
191
      mQuats[i][1] = q.get1();
192
      mQuats[i][2] = q.get2();
193
      mQuats[i][3] = q.get3();
194
      }
195

    
196
    mPreparationDone = true;
197
    }
198

    
199
///////////////////////////////////////////////////////////////////////////////////////////////////
200
// input: four co-planar points in 3D. Guaranteed point1 != point2 != p2
201
//
202
// Draw a line through point1 and point2. This line splits the plana into two parts.
203
// Return true iff points 'p1' and 'p2' are located in different parts.
204
//
205
// Points 'p1' and 'p2' are in different parts iff vectors
206
// (p1-point2)x(point2-point1)  and (p2-point2)x(point2-point1)
207
// (which should be parallel because they are both normal to the plane) point in different directions.
208
//
209
// Two (almost) parallel vectors 'v1' and 'v2' point in different directions iff |v1+v2| < |v1-v2|
210

    
211
  private boolean areOnDifferentSides(float[] p1, float[] p2, float[] point1, float[] point2 )
212
    {
213
    float a1 = point2[0] - point1[0];
214
    float a2 = point2[1] - point1[2];
215
    float a3 = point2[2] - point1[3];
216
    float b1 = p1[0] - point2[0];
217
    float b2 = p1[1] - point2[2];
218
    float b3 = p1[2] - point2[3];
219
    float c1 = p2[0] - point2[0];
220
    float c2 = p2[1] - point2[2];
221
    float c3 = p2[2] - point2[3];
222

    
223
    float vx1 = a2*b3-a3*b2;
224
    float vy1 = a3*b1-a1*b3;
225
    float vz1 = a1*b2-a2*b1;
226

    
227
    float vx2 = a2*c3-a3*c2;
228
    float vy2 = a3*c1-a1*c3;
229
    float vz2 = a1*c2-a2*c1;
230

    
231
    float sx = vx1+vx2;
232
    float sy = vy1+vy2;
233
    float sz = vz1+vz2;
234

    
235
    float dx = vx1-vx2;
236
    float dy = vy1-vy2;
237
    float dz = vz1-vz2;
238

    
239
    return sx*sx+sy*sy+sz*sz < dx*dx+dy*dy+dz*dz;
240
    }
241

    
242
///////////////////////////////////////////////////////////////////////////////////////////////////
243
// vertices are counterclockwise
244

    
245
  private boolean isInside(float[] point, float[][] vertices )
246
    {
247
    int numVert = vertices.length;
248

    
249
    for(int i=0; i<numVert; i++)
250
      {
251
      int index1= i==numVert-1 ? 0 : i+1;
252
      int index2= i==0 ? numVert-1 : i-1;
253
      if( areOnDifferentSides(point,vertices[index2],vertices[i],vertices[index1]) ) return false;
254
      }
255

    
256
    return true;
257
    }
258

    
259
///////////////////////////////////////////////////////////////////////////////////////////////////
260

    
261
  private void rotateVertices(float[][] points, float[][] rotated, float[] quat)
262
    {
263
    int numPoints = points.length;
264

    
265
    for(int i=0; i<numPoints; i++)
266
      {
267
      QuatHelper.rotateVectorByQuat(rotated[i],points[i],quat);
268
      }
269
    }
270

    
271
///////////////////////////////////////////////////////////////////////////////////////////////////
272
// given precomputed mCamera and mPoint, respectively camera and touch point positions in ScreenSpace,
273
// a normalVec (nx,ny,nz) and distance (which together define a plane) compute point 'output[]' which:
274
// 1) lies on this plane
275
// 2) is co-linear with mCamera and mPoint
276
//
277
// output = camera + alpha*(point-camera), where alpha = [dist-normalVec*camera] / [normalVec*(point-camera)]
278

    
279
  private void castTouchPointOntoFace(float nx, float ny, float nz, float distance, float[] output)
280
    {
281
    float d0 = mPoint[0]-mCamera[0];
282
    float d1 = mPoint[1]-mCamera[1];
283
    float d2 = mPoint[2]-mCamera[2];
284

    
285
    float denom = nx*d0 + ny*d1 + nz*d2;
286

    
287
    if( denom != 0.0f )
288
      {
289
      float axisCam = nx*mCamera[0] + ny*mCamera[1] + nz*mCamera[2];
290
      float alpha = (distance-axisCam)/denom;
291

    
292
      output[0] = mCamera[0] + d0*alpha;
293
      output[1] = mCamera[1] + d1*alpha;
294
      output[2] = mCamera[2] + d2*alpha;
295
      }
296
    }
297

    
298
///////////////////////////////////////////////////////////////////////////////////////////////////
299

    
300
  private boolean faceIsVisible(float nx, float ny, float nz, float distance)
301
    {
302
    return mCamera[0]*nx + mCamera[1]*ny + mCamera[2]*nz > distance;
303
    }
304

    
305
///////////////////////////////////////////////////////////////////////////////////////////////////
306
// FaceInfo defines a 3D plane (by means of a unit normal vector 'vector' and distance from the origin
307
// 'distance') and a list of points on the plane ('vertices').
308
//
309
// 0) rotate the face normal vector by quat
310
// 1) see if the face is visible. If not, return NOT_TOUCHED
311
// 2) else, cast the line passing through mPoint and mCamera onto this plane
312
// 3) if Z of this point is further from us than the already computed closestSoFar, return NOT_TOUCHED
313
// 4) else, rotate 'vertices' by quat and see if the casted point lies inside the polygon defined by them
314
// 5) if yes, return its Z; otherwise, return NOT_TOUCHED
315

    
316
  private float cubitFaceTouched(FaceInfo info, float[] quat, float closestSoFar)
317
    {
318
    QuatHelper.rotateVectorByQuat(mTmp,info.vector,quat);
319
    float nx = mTmp[0];
320
    float ny = mTmp[1];
321
    float nz = mTmp[2];
322

    
323
    if( faceIsVisible(nx,ny,nz,info.distance) )
324
      {
325
      castTouchPointOntoFace(nx,ny,nz,info.distance,mTouch);
326

    
327
      if( mTouch[2]>closestSoFar )
328
        {
329
        rotateVertices(info.vertices,info.rotated,quat);
330
        if( isInside(mTouch,info.rotated) ) return mTouch[2];
331
        }
332
      }
333

    
334
    return NOT_TOUCHED;
335
    }
336

    
337
///////////////////////////////////////////////////////////////////////////////////////////////////
338
// PUBLIC API
339
///////////////////////////////////////////////////////////////////////////////////////////////////
340

    
341
  public boolean objectTouched(Static4D rotatedTouchPoint, Static4D rotatedCamera)
342
    {
343
    if( !mPreparationDone ) prepare();
344

    
345
    mPoint[0]  = rotatedTouchPoint.get0()/mObjectRatio;
346
    mPoint[1]  = rotatedTouchPoint.get1()/mObjectRatio;
347
    mPoint[2]  = rotatedTouchPoint.get2()/mObjectRatio;
348

    
349
    mCamera[0] = rotatedCamera.get0()/mObjectRatio;
350
    mCamera[1] = rotatedCamera.get1()/mObjectRatio;
351
    mCamera[2] = rotatedCamera.get2()/mObjectRatio;
352

    
353
    float closestSoFar = NOT_TOUCHED;
354
    mTouchedCubit = -1;
355
    mTouchedFace  = -1;
356

    
357
    for(int i=0; i<mNumCubits; i++)
358
      {
359
      int quatIndex = mObject.getCubitQuatIndex(i);
360
      float[] quat = mQuats[quatIndex];
361

    
362
      for(int j=0; j<mNumFaces[i]; j++)
363
        {
364
        float dist = cubitFaceTouched(mInfos[i][j],quat,closestSoFar);
365
        if( dist!=NOT_TOUCHED )
366
          {
367
          mTouchedCubit = i;
368
          mTouchedFace  = j;
369
          }
370
        closestSoFar = dist;
371
        }
372
      }
373

    
374
    if( closestSoFar!=NOT_TOUCHED )
375
      {
376
      android.util.Log.e("D", "cubit="+mTouchedCubit+" face="+mTouchedFace);
377
      }
378

    
379
    return closestSoFar!=NOT_TOUCHED;
380
    }
381

    
382
///////////////////////////////////////////////////////////////////////////////////////////////////
383
// TODO
384

    
385
  public void newRotation(int[] output, Static4D rotatedTouchPoint)
386
    {
387
    if( !mPreparationDone ) prepare();
388

    
389
    int rotIndex = 0;
390
    int row = 0;
391

    
392
    output[0] = rotIndex;
393
    output[1] = row;
394
    }
395

    
396
///////////////////////////////////////////////////////////////////////////////////////////////////
397
// TODO
398

    
399
  public void getCastedRotAxis(float[] output, Static4D quat, int rotIndex)
400
    {
401
    output[0] = 1.0f;
402
    output[1] = 0.0f;
403
    }
404

    
405
///////////////////////////////////////////////////////////////////////////////////////////////////
406
// replace mode only
407

    
408
  public int getTouchedFace()
409
    {
410
    return -1;
411
    }
412

    
413
///////////////////////////////////////////////////////////////////////////////////////////////////
414
// replace mode only
415

    
416
  public float[] getTouchedPoint3D()
417
    {
418
    return null;
419
    }
420

    
421
///////////////////////////////////////////////////////////////////////////////////////////////////
422

    
423
  public float returnRotationFactor(int[] numLayers, int row)
424
    {
425
    return 1.0f;
426
    }
427
  }
(6-6/8)