Project

General

Profile

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

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

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 abstract 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][2];
53
      float a3 = vertices[0][2] - vertices[1][3];
54
      float b1 = vertices[1][0] - vertices[2][0];
55
      float b2 = vertices[1][1] - vertices[2][2];
56
      float b3 = vertices[1][2] - vertices[2][3];
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 mTouchedSide;
96

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

    
99
  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][3];
137

    
138
      for(int j=0; j<numFaces; 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
        }
152

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

    
156
    return infos;
157
    }
158

    
159
///////////////////////////////////////////////////////////////////////////////////////////////////
160

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

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

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

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

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

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

    
195
    mPreparationDone = true;
196
    }
197

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

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

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

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

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

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

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

    
241
///////////////////////////////////////////////////////////////////////////////////////////////////
242
// vertices are counterclockwise
243

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

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

    
255
    return true;
256
    }
257

    
258
///////////////////////////////////////////////////////////////////////////////////////////////////
259

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

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

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

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

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

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

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

    
297
///////////////////////////////////////////////////////////////////////////////////////////////////
298

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

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

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

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

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

    
333
    return NOT_TOUCHED;
334
    }
335

    
336
///////////////////////////////////////////////////////////////////////////////////////////////////
337
// PUBLIC API
338
///////////////////////////////////////////////////////////////////////////////////////////////////
339

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

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

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

    
352
    float closestSoFar = NOT_TOUCHED;
353
    mTouchedCubit = -1;
354
    mTouchedSide  = -1;
355

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

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

    
373
    return closestSoFar!=NOT_TOUCHED;
374
    }
375

    
376
///////////////////////////////////////////////////////////////////////////////////////////////////
377
// TODO
378

    
379
  public void newRotation(int[] output, Static4D rotatedTouchPoint)
380
    {
381
    if( !mPreparationDone ) prepare();
382

    
383
    int rotIndex = 0;
384
    int row = 0;
385

    
386
    output[0] = rotIndex;
387
    output[1] = row;
388
    }
389

    
390
///////////////////////////////////////////////////////////////////////////////////////////////////
391
// TODO
392

    
393
  public void getCastedRotAxis(float[] output, Static4D quat, int rotIndex)
394
    {
395
    output[0] = 1.0f;
396
    output[1] = 0.0f;
397
    }
398

    
399
///////////////////////////////////////////////////////////////////////////////////////////////////
400
// replace mode only
401

    
402
  public int getTouchedFace()
403
    {
404
    return -1;
405
    }
406

    
407
///////////////////////////////////////////////////////////////////////////////////////////////////
408
// replace mode only
409

    
410
  public float[] getTouchedPoint3D()
411
    {
412
    return null;
413
    }
414
  }
(6-6/8)