Project

General

Profile

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

library / src / main / java / org / distorted / library / type / Dynamic.java @ 649544b8

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.Random;
23
import java.util.Vector;
24

    
25
///////////////////////////////////////////////////////////////////////////////////////////////////
26
/** A class to interpolate between a List of Static{1,2,3,4}Ds.
27
* <p><ul>
28
* <li>if there is only one Point, just jump to it.
29
* <li>if there are two Points, linearly bounce between them
30
* <li>if there are more, interpolate a loop (or a path!) between them.
31
* </ul>
32
*/
33

    
34
// The way Interpolation between more than 2 Points is done:
35
// 
36
// Def: let w[i] = (w[i](x), w[i](y), w[i](z)) be the direction and speed we have to be flying at Point P[i]
37
//
38
// time it takes to fly though one segment v[i] --> v[i+1] : 0.0 --> 1.0
39
// w[i] should be parallel to v[i+1] - v[i-1]   (cyclic notation)
40
// |w[i]| proportional to | P[i]-P[i+1] |
41
//
42
// Given that the flight route (X(t), Y(t), Z(t)) from P(i) to P(i+1)  (0<=t<=1) has to satisfy
43
// X(0) = P[i  ](x), Y(0)=P[i  ](y), Z(0)=P[i  ](z), X'(0) = w[i  ](x), Y'(0) = w[i  ](y), Z'(0) = w[i  ](z)
44
// X(1) = P[i+1](x), Y(1)=P[i+1](y), Z(1)=P[i+1](z), X'(1) = w[i+1](x), Y'(1) = w[i+1](y), Z'(1) = w[i+1](z)
45
//
46
// we have the solution:  X(t) = at^3 + bt^2 + ct + d where
47
// a =  2*P[i](x) +   w[i](x) - 2*P[i+1](x) + w[i+1](x)
48
// b = -3*P[i](x) - 2*w[i](x) + 3*P[i+1](x) - w[i+1](x)
49
// c = w[i](x)<br>
50
// d = P[i](x)
51
//
52
// and similarly Y(t) and Z(t).
53

    
54
public abstract class Dynamic
55
  {
56
  /**
57
   * One revolution takes us from the first vector to the last and back to first through the shortest path. 
58
   */
59
  public static final int MODE_LOOP = 0; 
60
  /**
61
   * We come back from the last to the first vector through the same way we got there.
62
   */
63
  public static final int MODE_PATH = 1; 
64
  /**
65
   * We just jump back from the last point to the first.
66
   */
67
  public static final int MODE_JUMP = 2; 
68

    
69
  protected static Random mRnd = new Random();
70
  
71
  protected static final int NUM_NOISE = 5; // used iff mNoise>0.0. Number of intermediary points between each pair of adjacent vectors
72
                                            // where we randomize noise factors to make the way between the two vectors not so smooth.
73

    
74
  protected int mDimension;
75
  protected int numPoints;
76
  protected int mVecCurr;    
77
  protected boolean cacheDirty; // VectorCache not up to date
78
  protected int mMode;          // LOOP, PATH or JUMP
79
  protected long mDuration;     // number of milliseconds it takes to do a full loop/path from first vector to the last and back to the first
80
  protected float mCount;       // number of loops/paths we will do; mCount = 1.5 means we go from the first vector to the last, back to first, and to the last again. 
81
  protected float mNoise;       // how 'smooth' our path form each vector to the next is. mNoise = 0.0 (min) --> completely smooth; mNoise==1.0 (max) --> very uneven
82

    
83
  protected class VectorNoise
84
    {
85
    float[][] n;
86

    
87
    VectorNoise(int dim)
88
      {
89
      n = new float[dim][NUM_NOISE];
90

    
91
      n[0][0] = mRnd.nextFloat();
92
      for(int i=1; i<NUM_NOISE; i++) n[0][i] = n[0][i-1]+mRnd.nextFloat();
93
      float sum = n[0][NUM_NOISE-1] + mRnd.nextFloat();
94
      for(int i=0; i<NUM_NOISE; i++) n[0][i] /=sum;
95

    
96
      for(int j=1; j<dim; j++)
97
        {
98
        for(int i=0; i<NUM_NOISE; i++) n[j][i] = mRnd.nextFloat()-0.5f;
99
        }
100
      }
101
    }
102

    
103
  protected Vector<VectorNoise> vn;
104
  protected float[] mFactor;
105
  protected float[][] baseV;
106
  private float[] buffer;
107

    
108
  ///////////////////////////////////////////////////////////////////////////////////////////////////
109
  // the coefficients of the X(t), Y(t) and Z(t) polynomials: X(t) = ax*T^3 + bx*T^2 + cx*t + dx  etc.
110
  // (tangent) is the vector tangent to the path.
111
  // (cached) is the original vector from vv (copied here so when interpolating we can see if it is
112
  // still valid and if not - rebuild the Cache
113

    
114
  protected class VectorCache
115
    {
116
    float[] a;
117
    float[] b;
118
    float[] c;
119
    float[] d;
120
    float[] tangent;
121
    float[] cached;
122

    
123
    VectorCache(int dim)
124
      {
125
      a = new float[dim];
126
      b = new float[dim];
127
      c = new float[dim];
128
      d = new float[dim];
129
      tangent = new float[dim];
130
      cached = new float[dim];
131
      }
132
    }
133

    
134
  protected Vector<VectorCache> vc;
135
  protected VectorCache tmp1, tmp2;
136

    
137
///////////////////////////////////////////////////////////////////////////////////////////////////
138
// hide this from Javadoc
139
  
140
  protected Dynamic()
141
    {
142
    }
143

    
144
///////////////////////////////////////////////////////////////////////////////////////////////////
145

    
146
  protected Dynamic(int duration, float count, int dimension)
147
    {
148
    vc = new Vector<>();
149
    vn = null;
150
    numPoints = 0;
151
    cacheDirty = false;
152
    mMode = MODE_LOOP;
153
    mDuration = duration;
154
    mCount = count;
155
    mDimension = dimension;
156

    
157
    baseV = new float[mDimension][mDimension];
158
    buffer= new float[mDimension];
159
    }
160

    
161
///////////////////////////////////////////////////////////////////////////////////////////////////
162
  
163
  public void interpolateMain(float[] buffer, int offset, long currentDuration)
164
    {
165
    if( mDuration<=0.0f ) 
166
      {
167
      interpolate(buffer,offset,mCount-(int)mCount);  
168
      }
169
    else
170
      {
171
      float x = (float)currentDuration/mDuration;
172
           
173
      if( x<=mCount || mCount<=0.0f )
174
        {
175
        interpolate(buffer,offset,x-(int)x);
176
        }
177
      }
178
    }
179
  
180
///////////////////////////////////////////////////////////////////////////////////////////////////
181

    
182
  public boolean interpolateMain(float[] buffer, int offset, long currentDuration, long step)
183
    {
184
    if( mDuration<=0.0f ) 
185
      {
186
      interpolate(buffer,offset,mCount-(int)mCount);
187
      return false;
188
      }
189
     
190
    float x = (float)currentDuration/mDuration;
191
           
192
    if( x<=mCount || mCount<=0.0f )
193
      {
194
      interpolate(buffer,offset,x-(int)x);
195
        
196
      if( currentDuration+step > mDuration*mCount && mCount>0.0f )
197
        {
198
        interpolate(buffer,offset,mCount-(int)mCount);
199
        return true;
200
        }
201
      }
202
    
203
    return false;
204
    }
205

    
206
///////////////////////////////////////////////////////////////////////////////////////////////////
207

    
208
  protected float noise(float time,int vecNum)
209
    {
210
    float lower, upper, len;
211
    float d = time*(NUM_NOISE+1);
212
    int index = (int)d;
213
    if( index>=NUM_NOISE+1 ) index=NUM_NOISE;
214
    VectorNoise tmpN = vn.elementAt(vecNum);
215

    
216
    float t = d-index;
217
    t = t*t*(3-2*t);
218

    
219
    switch(index)
220
      {
221
      case 0        : for(int i=0;i<mDimension-1;i++) mFactor[i] = mNoise*tmpN.n[i+1][0]*t;
222
                      return time + mNoise*(d*tmpN.n[0][0]-time);
223
      case NUM_NOISE: for(int i=0;i<mDimension-1;i++) mFactor[i] = mNoise*tmpN.n[i+1][NUM_NOISE-1]*(1-t);
224
                      len = ((float)NUM_NOISE)/(NUM_NOISE+1);
225
                      lower = len + mNoise*(tmpN.n[0][NUM_NOISE-1]-len);
226
                      return (1.0f-lower)*(d-NUM_NOISE) + lower;
227
      default       : float ya,yb;
228

    
229
                      for(int i=0;i<mDimension-1;i++)
230
                        {
231
                        yb = tmpN.n[i+1][index  ];
232
                        ya = tmpN.n[i+1][index-1];
233
                        mFactor[i] = mNoise*((yb-ya)*t+ya);
234
                        }
235

    
236
                      len = ((float)index)/(NUM_NOISE+1);
237
                      lower = len + mNoise*(tmpN.n[0][index-1]-len);
238
                      len = ((float)index+1)/(NUM_NOISE+1);
239
                      upper = len + mNoise*(tmpN.n[0][index  ]-len);
240

    
241
                      return (upper-lower)*(d-index) + lower;
242
      }
243
    }
244

    
245
///////////////////////////////////////////////////////////////////////////////////////////////////
246
// debugging only
247

    
248
  private void printBase(String str)
249
    {
250
    String s;
251
    float t;
252

    
253
    for(int i=0; i<mDimension; i++)
254
      {
255
      s = "";
256

    
257
      for(int j=0; j<mDimension; j++)
258
        {
259
        t = ((int)(1000*baseV[i][j]))/(1000.0f);
260
        s+=(" "+t);
261
        }
262
      android.util.Log.e("dynamic", str+" base "+i+" : " + s);
263
      }
264
    }
265

    
266
///////////////////////////////////////////////////////////////////////////////////////////////////
267
// debugging only
268

    
269
  private void checkBase()
270
    {
271
    float tmp;
272

    
273
    for(int i=0; i<mDimension; i++)
274
      for(int j=i+1; j<mDimension; j++)
275
        {
276
        tmp = 0.0f;
277

    
278
        for(int k=0; k<mDimension; k++)
279
          {
280
          tmp += baseV[i][k]*baseV[j][k];
281
          }
282

    
283
        android.util.Log.e("dynamic", "vectors "+i+" and "+j+" : "+tmp);
284
        }
285

    
286
    for(int i=0; i<mDimension; i++)
287
      {
288
      tmp = 0.0f;
289

    
290
      for(int k=0; k<mDimension; k++)
291
        {
292
        tmp += baseV[i][k]*baseV[i][k];
293
        }
294

    
295
      android.util.Log.e("dynamic", "length of vector "+i+" : "+Math.sqrt(tmp));
296
      }
297
    }
298

    
299
///////////////////////////////////////////////////////////////////////////////////////////////////
300
// helper function in case we are interpolating through exactly 2 points
301

    
302
  protected void computeOrthonormalBase2(Static1D curr, Static1D next)
303
    {
304
    switch(mDimension)
305
      {
306
      case 1: baseV[0][0] = (next.x-curr.x);
307
              break;
308
      case 2: Static2D curr2 = (Static2D)curr;
309
              Static2D next2 = (Static2D)next;
310
              baseV[0][0] = (next2.x-curr2.x);
311
              baseV[0][1] = (next2.y-curr2.y);
312
              break;
313
      case 3: Static3D curr3 = (Static3D)curr;
314
              Static3D next3 = (Static3D)next;
315
              baseV[0][0] = (next3.x-curr3.x);
316
              baseV[0][1] = (next3.y-curr3.y);
317
              baseV[0][2] = (next3.z-curr3.z);
318
              break;
319
      case 4: Static4D curr4 = (Static4D)curr;
320
              Static4D next4 = (Static4D)next;
321
              baseV[0][0] = (next4.x-curr4.x);
322
              baseV[0][1] = (next4.y-curr4.y);
323
              baseV[0][2] = (next4.z-curr4.z);
324
              baseV[0][3] = (next4.w-curr4.w);
325
              break;
326
      case 5: Static5D curr5 = (Static5D)curr;
327
              Static5D next5 = (Static5D)next;
328
              baseV[0][0] = (next5.x-curr5.x);
329
              baseV[0][1] = (next5.y-curr5.y);
330
              baseV[0][2] = (next5.z-curr5.z);
331
              baseV[0][3] = (next5.w-curr5.w);
332
              baseV[0][4] = (next5.v-curr5.v);
333
              break;
334
      default: throw new RuntimeException("Unsupported dimension");
335
      }
336

    
337
    if( baseV[0][0] == 0.0f )
338
      {
339
      baseV[1][0] = 1.0f;
340
      baseV[1][1] = 0.0f;
341
      }
342
    else
343
      {
344
      baseV[1][0] = 0.0f;
345
      baseV[1][1] = 1.0f;
346
      }
347

    
348
    for(int i=2; i<mDimension; i++)
349
      {
350
      baseV[1][i] = 0.0f;
351
      }
352

    
353
    computeOrthonormalBase();
354
    }
355

    
356
///////////////////////////////////////////////////////////////////////////////////////////////////
357
// helper function in case we are interpolating through more than 2 points
358

    
359
  protected void computeOrthonormalBaseMore(float time,VectorCache vc)
360
    {
361
    for(int i=0; i<mDimension; i++)
362
      {
363
      baseV[0][i] = (3*vc.a[i]*time+2*vc.b[i])*time+vc.c[i];
364
      baseV[1][i] =  6*vc.a[i]*time+2*vc.b[i];
365
      }
366

    
367
    computeOrthonormalBase();
368
    }
369

    
370
///////////////////////////////////////////////////////////////////////////////////////////////////
371
// When this function gets called, baseV[0] and baseV[1] should have been filled with two mDimension-al
372
// vectors. This function then fills the rest of the baseV array with a mDimension-al Orthonormal base.
373
// (mDimension-2 vectors, pairwise orthogonal to each other and to the original 2). The function always
374
// leaves base[0] alone but generally speaking must adjust base[1] to make it orthogonal to base[0]!
375
// The whole baseV is then used to compute Noise.
376
//
377
// When computing noise of a point travelling along a N-dimensional path, there are three cases:
378
// a) we may be interpolating through 1 point, i.e. standing in place - nothing to do in this case
379
// b) we may be interpolating through 2 points, i.e. travelling along a straight line between them -
380
//    then pass the velocity vector in baseV[0] and anything linearly independent in base[1].
381
//    The output will then be discontinuous in dimensions>2 (sad corollary from the Hairy Ball Theorem)
382
//    but we don't care - we are travelling along a straight line, so velocity (aka baseV[0]!) does
383
//    not change.
384
// c) we may be interpolating through more than 2 points. Then interpolation formulas ensure the path
385
//    will never be a straight line, even locally -> we can pass in baseV[0] and baseV[1] the velocity
386
//    and the acceleration (first and second derivatives of the path) which are then guaranteed to be
387
//    linearly independent. Then we can ensure this is continuous in dimensions <=4. This leaves
388
//    dimension 5 (ATM WAVE is 5-dimensional) discontinuous -> WAVE will suffer from chaotic noise.
389
//
390
// Bear in mind here the 'normal' in 'orthonormal' means 'length equal to the length of the original
391
// velocity vector' (rather than the standard 1)
392

    
393
  protected void computeOrthonormalBase()
394
    {
395
    int non_zeros=0;
396
    int last_non_zero=-1;
397
    float value;
398
    for(int i=0; i<mDimension; i++)
399
      {
400
      value = baseV[0][i];
401

    
402
      if( value != 0.0f )
403
        {
404
        non_zeros++;
405
        last_non_zero=i;
406
        }
407
      }
408
                         // velocity is the 0 vector -> two consecutive points we are interpolating
409
    if( non_zeros==0 )   // through are identical -> no noise, set the base to 0 vectors.
410
      {
411
      for(int i=0; i<mDimension-1; i++)
412
        for(int j=0; j<mDimension; j++)
413
          baseV[i+1][j]= 0.0f;
414
      }
415
    else
416
      {
417
      for(int i=0; i<mDimension-1; i++)
418
        for(int j=0; j<mDimension; j++)
419
          {
420
          if( (i<last_non_zero && j==i) || (i>=last_non_zero && j==i+1) )
421
            baseV[i+1][j]= baseV[0][last_non_zero];
422
          else
423
            baseV[i+1][j]= 0.0f;
424
          }
425

    
426
      // That's it if velocity vector is already one of the standard orthonormal
427
      // vectors. Otherwise (i.e. non_zeros>1) velocity is linearly independent
428
      // to what's in baseV right now and we can use (modified!) Gram-Schmidt.
429
      //
430
      // b[0] = b[0]
431
      // b[1] = b[1] - (<b[1],b[0]>/<b[0],b[0]>)*b[0]
432
      // b[2] = b[2] - (<b[2],b[0]>/<b[0],b[0]>)*b[0] - (<b[2],b[1]>/<b[1],b[1]>)*b[1]
433
      // b[3] = b[3] - (<b[3],b[0]>/<b[0],b[0]>)*b[0] - (<b[3],b[1]>/<b[1],b[1]>)*b[1] - (<b[3],b[2]>/<b[2],b[2]>)*b[2]
434
      //
435
      // then b[i] = b[i] / |b[i]|
436

    
437
      if( non_zeros>1 )
438
        {
439
        float tmp;
440

    
441
        for(int i=1; i<mDimension; i++)
442
          {
443
          buffer[i-1]=0.0f;
444

    
445
          for(int k=0; k<mDimension; k++)
446
            {
447
            value = baseV[i-1][k];
448
            buffer[i-1] += value*value;
449
            }
450

    
451
          for(int j=0; j<i; j++)
452
            {
453
            tmp = 0.0f;
454

    
455
            for(int k=0;k<mDimension; k++)
456
              {
457
              tmp += baseV[i][k]*baseV[j][k];
458
              }
459

    
460
            tmp /= buffer[j];
461

    
462
            for(int k=0;k<mDimension; k++)
463
              {
464
              baseV[i][k] -= tmp*baseV[j][k];
465
              }
466
            }
467
          }
468

    
469
        buffer[mDimension-1]=0.0f;
470
        for(int k=0; k<mDimension; k++)
471
          {
472
          value = baseV[mDimension-1][k];
473
          buffer[mDimension-1] += value*value;
474
          }
475

    
476
        for(int i=1; i<mDimension; i++)
477
          {
478
          tmp = (float)Math.sqrt(buffer[0]/buffer[i]);
479

    
480
          for(int k=0;k<mDimension; k++)
481
            {
482
            baseV[i][k] *= tmp;
483
            }
484
          }
485
        }
486
      }
487

    
488
    //printBase("end");
489
    //checkBase();
490
    }
491

    
492
///////////////////////////////////////////////////////////////////////////////////////////////////
493
// internal debugging only!
494
  
495
  public String print()
496
    {
497
    return "duration="+mDuration+" count="+mCount+" Noise="+mNoise+" numVectors="+numPoints+" mMode="+mMode;
498
    }
499

    
500
///////////////////////////////////////////////////////////////////////////////////////////////////
501

    
502
  abstract void interpolate(float[] buffer, int offset, float time);
503

    
504
///////////////////////////////////////////////////////////////////////////////////////////////////
505
// PUBLIC API
506
///////////////////////////////////////////////////////////////////////////////////////////////////
507

    
508
/**
509
 * Sets the mode of the interpolation to Loop, Path or Jump.
510
 * <ul>
511
 * <li>Loop is when we go from the first point all the way to the last, and the back to the first through 
512
 * the shortest way.
513
 * <li>Path is when we come back from the last point back to the first the same way we got there.
514
 * <li>Jump is when we go from first to last and then jump back to the first.
515
 * </ul>
516
 * 
517
 * @param mode {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
518
 */
519

    
520
  public void setMode(int mode)
521
    {
522
    mMode = mode;  
523
    }
524

    
525
///////////////////////////////////////////////////////////////////////////////////////////////////
526
/**
527
 * Returns the number of Static{1,2,3,4}Ds this Dynamic has been fed with.
528
 *   
529
 * @return the number of Static{1,2,3,4}Ds we are currently interpolating through.
530
 */
531
  public synchronized int getNumPoints()
532
    {
533
    return numPoints;  
534
    }
535

    
536
///////////////////////////////////////////////////////////////////////////////////////////////////
537
/**
538
 * Controls how many times we want to interpolate.
539
 * <p>
540
 * Count equal to 1 means 'go from the first Static{1,2,3,4}D to the last and back'. Does not have to be an
541
 * integer - i.e. count=1.5 would mean 'start at the first Point, go to the last, come back to the first, 
542
 * go to the last again and stop'.
543
 * Count<=0 means 'go on interpolating indefinitely'.
544
 * 
545
 * @param count the number of times we want to interpolate between our collection of Static{1,2,3,4}Ds.
546
 */
547
  public void setCount(float count)
548
    {
549
    mCount = count;  
550
    }
551

    
552
///////////////////////////////////////////////////////////////////////////////////////////////////
553
/**
554
 * Sets the time it takes to do one full interpolation.
555
 * 
556
 * @param duration Time, in milliseconds, it takes to do one full interpolation, i.e. go from the first 
557
 *                 Point to the last and back. 
558
 */
559
  
560
  public void setDuration(long duration)
561
    {
562
    mDuration = duration;
563
    }
564

    
565
///////////////////////////////////////////////////////////////////////////////////////////////////
566
/**
567
 * Sets the 'smoothness' of interpolation. 
568
 * <p>
569
 * When Noise=0 (the default), we interpolate between our Points through the most smooth path possible. 
570
 * Increasing noise makes the Dynamic increasingly deviate from this path, pseudo-randomly speeding
571
 * up and slowing down, etc.
572
 * 
573
 * @param noise The noise level. Permitted range: 0 <= noise <= 1.
574
 */
575
  
576
  public synchronized void setNoise(float noise)
577
    {
578
    if( mNoise==0.0f && noise != 0.0f && vn==null )
579
      {
580
      vn = new Vector<>();
581
      for(int i=0; i<numPoints; i++) vn.add(new VectorNoise(mDimension));
582

    
583
      if( mDimension>=2 )
584
        {
585
        mFactor = new float[mDimension-1];
586
        }
587
      }
588
   
589
    if( mNoise<0.0f ) mNoise = 0.0f;
590
    if( mNoise>1.0f ) mNoise = 1.0f;
591
   
592
    mNoise = noise;
593
    }
594

    
595
///////////////////////////////////////////////////////////////////////////////////////////////////
596
// end of DistortedInterpolator
597
  }
(6-6/17)