Project

General

Profile

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

library / src / main / java / org / distorted / library / type / DynamicQuat.java @ 8c57d77b

1
////////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
3
//                                                                                               //
4
// This file is part of Distorted.                                                               //
5
//                                                                                               //
6
// This library is free software; you can redistribute it and/or                                 //
7
// modify it under the terms of the GNU Lesser General Public                                    //
8
// License as published by the Free Software Foundation; either                                  //
9
// version 2.1 of the License, or (at your option) any later version.                            //
10
//                                                                                               //
11
// This library 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 GNU                             //
14
// Lesser General Public License for more details.                                               //
15
//                                                                                               //
16
// You should have received a copy of the GNU Lesser General Public                              //
17
// License along with this library; if not, write to the Free Software                           //
18
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
19
///////////////////////////////////////////////////////////////////////////////////////////////////
20

    
21
package org.distorted.library.type;
22

    
23
import java.util.Vector;
24

    
25
///////////////////////////////////////////////////////////////////////////////////////////////////
26
/** 
27
* A 4-dimensional implementation of the Dynamic class to interpolate between a list
28
* of Static4Ds.
29
* Here, the Points are assumed to be Quaternions - thus we do the Spherical Linear Interpolation, aka
30
* SLERP. Noise not supported (yet?).
31
*
32
* Only unit quaternions represent valid rotations in 3D - and interpolating through rotations is the
33
* most common use case for this class. No effort is done to normalize the Points though.
34
*
35
* Rotation Quaternion is assumed to be in the form ( axisX*sinT, axisY*sinT, axisZ*sinT, cosT ).
36
*/
37

    
38
public class DynamicQuat extends Dynamic implements Data4D
39
  {
40

    
41
///////////////////////////////////////////////////////////////////////////////////////////////////
42
// Here we implement our own Cache as we need something slightly different.
43
// omega, sinOmega, cosOmega - angle between pair of quaternions, its sinus and cosinus.
44
//  
45
// (vx,vy,vz,vw) is the original vector from vv (copied here so when interpolating we can see if it is 
46
// still valid and if not - rebuild the Cache.
47
  
48
  private class VectorCacheQuat
49
    {
50
    float omega, sinOmega,cosOmega;
51
    float vx,vy,vz,vw;
52
    }
53
  
54
  private final Vector<VectorCacheQuat> vc;
55
  private VectorCacheQuat tmp1, tmp2;
56

    
57
  private final Vector<Static4D> vv;
58
  private Static4D curr, next;
59
 
60
///////////////////////////////////////////////////////////////////////////////////////////////////
61
// Abramowitz / Stegun
62

    
63
  private static float arcCos(float x)
64
    {
65
    if( x<0 )
66
      return 3.14159265358979f - (float)Math.sqrt(1+x)*(1.5707288f + 0.2121144f*x + 0.074261f*x*x + 0.0187293f*x*x*x);
67
     
68
    return (float)Math.sqrt(1-x)*(1.5707288f - 0.2121144f*x + 0.074261f*x*x - 0.0187293f*x*x*x);
69
    }
70

    
71
///////////////////////////////////////////////////////////////////////////////////////////////////
72

    
73
  private void recomputeCache()
74
    {  
75
    if( numPoints>=2 )
76
      {
77
      int i, n;  
78
      Static4D cu,ne;
79
      VectorCacheQuat vq;
80

    
81
      for(i=0; i<numPoints; i++)
82
        {
83
        n = i<numPoints-1 ? i+1:0;  
84
      
85
        vq= vc.elementAt(i);
86
        cu= vv.elementAt(i);
87
        ne= vv.elementAt(n);
88
      
89
        vq.vx = cu.x;
90
        vq.vy = cu.y;
91
        vq.vz = cu.z;
92
        vq.vw = cu.w;
93
    	
94
        vq.cosOmega = cu.x*ne.x + cu.y*ne.y + cu.z*ne.z + cu.w*ne.w;
95
        vq.sinOmega = (float)Math.sqrt(1-vq.cosOmega*vq.cosOmega);
96
        vq.omega    = arcCos(vq.cosOmega);
97
        }
98
      }
99
   
100
    cacheDirty = false;
101
    }
102

    
103
///////////////////////////////////////////////////////////////////////////////////////////////////
104
// PUBLIC API
105
///////////////////////////////////////////////////////////////////////////////////////////////////
106
/**
107
 * Default constructor.
108
 */
109
  public DynamicQuat()
110
    {
111
    this(0,0.5f);
112
    }
113

    
114
///////////////////////////////////////////////////////////////////////////////////////////////////
115
/**
116
 * Constructor setting the speed of interpolation and the number of revolutions.
117
 *
118
 * What constitutes 'one revolution' depends on the MODE:
119
 * {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
120
 *
121
 * @param duration number of milliseconds it takes to do one revolution.
122
 * @param count    number of revolutions we will do. Count<=0 means 'infinite'.
123
 */
124
  public DynamicQuat(int duration, float count)
125
    {
126
    vv         = new Vector<>();
127
    vc         = new Vector<>();
128
    numPoints  = 0;
129
    cacheDirty = false;
130
    mMode      = MODE_LOOP;
131
    mDuration  = duration;
132
    mCount     = count;
133
    mLastPos   =-1;
134
    mAccessType= ACCESS_TYPE_RANDOM;
135
    mDimension = 4;
136
    mSegment   =-1;
137

    
138
    initDynamic();
139
    }
140

    
141
///////////////////////////////////////////////////////////////////////////////////////////////////
142
/**
143
 * Returns the location'th Static4D.
144
 *   
145
 * @param location the index of the Point we are interested in.
146
 * @return The Static4D, if 0<=location&lt;getNumPoints(), or null otherwise.
147
 */  
148
  public synchronized Static4D getPoint(int location)
149
    {
150
    return (location>=0 && location<numPoints) ? vv.elementAt(location) : null;  
151
    }
152
  
153
///////////////////////////////////////////////////////////////////////////////////////////////////
154
/**
155
 * Resets the location'th Point.
156
 * <p>
157
 * Rotation Quaternion is assumed to be in the form ( axisX*sinT, axisY*sinT, axisZ*sinT, cosT ).
158
 *
159
 * @param location the index of the Point we are setting.
160
 * @param x New value of its first float.
161
 */
162
  public synchronized void setPoint(int location, float x, float y, float z, float w)
163
    {
164
    if( location>=0 && location<numPoints )
165
      {
166
      curr = vv.elementAt(location);
167
   
168
      if( curr!=null )
169
        {
170
        curr.set(x,y,z,w);
171
        cacheDirty=true;
172
        }
173
      }
174
    }
175

    
176
///////////////////////////////////////////////////////////////////////////////////////////////////
177
/**
178
 * Adds a new Static4D to the end of our list of Points to interpolate through.
179
 * <p>   
180
 * Only a reference to the Point gets added to the List; this means that one can add a Point 
181
 * here, and later on {@link Static4D#set(float,float,float,float)} it to some new value and
182
 * the change will be seamlessly reflected in the interpolated path.  
183
 * <p>
184
 * A Point can be added multiple times.
185
 *   
186
 * @param v The Point to add.
187
 */    
188
  public synchronized void add(Static4D v)
189
    {
190
    if( v!=null )
191
      {
192
      vv.add(v);
193
      
194
      switch(numPoints)
195
         {
196
         case 0: 
197
         case 1: vc.add(new VectorCacheQuat());
198
                 vc.add(new VectorCacheQuat());
199
        	     break;
200
         default:vc.add(new VectorCacheQuat());
201
         }
202

    
203
       numPoints++;
204
       cacheDirty = true;
205
       }
206
    }
207

    
208
///////////////////////////////////////////////////////////////////////////////////////////////////
209
/**
210
 * Adds a new Static4D to the location'th place in our List of Points to interpolate through.
211
 *   
212
 * @param location Index in our List to add the new Point at.
213
 * @param v The Static4D to add.
214
 */  
215
  public synchronized void add(int location, Static4D v)
216
    {
217
    if( v!=null )
218
      {
219
      vv.add(location, v);
220
      
221
      switch(numPoints)
222
        {
223
        case 0: 
224
        case 1: vc.add(new VectorCacheQuat());
225
                vc.add(new VectorCacheQuat());
226
                break;
227
        default:vc.add(location,new VectorCacheQuat());
228
        }
229

    
230
      numPoints++;
231
      cacheDirty = true;
232
      }
233
    }
234
  
235
///////////////////////////////////////////////////////////////////////////////////////////////////
236
/**
237
 * Removes all occurrences of Point v from the List of Points to interpolate through.  
238
 * 
239
 * @param v The Point to remove.
240
 * @return <code>true</code> if we have removed at least one Point.
241
 */
242
  public synchronized boolean remove(Static4D v)
243
    {
244
    int n = vv.indexOf(v);
245
    boolean found = false;
246
   
247
    while( n>=0 ) 
248
      {
249
      vv.remove(n);
250
     
251
      switch(numPoints)
252
        {
253
        case 0:
254
        case 1: break;
255
        case 2: vc.removeAllElements();
256
                break;
257
        default:vc.remove(n);
258
        }
259

    
260
      numPoints--;
261
      found = true;
262
      n = vv.indexOf(v);
263
      }
264
   
265
    if( found ) 
266
      {
267
      cacheDirty=true;
268
      }
269
   
270
    return found;
271
    }
272

    
273
///////////////////////////////////////////////////////////////////////////////////////////////////
274
/**
275
 * Removes a location'th Point from the List of Points we interpolate through.
276
 * 
277
 * @param location index of the Point we want to remove. 
278
 * @return <code>true</code> if location is valid, i.e. if 0<=location&lt;getNumPoints().
279
 */
280
  public synchronized boolean remove(int location)
281
    {
282
    if( location>=0 && location<numPoints ) 
283
      {
284
      vv.removeElementAt(location);
285
      
286
      switch(numPoints)
287
        {
288
        case 0: 
289
        case 1: break;
290
        case 2: vc.removeAllElements();
291
                break;
292
        default:vc.removeElementAt(location);
293
        }
294

    
295
      numPoints--;
296
      cacheDirty = true; 
297
      return true;
298
      }
299

    
300
    return false;
301
    }
302
  
303
///////////////////////////////////////////////////////////////////////////////////////////////////
304
/**
305
 * Removes all Points.
306
 */
307
  public synchronized void removeAll()
308
    {
309
    numPoints = 0;
310
    vv.removeAllElements();
311
    vc.removeAllElements();
312
    cacheDirty = false;
313
    }
314
  
315
///////////////////////////////////////////////////////////////////////////////////////////////////
316
/**
317
 * Writes the results of interpolation between the Points at time 'time' to the passed float buffer.
318
 * Interpolation is done using the spherical linear algorithm, aka SLERP.
319
 * <p>
320
 * Since this is a 4-dimensional Dynamic, the resulting interpolated Static4D gets written
321
 * to four locations in the buffer: buffer[offset], buffer[offset+1], buffer[offset+2] and buffer[offset+3]. 
322
 * 
323
 * @param buffer Float buffer we will write the resulting Static4D to.
324
 * @param offset Offset in the buffer where to write the result.
325
 * @param time   Time of interpolation. Time=0.0 is the beginning of the first revolution, time=1.0 - the end
326
 *               of the first revolution, time=2.5 - the middle of the third revolution.
327
 *               What constitutes 'one revolution' depends on the MODE:
328
 *               {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
329
 **/
330
  synchronized void interpolate(float[] buffer, int offset, float time)
331
    {
332
    switch(numPoints)
333
      {
334
      case 0: buffer[offset  ] = 0.0f;
335
              buffer[offset+1] = 0.0f;
336
              buffer[offset+2] = 0.0f;
337
              buffer[offset+3] = 0.0f;
338
              break;
339
      case 1: curr = vv.elementAt(0);
340
              buffer[offset  ] = curr.x; 
341
              buffer[offset+1] = curr.y;
342
              buffer[offset+2] = curr.z;
343
              buffer[offset+3] = curr.w;
344
              break;
345
      default:float t = time;
346
              int vecCurr, segment;
347
              float scale0, scale1;
348

    
349
              switch(mMode)
350
                {
351
                case MODE_LOOP: time = time*numPoints;
352
                                segment = (int)time;
353
                                vecCurr = segment;
354
                                break;
355
                case MODE_PATH: if( t>0.5f ) t = 1.0f-t;
356
                                time = 2*t*(numPoints-1);
357
                                segment = (int)(2*t*(numPoints-1));
358
                                vecCurr = segment;
359
                                break;
360
                case MODE_JUMP: time = time*(numPoints-1);
361
                                segment = (int)time;
362
                                vecCurr = segment;
363
                                break;
364
                default       : vecCurr = 0;
365
                                segment = 0;
366
                }
367

    
368
              if( vecCurr>=0 && vecCurr<numPoints )
369
                {
370
                int vecNext = getNext(vecCurr,t);
371

    
372
                curr = vv.elementAt(vecCurr);
373
                tmp1 = vc.elementAt(vecCurr);
374
                next = vv.elementAt(vecNext);
375

    
376
                if( cacheDirty ) recomputeCache();  // recompute cache if we have added or remove vectors since last computation
377
                else if( mSegment!= segment )       // ...or if we have just passed a vector and the vector we are currently flying to has changed
378
                  {
379
                  tmp2 = vc.elementAt(vecNext);
380

    
381
                  if( tmp2.vx!=next.x || tmp2.vy!=next.y || tmp2.vz!=next.z || tmp2.vw!=next.w ) recomputeCache();
382
                  }
383

    
384
                mSegment = segment;
385

    
386
                time = time-vecCurr;
387

    
388
                if( tmp1.sinOmega==0 )
389
                  {
390
                  scale0 = 1f;
391
                  scale1 = 0f;
392
                  }
393
                else if( tmp1.cosOmega < 0.99 )
394
                  {
395
                  scale0 = (float)Math.sin( (1f-time)*tmp1.omega ) / tmp1.sinOmega;
396
                  scale1 = (float)Math.sin(     time *tmp1.omega ) / tmp1.sinOmega;
397
                  }
398
                else
399
                  {
400
                  scale0 = 1f-time;
401
                  scale1 = time;
402
                  }
403

    
404
                buffer[offset  ] = scale0*curr.x + scale1*next.x;
405
                buffer[offset+1] = scale0*curr.y + scale1*next.y;
406
                buffer[offset+2] = scale0*curr.z + scale1*next.z;
407
                buffer[offset+3] = scale0*curr.w + scale1*next.w;
408

    
409
                break;
410
                }
411
      }
412
    }  
413

    
414
  }
(12-12/18)