Project

General

Profile

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

library / src / main / java / org / distorted / library / type / DynamicQuat.java @ c45c2ab1

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2016 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Distorted.                                                               //
5
//                                                                                               //
6
// Distorted 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
// Distorted 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 Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
18
///////////////////////////////////////////////////////////////////////////////////////////////////
19

    
20
package org.distorted.library.type;
21

    
22
import java.util.Vector;
23

    
24
///////////////////////////////////////////////////////////////////////////////////////////////////
25
/** 
26
* A 4-dimensional implementation of the Dynamic class to interpolate between a list
27
* of Static4Ds.
28
* Here, the Points are assumed to be Quaternions - thus we do the Spherical Linear Interpolation, aka
29
* SLERP. Noise not supported (yet?).
30
*/
31

    
32
public class DynamicQuat extends Dynamic implements Data4D
33
  {
34
 
35
///////////////////////////////////////////////////////////////////////////////////////////////////
36
// Here we implement our own Cache as we need something slightly different.
37
// omega, sinOmega, cosOmega - angle between pair of quaternions, its sinus and cosinus.
38
//  
39
// (vx,vy,vz,vw) is the original vector from vv (copied here so when interpolating we can see if it is 
40
// still valid and if not - rebuild the Cache.
41
  
42
  private class VectorCacheQuat
43
    {
44
    float omega, sinOmega,cosOmega;
45
    float vx,vy,vz,vw;
46
    }
47
  
48
  private Vector<VectorCacheQuat> vc;
49
  private VectorCacheQuat tmp1, tmp2;
50

    
51
  private Vector<Static4D> vv;
52
  private Static4D curr, next;
53
 
54
///////////////////////////////////////////////////////////////////////////////////////////////////
55
//Abramowitz / Stegun
56

    
57
  private static float arcCos(float x)
58
    {
59
    if( x<0 )
60
      return 3.14159265358979f - (float)Math.sqrt(1+x)*(1.5707288f + 0.2121144f*x + 0.074261f*x*x + 0.0187293f*x*x*x);
61
     
62
    return (float)Math.sqrt(1-x)*(1.5707288f - 0.2121144f*x + 0.074261f*x*x - 0.0187293f*x*x*x);
63
    }
64

    
65
///////////////////////////////////////////////////////////////////////////////////////////////////
66
  
67
  private void recomputeCache()
68
    {  
69
    if( numPoints>=2 )
70
      {
71
      int i, n;  
72
     
73
      for(i=0; i<numPoints; i++)
74
        {
75
        n = i<numPoints-1 ? i+1:0;  
76
      
77
        tmp1= vc.elementAt(i);
78
        tmp2= vc.elementAt(n);
79
        curr= vv.elementAt(i);
80
        next= vv.elementAt(n);
81
      
82
        tmp1.vx = curr.x;
83
        tmp1.vy = curr.y;
84
        tmp1.vz = curr.z;
85
        tmp1.vw = curr.w;
86
    	
87
        tmp1.cosOmega = curr.x*next.x + curr.y*next.y + curr.z*next.z + curr.w*next.w;
88
      	
89
        if( tmp1.cosOmega<0 && n!=0 )  // do not invert the last quaternion even if we'd have to go the long way around!
90
          {
91
          tmp1.cosOmega = -tmp1.cosOmega;
92
          next.x = -next.x;
93
          next.y = -next.y;
94
          next.z = -next.z;
95
          next.w = -next.w;
96
          }
97
      	
98
        tmp1.sinOmega = (float)Math.sqrt(1-tmp1.cosOmega*tmp1.cosOmega);
99
        tmp1.omega = arcCos(tmp1.cosOmega);
100
        }
101
      }
102
   
103
    cacheDirty = false;
104
    }
105

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

    
117
///////////////////////////////////////////////////////////////////////////////////////////////////
118
/**
119
 * Constructor setting the speed of interpolation and the number of revolutions.
120
 *
121
 * What constitutes 'one revolution' depends on the MODE:
122
 * {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
123
 *
124
 * @param duration number of milliseconds it takes to do one revolution.
125
 * @param count    number of revolutions we will do. Count<=0 means 'infinite'.
126
 */
127
  public DynamicQuat(int duration, float count)
128
    {
129
    vv         = new Vector<>();
130
    vc         = new Vector<>();
131
    numPoints  = 0;
132
    cacheDirty = false;
133
    mMode      = MODE_LOOP;
134
    mDuration  = duration;
135
    mCount     = count;
136
    mLastPos   = -1;
137
    mAccessType = ACCESS_TYPE_RANDOM;
138
    mDimension = 4;
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
 * 
157
 * @param location the index of the Point we are setting.
158
 * @param x New value of its first float.
159
 */
160
  public synchronized void setPoint(int location, float x, float y, float z, float w)
161
    {
162
    if( location>=0 && location<numPoints )
163
      {
164
      curr = vv.elementAt(location);
165
   
166
      if( curr!=null )
167
        {
168
        curr.set(x,y,z,w);
169
        cacheDirty=true;
170
        }
171
      }
172
    }
173

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

    
201
       numPoints++;
202
       cacheDirty = true;
203
       }
204
    }
205

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

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

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

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

    
293
      numPoints--;
294
      cacheDirty = true; 
295
      return true;
296
      }
297

    
298
    return false;
299
    }
300
  
301
///////////////////////////////////////////////////////////////////////////////////////////////////
302
/**
303
 * Removes all Points.
304
 */
305
  public synchronized void removeAll()
306
    {
307
    numPoints = 0;
308
    vv.removeAllElements();
309
    vc.removeAllElements();
310
    cacheDirty = false;
311
    }
312
  
313
///////////////////////////////////////////////////////////////////////////////////////////////////
314
/**
315
 * Writes the results of interpolation between the Points at time 'time' to the passed float buffer.
316
 * Interpolation is done using the spherical linear algorithm, aka SLERP.
317
 * <p>
318
 * Since this is a 4-dimensional Dynamic, the resulting interpolated Static4D gets written
319
 * to four locations in the buffer: buffer[offset], buffer[offset+1], buffer[offset+2] and buffer[offset+3]. 
320
 * 
321
 * @param buffer Float buffer we will write the resulting Static4D to.
322
 * @param offset Offset in the buffer where to write the result.
323
 * @param time   Time of interpolation. Time=0.0 is the beginning of the first revolution, time=1.0 - the end
324
 *               of the first revolution, time=2.5 - the middle of the third revolution.
325
 *               What constitutes 'one revolution' depends on the MODE:
326
 *               {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
327
 **/
328
  synchronized void interpolate(float[] buffer, int offset, float time)
329
    {  
330
    switch(numPoints)
331
      {
332
      case 0: buffer[offset  ] = 0.0f;
333
              buffer[offset+1] = 0.0f;
334
              buffer[offset+2] = 0.0f;
335
              buffer[offset+3] = 0.0f;
336
              break;
337
      case 1: curr = vv.elementAt(0);
338
              buffer[offset  ] = curr.x; 
339
              buffer[offset+1] = curr.y;
340
              buffer[offset+2] = curr.z;
341
              buffer[offset+3] = curr.w;
342
              break;
343
      default:float t = time;
344
              float scale0, scale1;
345
  
346
              if( mMode==MODE_JUMP ) time = time*(numPoints-1);
347
              else if( mMode==MODE_PATH || numPoints==2 ) time = (time<=0.5f) ? 2*time*(numPoints-1) : 2*(1-time)*(numPoints-1);
348
              else time = time*numPoints;
349
              
350
              int vecNext, vecCurr = (int)time;
351
              time = time-vecCurr;
352
      
353
              if( vecCurr>=0 && vecCurr<numPoints )
354
                {
355
                if( cacheDirty ) recomputeCache();    // recompute cache if we have added or remove vectors since last computation
356
                   
357
                switch(mMode)
358
                  {
359
                  case MODE_LOOP: vecNext = vecCurr==numPoints-1 ? 0:vecCurr+1; 
360
                                  break;
361
                  case MODE_PATH: if( t<0.5f ) vecNext = vecCurr+1;  
362
                                  else         vecNext = vecCurr==0 ? 1 : vecCurr-1;  
363
                                  break;
364
                  case MODE_JUMP: vecNext = vecCurr==numPoints-1 ? 1:vecCurr+1;
365
                                  break;
366
                  default       : vecNext = 0;                
367
                  }
368
     
369
                curr = vv.elementAt(vecCurr);
370
                next = vv.elementAt(vecNext);
371
                tmp1 = vc.elementAt(vecCurr);
372
                tmp2 = vc.elementAt(vecNext);
373
              
374
                if( tmp2.vx!=next.x || tmp2.vy!=next.y || tmp2.vz!=next.z || tmp2.vw!=next.w ) recomputeCache();
375
               
376
                if( tmp1.sinOmega==0 )
377
                  {
378
                  scale0 = 0f;
379
                  scale1 = 1f;
380
                  }
381
                else if( tmp1.cosOmega < 0.99 ) 
382
                  {
383
                  scale0 = (float)Math.sin( (1f-time)*tmp1.omega ) / tmp1.sinOmega;
384
                  scale1 = (float)Math.sin(     time *tmp1.omega ) / tmp1.sinOmega;
385
                  }
386
                else 
387
                  {
388
                  scale0 = 1f-time;
389
                  scale1 = time;
390
                  }
391

    
392
                buffer[offset  ] = scale0*curr.x + scale1*next.x;
393
                buffer[offset+1] = scale0*curr.y + scale1*next.y; 
394
                buffer[offset+2] = scale0*curr.z + scale1*next.z; 
395
                buffer[offset+3] = scale0*curr.w + scale1*next.w; 
396
                
397
                break;
398
                }
399
      }
400
    }  
401

    
402
  }
(12-12/18)